Skip to content

Commit 936fc49

Browse files
Added functionality to deploy the current bundle (#1010)
## Changes * Allow deploying the current bundle. * Users can do this with a command or by clicking the "deploy" button in the dabs resource explorer view. * The outputs of the deployment are shown in an output channel. ## Tests <!-- How is this tested? -->
1 parent acfa61a commit 936fc49

File tree

9 files changed

+197
-8
lines changed

9 files changed

+197
-8
lines changed

packages/databricks-vscode/package.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,20 @@
231231
"enablement": "databricks.context.activated",
232232
"category": "Databricks"
233233
},
234+
{
235+
"command": "databricks.bundle.refreshRemoteState",
236+
"icon": "$(refresh)",
237+
"title": "Refresh remote state",
238+
"enablement": "databricks.context.activated && databricks.context.bundle.isTargetSet",
239+
"category": "Databricks"
240+
},
241+
{
242+
"command": "databricks.bundle.deploy",
243+
"icon": "$(cloud-upload)",
244+
"title": "Deploy bundle",
245+
"enablement": "databricks.context.activated && databricks.context.bundle.isTargetSet",
246+
"category": "Databricks"
247+
},
234248
{
235249
"command": "databricks.bundle.initNewProject",
236250
"title": "Initialize new project",
@@ -322,6 +336,16 @@
322336
"command": "databricks.wsfs.createFolder",
323337
"when": "view == workspaceFsView",
324338
"group": "navigation@1"
339+
},
340+
{
341+
"command": "databricks.bundle.refreshRemoteState",
342+
"when": "view == dabsResourceExplorerView",
343+
"group": "navigation@1"
344+
},
345+
{
346+
"command": "databricks.bundle.deploy",
347+
"when": "view == dabsResourceExplorerView",
348+
"group": "navigation@1"
325349
}
326350
],
327351
"databricks.cluster.filter": [
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {Disposable, window} from "vscode";
2+
import {BundleRemoteStateModel} from "./models/BundleRemoteStateModel";
3+
import {onError} from "../utils/onErrorDecorator";
4+
5+
export class BundleCommands implements Disposable {
6+
private disposables: Disposable[] = [];
7+
private outputChannel = window.createOutputChannel(
8+
"Databricks Asset Bundles"
9+
);
10+
11+
constructor(private bundleRemoteStateModel: BundleRemoteStateModel) {}
12+
13+
@onError({popup: {prefix: "Error refreshing remote state."}})
14+
async refreshRemoteState() {
15+
await this.bundleRemoteStateModel.refresh();
16+
}
17+
18+
@onError({popup: {prefix: "Error deploying the bundle."}})
19+
async deploy() {
20+
this.outputChannel.show(true);
21+
this.outputChannel.appendLine("");
22+
23+
const writeToChannel = (data: string) => {
24+
this.outputChannel.append(data);
25+
};
26+
await window.withProgress(
27+
{location: {viewId: "dabsResourceExplorerView"}},
28+
async () => {
29+
await this.bundleRemoteStateModel.deploy(
30+
writeToChannel,
31+
writeToChannel
32+
);
33+
}
34+
);
35+
}
36+
37+
dispose() {
38+
this.disposables.forEach((i) => i.dispose());
39+
}
40+
}

packages/databricks-vscode/src/bundle/models/BundleRemoteStateModel.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,34 @@ export class BundleRemoteStateModel extends BaseModelWithStateCache<BundleRemote
4141
super();
4242
}
4343

44+
public async refresh() {
45+
return await this.stateCache.refresh();
46+
}
47+
48+
@Mutex.synchronise("mutex")
49+
public async deploy(
50+
onStdOut?: (data: string) => void,
51+
onStdErr?: (data: string) => void
52+
) {
53+
if (this.target === undefined) {
54+
throw new Error("Target is undefined");
55+
}
56+
if (this.authProvider === undefined) {
57+
throw new Error("No authentication method is set");
58+
}
59+
60+
await this.cli.bundleDeploy(
61+
this.target,
62+
this.authProvider,
63+
this.workspaceFolder,
64+
this.workspaceConfigs.databrickscfgLocation,
65+
onStdOut,
66+
onStdErr
67+
);
68+
69+
await this.refresh();
70+
}
71+
4472
@withLogContext(Loggers.Extension)
4573
public init(@context ctx?: Context) {
4674
this.refreshInterval = setInterval(async () => {

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

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import {execFile as execFileCb} from "child_process";
1+
import {
2+
ChildProcessWithoutNullStreams,
3+
execFile as execFileCb,
4+
spawn,
5+
} from "child_process";
26
import {ExtensionContext, window, commands, Uri} from "vscode";
37
import {SyncDestinationMapper} from "../sync/SyncDestination";
48
import {workspaceConfigs} from "../vscode-objs/WorkspaceConfigs";
@@ -29,18 +33,58 @@ export interface ConfigEntry {
2933

3034
export type SyncType = "full" | "incremental";
3135

36+
async function waitForProcess(
37+
p: ChildProcessWithoutNullStreams,
38+
onStdOut?: (data: string) => void,
39+
onStdError?: (data: string) => void
40+
) {
41+
const output: string[] = [];
42+
p.stdout.on("data", (data) => {
43+
output.push(data.toString());
44+
if (onStdOut) {
45+
onStdOut(data.toString());
46+
}
47+
});
48+
49+
const stderr: string[] = [];
50+
p.stderr.on("data", (data) => {
51+
stderr.push(data.toString());
52+
output.push(data.toString());
53+
if (onStdError) {
54+
onStdError(data.toString());
55+
}
56+
});
57+
58+
await new Promise((resolve, reject) => {
59+
p.on("close", (code) => {
60+
if (code === 0) {
61+
resolve(output.join(""));
62+
} else {
63+
reject(stderr.join(""));
64+
}
65+
});
66+
p.on("error", reject);
67+
});
68+
69+
return output.join("");
70+
}
3271
/**
3372
* Entrypoint for all wrapped CLI commands
3473
*
3574
* Righ now this is a placeholder for a future implementation
3675
* of the databricks CLI
3776
*/
3877
export class CliWrapper {
78+
private clusterId?: string;
3979
constructor(
4080
private extensionContext: ExtensionContext,
4181
private logFilePath?: string
4282
) {}
4383

84+
public setClusterId(clusterId?: string) {
85+
this.clusterId = clusterId;
86+
}
87+
4488
get cliPath(): string {
4589
return this.extensionContext.asAbsolutePath("./bin/databricks");
4690
}
@@ -192,7 +236,7 @@ export class CliWrapper {
192236
workspaceFolder: Uri,
193237
configfilePath?: string
194238
) {
195-
const {stdout, stderr} = await execFile(
239+
const {stdout} = await execFile(
196240
this.cliPath,
197241
["bundle", "validate", "--target", target],
198242
{
@@ -201,14 +245,13 @@ export class CliWrapper {
201245
...EnvVarGenerators.getEnvVarsForCli(configfilePath),
202246
...EnvVarGenerators.getProxyEnvVars(),
203247
...authProvider.toEnv(),
248+
// eslint-disable-next-line @typescript-eslint/naming-convention
249+
DATABRICKS_CLUSTER_ID: this.clusterId,
204250
},
205251
shell: true,
206252
}
207253
);
208254

209-
if (stderr !== "") {
210-
throw new Error(stderr);
211-
}
212255
return stdout;
213256
}
214257

@@ -227,6 +270,8 @@ export class CliWrapper {
227270
...EnvVarGenerators.getEnvVarsForCli(configfilePath),
228271
...EnvVarGenerators.getProxyEnvVars(),
229272
...authProvider.toEnv(),
273+
// eslint-disable-next-line @typescript-eslint/naming-convention
274+
DATABRICKS_CLUSTER_ID: this.clusterId,
230275
},
231276
shell: true,
232277
}
@@ -237,4 +282,37 @@ export class CliWrapper {
237282
}
238283
return stdout;
239284
}
285+
async bundleDeploy(
286+
target: string,
287+
authProvider: AuthProvider,
288+
workspaceFolder: Uri,
289+
configfilePath?: string,
290+
onStdOut?: (data: string) => void,
291+
onStdError?: (data: string) => void
292+
) {
293+
if (onStdError) {
294+
onStdError(`Deploying the bundle for target ${target}...\n\n`);
295+
onStdError(`${this.cliPath} bundle deploy --target ${target}\n`);
296+
if (this.clusterId) {
297+
onStdError(`DATABRICKS_CLUSTER_ID=${this.clusterId}\n\n`);
298+
}
299+
}
300+
const p = spawn(
301+
this.cliPath,
302+
["bundle", "deploy", "--target", target],
303+
{
304+
cwd: workspaceFolder.fsPath,
305+
env: {
306+
...EnvVarGenerators.getEnvVarsForCli(configfilePath),
307+
...EnvVarGenerators.getProxyEnvVars(),
308+
...authProvider.toEnv(),
309+
// eslint-disable-next-line @typescript-eslint/naming-convention
310+
DATABRICKS_CLUSTER_ID: this.clusterId,
311+
},
312+
shell: true,
313+
}
314+
);
315+
316+
return await waitForProcess(p, onStdOut, onStdError);
317+
}
240318
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ export class ConnectionManager implements Disposable {
113113
}
114114
)
115115
: undefined;
116+
117+
this.cli.setClusterId(clusterId);
116118
this.onDidChangeClusterEmitter.fire(this.cluster);
117119
}
118120

packages/databricks-vscode/src/configuration/models/ConfigModel.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ export class ConfigModel implements Disposable {
208208
this.bundlePreValidateModel.setTarget(target),
209209
this.bundleValidateModel.setTarget(target),
210210
this.overrideableConfigModel.setTarget(target),
211+
this.bundleRemoteStateModel.setTarget(target),
211212
]);
212213
});
213214
this.onDidChangeTargetEmitter.fire();
@@ -222,6 +223,7 @@ export class ConfigModel implements Disposable {
222223
this._authProvider = authProvider;
223224
await this.readStateMutex.synchronise(async () => {
224225
await this.bundleValidateModel.setAuthProvider(authProvider);
226+
await this.bundleRemoteStateModel.setAuthProvider(authProvider);
225227
});
226228
this.onDidChangeAuthProviderEmitter.fire();
227229
}

packages/databricks-vscode/src/extension.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import {ConfigModel} from "./configuration/models/ConfigModel";
5858
import {OverrideableConfigModel} from "./configuration/models/OverrideableConfigModel";
5959
import {BundlePreValidateModel} from "./bundle/models/BundlePreValidateModel";
6060
import {BundleRemoteStateModel} from "./bundle/models/BundleRemoteStateModel";
61+
import {BundleCommands} from "./bundle/BundleCommands";
6162
import {BundleProjectManager} from "./bundle/BundleProjectManager";
6263

6364
const customWhenContext = new CustomWhenContext();
@@ -597,6 +598,21 @@ export async function activate(
597598
}
598599
})
599600
);
601+
602+
// Bundle
603+
const bundleCommands = new BundleCommands(bundleRemoteStateModel);
604+
context.subscriptions.push(
605+
telemetry.registerCommand(
606+
"databricks.bundle.refreshRemoteState",
607+
bundleCommands.refreshRemoteState,
608+
bundleCommands
609+
),
610+
telemetry.registerCommand(
611+
"databricks.bundle.deploy",
612+
bundleCommands.deploy,
613+
bundleCommands
614+
)
615+
);
600616
// generate a json schema for bundle root and load a custom provider into
601617
// redhat.vscode-yaml extension to validate bundle config files with this schema
602618
registerBundleAutocompleteProvider(

packages/databricks-vscode/src/utils/envVarGenerators.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ describe(__filename, () => {
5858
DATABRICKS_AUTH_TYPE: "metadata-service",
5959
DATABRICKS_METADATA_SERVICE_URL:
6060
"http://example.com/metadata-service",
61-
DATABRICKS_CLUSTER_ID: mockClusterId,
6261
});
6362
});
6463

packages/databricks-vscode/src/utils/envVarGenerators.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ function getUserAgent(connectionManager: ConnectionManager) {
4747
}
4848

4949
export function getAuthEnvVars(connectionManager: ConnectionManager) {
50-
const cluster = connectionManager.cluster;
5150
const host = connectionManager.databricksWorkspace?.host.toString();
5251
if (!host || !connectionManager.metadataServiceUrl) {
5352
return;
@@ -58,18 +57,19 @@ export function getAuthEnvVars(connectionManager: ConnectionManager) {
5857
DATABRICKS_HOST: host,
5958
DATABRICKS_AUTH_TYPE: "metadata-service",
6059
DATABRICKS_METADATA_SERVICE_URL: connectionManager.metadataServiceUrl,
61-
DATABRICKS_CLUSTER_ID: cluster?.id,
6260
};
6361
/* eslint-enable @typescript-eslint/naming-convention */
6462
}
6563

6664
export function getCommonDatabricksEnvVars(
6765
connectionManager: ConnectionManager
6866
) {
67+
const cluster = connectionManager.cluster;
6968
/* eslint-disable @typescript-eslint/naming-convention */
7069
return {
7170
...(getAuthEnvVars(connectionManager) || {}),
7271
...(getProxyEnvVars() || {}),
72+
DATABRICKS_CLUSTER_ID: cluster?.id,
7373
};
7474
/* eslint-enable @typescript-eslint/naming-convention */
7575
}

0 commit comments

Comments
 (0)