diff --git a/.github/workflows/create-build-artifacts.yml b/.github/workflows/create-build-artifacts.yml index d2c8bd363..382c1323f 100644 --- a/.github/workflows/create-build-artifacts.yml +++ b/.github/workflows/create-build-artifacts.yml @@ -38,7 +38,6 @@ jobs: working-directory: packages/databricks-vscode env: GH_TOKEN: ${{ github.token }} - BUILD_PLATFORM_ARCH: linux_amd64 - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: diff --git a/packages/databricks-vscode/package.json b/packages/databricks-vscode/package.json index 98569dfdd..8ba6f6545 100644 --- a/packages/databricks-vscode/package.json +++ b/packages/databricks-vscode/package.json @@ -1947,7 +1947,7 @@ "useYarn": false }, "cli": { - "version": "1.19.0" + "version": "1.20.0" }, "scripts": { "vscode:prepublish": "rm -rf out && yarn run package:compile && yarn run package:wrappers:write && yarn run package:jupyter-init-script:write && yarn run package:copy-webview-toolkit && yarn run package:copy-markdown-it && yarn run package:copy-highlight && yarn run generate-telemetry", @@ -2037,7 +2037,6 @@ "esbuild": "^0.28.1", "eslint": "^10.6.0", "eslint-plugin-local-rules": "^3.0.2", - "extract-zip": "^2.0.1", "fs-extra": "^11.2.0", "glob": "^13.0.6", "mocha": "^12.0.2", diff --git a/packages/databricks-vscode/scripts/package-vsix-tests.sh b/packages/databricks-vscode/scripts/package-vsix-tests.sh index 4789a810b..b61b28977 100755 --- a/packages/databricks-vscode/scripts/package-vsix-tests.sh +++ b/packages/databricks-vscode/scripts/package-vsix-tests.sh @@ -4,28 +4,27 @@ set -ex # Wrapper around package-vsix.sh that builds a VSIX for the host platform/arch # and records its filename for the e2e test runner (wdio.conf.ts) to pick up. -# This ensures e2e tests run against a VSIX with terraform + databricks -# provider bundled, matching what is shipped to users. +# This ensures e2e tests run against the same VSIX layout that is shipped +# to users. cd $(dirname $(realpath $0))/.. OS_RAW=$(uname -s | tr '[:upper:]' '[:lower:]') case "$OS_RAW" in - linux) PLATFORM=linux; BUILD_OS=linux ;; - darwin) PLATFORM=darwin; BUILD_OS=darwin ;; - mingw*|msys*|cygwin*) PLATFORM=win32; BUILD_OS=windows ;; + linux) PLATFORM=linux ;; + darwin) PLATFORM=darwin ;; + mingw*|msys*|cygwin*) PLATFORM=win32 ;; *) echo "Unsupported OS: $OS_RAW" >&2; exit 1 ;; esac ARCH_RAW=$(uname -m) case "$ARCH_RAW" in - x86_64|amd64) ARCH=x64; BUILD_ARCH=amd64 ;; - arm64|aarch64) ARCH=arm64; BUILD_ARCH=arm64 ;; + x86_64|amd64) ARCH=x64 ;; + arm64|aarch64) ARCH=arm64 ;; *) echo "Unsupported arch: $ARCH_RAW" >&2; exit 1 ;; esac VSIX_ARCH="${PLATFORM}-${ARCH}" -export BUILD_PLATFORM_ARCH="${BUILD_OS}_${BUILD_ARCH}" ./scripts/package-vsix.sh "$VSIX_ARCH" diff --git a/packages/databricks-vscode/scripts/package-vsix.sh b/packages/databricks-vscode/scripts/package-vsix.sh index 0cb85bf68..5b571fa50 100755 --- a/packages/databricks-vscode/scripts/package-vsix.sh +++ b/packages/databricks-vscode/scripts/package-vsix.sh @@ -43,25 +43,10 @@ case $ARCH in ;; esac -# Download databricks cli to the .build directory with the correct arch for the environment that runs this script (not the target arch of the vsix) -# This CLI is used to request metadata about CLIs terraform dependencies. -if [ ! -d ./.build ]; then - mkdir ./.build - if [ -z "$BUILD_PLATFORM_ARCH" ]; then - BUILD_PLATFORM_ARCH="$(uname -s | awk '{print tolower($0)}')_$(uname -m)" - fi - ./scripts/fetch-databricks-cli.sh $BUILD_PLATFORM_ARCH ./.build -fi - rm -rf bin ./scripts/fetch-databricks-cli.sh $CLI_ARCH yarn ts-node ./scripts/setArchInPackage.ts $VSXI_ARCH -f package.json --cliArch $CLI_ARCH -V $VSXI_ARCH -c $(git rev-parse --short HEAD) -# Don't bundle terraform for win32-arm64 as they don't support it yet: https://github.com/hashicorp/terraform/issues/32719 -if [ $ARCH != "win32-arm64" ]; then - yarn ts-node ./scripts/setupCLIDependencies.ts --cli ./.build/databricks --binDir ./bin --package ./package.json --arch $CLI_ARCH -fi - yarn run prettier package.json --write TAG="release-v$(cat package.json | jq -r .version)" yarn run package -t $VSXI_ARCH diff --git a/packages/databricks-vscode/scripts/setupCLIDependencies.ts b/packages/databricks-vscode/scripts/setupCLIDependencies.ts deleted file mode 100644 index a7cce203f..000000000 --- a/packages/databricks-vscode/scripts/setupCLIDependencies.ts +++ /dev/null @@ -1,162 +0,0 @@ -import {mkdirp} from "fs-extra"; -import assert from "node:assert"; -import {spawnSync} from "node:child_process"; -import {createHash} from "node:crypto"; -import {createReadStream} from "node:fs"; -import {cp, readFile, writeFile} from "node:fs/promises"; -import {tmpdir} from "node:os"; -import path from "node:path"; -import yargs from "yargs"; -import extract from "extract-zip"; -import type { - TerraformMetadata, - TerraformMetadataFromCli, -} from "../src/utils/terraformUtils"; - -async function main() { - const argv = await yargs(process.argv.slice(2)) - .option("cli", { - description: "Path to the Databricks CLI", - type: "string", - requiresArg: true, - }) - .option("binDir", { - description: "Path to the bin directory", - type: "string", - requiresArg: true, - }) - .option("arch", { - description: "Architecture of databricks cli.", - type: "string", - requiresArg: true, - }) - .option("package", { - description: "path/to/package.json", - type: "string", - requiresArg: true, - }).argv; - - const res = spawn(argv.cli!, [ - "bundle", - "debug", - "terraform", - "--output", - "json", - ]); - const dependencies = JSON.parse(res.stdout.toString()); - const terraform = dependencies.terraform as TerraformMetadataFromCli; - assert(terraform, "cli must return terraform dependencies"); - assert(terraform.version, "cli must return terraform version"); - assert(terraform.providerHost, "cli must return provider host"); - assert(terraform.providerSource, "cli must return provider source"); - assert(terraform.providerVersion, "cli must return provider version"); - - const tempDir = path.join(tmpdir(), `terraform_${Date.now()}`); - const depsDir = path.join(argv.binDir!, "dependencies"); - await mkdirp(tempDir); - await mkdirp(depsDir); - - // Download terraform bin for the selected arch - const arch = argv.arch!; - const terraformZip = `terraform_${terraform.version}_${arch}.zip`; - const terraformUrl = `https://releases.hashicorp.com/terraform/${terraform.version}/${terraformZip}`; - spawn("curl", ["-sLO", "--fail", terraformUrl], {cwd: tempDir}); - // Check sha of the archive - const shasumsFile = `terraform_${terraform.version}_SHA256SUMS`; - const shasumsUrl = `https://releases.hashicorp.com/terraform/${terraform.version}/${shasumsFile}`; - spawn("curl", ["-sLO", "--fail", shasumsUrl], {cwd: tempDir}); - await verifySha256( - path.join(tempDir, terraformZip), - path.join(tempDir, shasumsFile), - terraformZip - ); - await extract(path.join(tempDir, terraformZip), {dir: tempDir}); - const fileExt = arch.includes("windows") ? ".exe" : ""; - const terraformBinRelPath = path.join(depsDir, `terraform${fileExt}`); - await cp(`${tempDir}/terraform${fileExt}`, terraformBinRelPath); - // Set the path to the terraform bin, the extension will use it to setup the environment variables - const execRelPath = terraformBinRelPath; - - // Download databricks provider archive for the selected arch - const providerZip = `terraform-provider-databricks_${terraform.providerVersion}_${arch}.zip`; - const providerUrl = `https://github.com/databricks/terraform-provider-databricks/releases/download/v${terraform.providerVersion}/${providerZip}`; - spawn("curl", ["-sLO", "--fail", providerUrl], {cwd: tempDir}); - const providersMirrorRelPath = path.join(depsDir, "providers"); - const databricksProviderDir = path.join( - providersMirrorRelPath, - terraform.providerHost, - terraform.providerSource - ); - await mkdirp(databricksProviderDir); - await cp( - path.join(tempDir, providerZip), - path.join(databricksProviderDir, providerZip) - ); - // Set the path to the providers mirror dir, the extension will use it - // to create the terraform CLI config at runtime. - const terraformCliConfigRelPath = path.join(depsDir, "config.tfrc"); - - // Save the info about all dependencies to the package.json - const terraformMetadata: TerraformMetadata = { - ...terraform, - execRelPath, - providersMirrorRelPath, - terraformCliConfigRelPath, - }; - const rawData = await readFile(argv.package!, {encoding: "utf-8"}); - const jsonData = JSON.parse(rawData); - jsonData["terraformMetadata"] = terraformMetadata; - await writeFile(argv.package!, JSON.stringify(jsonData, null, 4), { - encoding: "utf-8", - }); -} - -async function sha256OfFile(filePath: string): Promise { - return new Promise((resolve, reject) => { - const hash = createHash("sha256"); - const stream = createReadStream(filePath); - stream.on("error", reject); - stream.on("data", (chunk) => hash.update(chunk)); - stream.on("end", () => resolve(hash.digest("hex"))); - }); -} - -async function verifySha256( - filePath: string, - sumsFile: string, - expectedFilename: string -) { - const sumsContent = await readFile(sumsFile, {encoding: "utf-8"}); - const line = sumsContent - .split("\n") - .find((l) => l.trim().endsWith(expectedFilename)); - assert( - line, - `Could not find ${expectedFilename} in ${path.basename(sumsFile)}` - ); - const expected = line.trim().split(/\s+/)[0]; - const actual = await sha256OfFile(filePath); - assert( - actual === expected, - `SHA256 mismatch for ${expectedFilename}: expected ${expected}, got ${actual}` - ); -} - -function spawn(command: string, args: string[], options: any = {}) { - const child = spawnSync(command, args, options); - if (child.error) { - throw child.error; - } - if (child.status !== 0) { - throw new Error( - `${command} ${args.join(" ")} exited with status ${ - child.status - }\n` + - `stdout: ${child.stdout?.toString() ?? ""}\n` + - `stderr: ${child.stderr?.toString() ?? ""}` - ); - } - return child; -} - -main(); diff --git a/packages/databricks-vscode/src/bundle/BundleEngineManager.test.ts b/packages/databricks-vscode/src/bundle/BundleEngineManager.test.ts deleted file mode 100644 index c87474081..000000000 --- a/packages/databricks-vscode/src/bundle/BundleEngineManager.test.ts +++ /dev/null @@ -1,232 +0,0 @@ -import assert from "assert"; -import {Disposable, Uri} from "vscode"; -import { - anything, - deepEqual, - instance, - mock, - reset, - verify, - when, -} from "ts-mockito"; -import {StateStorage} from "../vscode-objs/StateStorage"; -import {Telemetry} from "../telemetry"; -import {Events} from "../telemetry/constants"; -import {BundleValidateModel} from "./models/BundleValidateModel"; -import { - BundleEngineManager, - BundleEnginePrompter, - DIRECT_ENGINE_DOCS_URL, - READ_MIGRATION_GUIDE_LABEL, - DONT_SHOW_AGAIN_LABEL, -} from "./BundleEngineManager"; - -const HIDE_KEY = "databricks.bundle.hideTerraformEngineWarning"; - -/** - * Hand-rolled stand-in for {@link BundleValidateModel}: the base model exposes - * `onDidChange` and `get` as bound instance fields, which ts-mockito can't stub, - * so we drive them directly. `fire()` replays a validate-state change. - */ -class FakeValidateModel { - private listeners: Array<() => unknown> = []; - public engine: string | undefined; - // When non-empty, successive get() calls consume this queue instead of - // `engine` — used to script the value seen by concurrent invocations. - public engines: Array = []; - - onDidChange(cb: () => unknown): Disposable { - this.listeners.push(cb); - return {dispose() {}}; - } - - async get(): Promise { - return this.engines.length > 0 ? this.engines.shift() : this.engine; - } - - async fire(): Promise { - for (const cb of this.listeners) { - await cb(); - } - } - - // Invoke the listener twice without awaiting between, so the second call - // runs while the first is still awaiting get() — the concurrency the - // manager must tolerate. - async fireConcurrentTwice(): Promise { - await Promise.all(this.listeners.flatMap((cb) => [cb(), cb()])); - } -} - -function makePrompter(choice: string | undefined): { - prompter: BundleEnginePrompter; - opened: Uri[]; - shownCount: () => number; -} { - const opened: Uri[] = []; - let shown = 0; - const prompter: BundleEnginePrompter = { - showWarningMessage: (() => { - shown++; - return Promise.resolve(choice); - }) as BundleEnginePrompter["showWarningMessage"], - openExternal: ((uri: Uri) => { - opened.push(uri); - return Promise.resolve(true); - }) as BundleEnginePrompter["openExternal"], - }; - return {prompter, opened, shownCount: () => shown}; -} - -describe("BundleEngineManager", () => { - let fakeModel: FakeValidateModel; - let mockStorage: StateStorage; - let mockTelemetry: Telemetry; - - function build(prompter: BundleEnginePrompter): BundleEngineManager { - return new BundleEngineManager( - fakeModel as unknown as BundleValidateModel, - instance(mockStorage), - instance(mockTelemetry), - prompter - ); - } - - beforeEach(() => { - fakeModel = new FakeValidateModel(); - mockStorage = mock(StateStorage); - mockTelemetry = mock(Telemetry); - when(mockStorage.get(HIDE_KEY)).thenReturn(false); - }); - - afterEach(() => { - reset(mockStorage); - reset(mockTelemetry); - }); - - it("warns when the engine is terraform", async () => { - const {prompter, shownCount} = makePrompter(undefined); - build(prompter); - fakeModel.engine = "terraform"; - - await fakeModel.fire(); - - assert.strictEqual(shownCount(), 1); - }); - - it("does not warn when the engine is direct", async () => { - const {prompter, shownCount} = makePrompter(undefined); - build(prompter); - fakeModel.engine = "direct"; - - await fakeModel.fire(); - - assert.strictEqual(shownCount(), 0); - verify( - mockTelemetry.recordEvent( - Events.BUNDLE_TERRAFORM_ENGINE_WARNING, - anything() - ) - ).never(); - }); - - it("does not warn when the engine is unset", async () => { - const {prompter, shownCount} = makePrompter(undefined); - build(prompter); - fakeModel.engine = undefined; - - await fakeModel.fire(); - - assert.strictEqual(shownCount(), 0); - }); - - it("does not warn when the user has opted out", async () => { - when(mockStorage.get(HIDE_KEY)).thenReturn(true); - const {prompter, shownCount} = makePrompter(undefined); - build(prompter); - fakeModel.engine = "terraform"; - - await fakeModel.fire(); - - assert.strictEqual(shownCount(), 0); - }); - - it("warns at most once per session even across repeated validations", async () => { - const {prompter, shownCount} = makePrompter(undefined); - build(prompter); - fakeModel.engine = "terraform"; - - await fakeModel.fire(); - await fakeModel.fire(); - await fakeModel.fire(); - - assert.strictEqual(shownCount(), 1); - }); - - it("still warns once when a concurrent validate resolves to terraform", async () => { - const {prompter, shownCount} = makePrompter(undefined); - build(prompter); - // First invocation reads "direct", the concurrent second reads - // "terraform"; the terraform transition must not be dropped. - fakeModel.engines = ["direct", "terraform"]; - - await fakeModel.fireConcurrentTwice(); - - assert.strictEqual(shownCount(), 1); - }); - - it("opens the migration guide and records 'guide'", async () => { - const {prompter, opened} = makePrompter(READ_MIGRATION_GUIDE_LABEL); - build(prompter); - fakeModel.engine = "terraform"; - - await fakeModel.fire(); - - assert.strictEqual(opened.length, 1); - assert.strictEqual( - opened[0].toString(), - Uri.parse(DIRECT_ENGINE_DOCS_URL).toString() - ); - verify(mockStorage.set(HIDE_KEY, true)).never(); - verify( - mockTelemetry.recordEvent( - Events.BUNDLE_TERRAFORM_ENGINE_WARNING, - deepEqual({action: "guide"}) - ) - ).once(); - }); - - it("persists opt-out and records 'hidden' on 'Don't show again'", async () => { - const {prompter, opened} = makePrompter(DONT_SHOW_AGAIN_LABEL); - build(prompter); - fakeModel.engine = "terraform"; - - await fakeModel.fire(); - - assert.strictEqual(opened.length, 0); - verify(mockStorage.set(HIDE_KEY, true)).once(); - verify( - mockTelemetry.recordEvent( - Events.BUNDLE_TERRAFORM_ENGINE_WARNING, - deepEqual({action: "hidden"}) - ) - ).once(); - }); - - it("records 'dismissed' when the warning is closed without a choice", async () => { - const {prompter, opened} = makePrompter(undefined); - build(prompter); - fakeModel.engine = "terraform"; - - await fakeModel.fire(); - - assert.strictEqual(opened.length, 0); - verify(mockStorage.set(HIDE_KEY, true)).never(); - verify( - mockTelemetry.recordEvent( - Events.BUNDLE_TERRAFORM_ENGINE_WARNING, - deepEqual({action: "dismissed"}) - ) - ).once(); - }); -}); diff --git a/packages/databricks-vscode/src/bundle/BundleEngineManager.ts b/packages/databricks-vscode/src/bundle/BundleEngineManager.ts deleted file mode 100644 index b9a8a2a87..000000000 --- a/packages/databricks-vscode/src/bundle/BundleEngineManager.ts +++ /dev/null @@ -1,105 +0,0 @@ -import {Disposable, Uri, env, window} from "vscode"; -import {BundleValidateModel} from "./models/BundleValidateModel"; -import {StateStorage} from "../vscode-objs/StateStorage"; -import {Telemetry} from "../telemetry"; -import { - BundleTerraformEngineWarningAction, - Events, -} from "../telemetry/constants"; -import {withOnErrorHandler} from "../utils/onErrorDecorator"; - -export const DIRECT_ENGINE_DOCS_URL = - "https://docs.databricks.com/aws/en/dev-tools/bundles/direct"; -export const READ_MIGRATION_GUIDE_LABEL = "Read migration guide"; -export const DONT_SHOW_AGAIN_LABEL = "Don't show again"; - -const HIDE_WARNING_KEY = "databricks.bundle.hideTerraformEngineWarning"; - -const WARNING_MESSAGE = - "This bundle uses the Terraform deployment engine, which is deprecated " + - "and will stop working in a future Databricks CLI version. Migrate to the " + - "direct deployment engine."; - -/** - * The prompting surface {@link BundleEngineManager} needs, behind one seam so - * the logic is unit-testable. The real implementation delegates to `window` / - * `env`. - * - * This is a Manager that renders `window.*` itself (via the seam) rather than - * delegating to a Commands/Component, since the warning is reactive to validate - * state with no command trigger — matching the existing - * {@link BundlePipelinesManager}, which surfaces its warnings the same way. - */ -export interface BundleEnginePrompter { - showWarningMessage: (typeof window)["showWarningMessage"]; - openExternal: (typeof env)["openExternal"]; -} - -/** - * Warns when a bundle's validate output reports the deprecated Terraform - * deployment engine, offering a link to the migration guide. Shows at most once - * per session per workspace; "Don't show again" persists the opt-out for the - * workspace via {@link StateStorage}. - */ -export class BundleEngineManager implements Disposable { - private disposables: Disposable[] = []; - private warned = false; - - constructor( - private readonly bundleValidateModel: BundleValidateModel, - private readonly stateStorage: StateStorage, - private readonly telemetry: Telemetry, - private readonly prompter: BundleEnginePrompter = { - showWarningMessage: window.showWarningMessage, - openExternal: env.openExternal, - } - ) { - this.disposables.push( - this.bundleValidateModel.onDidChange( - withOnErrorHandler(() => this.checkEngine(), { - log: true, - throw: false, - }) - ) - ); - } - - private async checkEngine(): Promise { - if (this.warned || this.stateStorage.get(HIDE_WARNING_KEY)) { - return; - } - const engine = await this.bundleValidateModel.get("engine"); - // Re-check `warned` after the await: a concurrent validate change may - // have shown the warning while we were reading the engine. - if (engine !== "terraform" || this.warned) { - return; - } - this.warned = true; - await this.warn(); - } - - private async warn(): Promise { - const choice = await this.prompter.showWarningMessage( - WARNING_MESSAGE, - READ_MIGRATION_GUIDE_LABEL, - DONT_SHOW_AGAIN_LABEL - ); - - let action: BundleTerraformEngineWarningAction = "dismissed"; - if (choice === READ_MIGRATION_GUIDE_LABEL) { - action = "guide"; - await this.prompter.openExternal(Uri.parse(DIRECT_ENGINE_DOCS_URL)); - } else if (choice === DONT_SHOW_AGAIN_LABEL) { - action = "hidden"; - await this.stateStorage.set(HIDE_WARNING_KEY, true); - } - - this.telemetry.recordEvent(Events.BUNDLE_TERRAFORM_ENGINE_WARNING, { - action, - }); - } - - dispose() { - this.disposables.forEach((d) => d.dispose()); - } -} diff --git a/packages/databricks-vscode/src/bundle/models/BundleValidateModel.test.ts b/packages/databricks-vscode/src/bundle/models/BundleValidateModel.test.ts deleted file mode 100644 index f543566bc..000000000 --- a/packages/databricks-vscode/src/bundle/models/BundleValidateModel.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import assert from "assert"; -import {Uri} from "vscode"; -import {anything, instance, mock, when} from "ts-mockito"; -import {CliWrapper} from "../../cli/CliWrapper"; -import {BundleWatcher} from "../BundleWatcher"; -import {WorkspaceFolderManager} from "../../vscode-objs/WorkspaceFolderManager"; -import {AuthProvider} from "../../configuration/auth/AuthProvider"; -import {BundleValidateModel} from "./BundleValidateModel"; - -describe("BundleValidateModel", () => { - function buildModel(validateStdout: object): BundleValidateModel { - const fakeWatcher = { - onDidChange: () => ({dispose() {}}), - } as unknown as BundleWatcher; - const fakeWorkspaceFolderManager = { - activeProjectUri: Uri.file("/tmp/project"), - } as unknown as WorkspaceFolderManager; - const mockCli = mock(CliWrapper); - when( - mockCli.bundleValidate( - anything(), - anything(), - anything(), - anything(), - anything() - ) - ).thenResolve({stdout: JSON.stringify(validateStdout), stderr: ""}); - - const model = new BundleValidateModel( - fakeWatcher, - instance(mockCli), - fakeWorkspaceFolderManager - ); - model.setTarget("dev"); - model.setAuthProvider({ - toJSON: () => ({}), - } as unknown as AuthProvider); - return model; - } - - it("reads bundle.engine off the validate output", async () => { - const model = buildModel({ - bundle: {name: "proj", engine: "terraform"}, - }); - - assert.strictEqual(await model.get("engine"), "terraform"); - }); - - it("leaves engine undefined when the validate output omits it", async () => { - const model = buildModel({ - bundle: {name: "proj"}, - }); - - assert.strictEqual(await model.get("engine"), undefined); - }); -}); diff --git a/packages/databricks-vscode/src/bundle/models/BundleValidateModel.ts b/packages/databricks-vscode/src/bundle/models/BundleValidateModel.ts index 10a74a625..2c98a9400 100644 --- a/packages/databricks-vscode/src/bundle/models/BundleValidateModel.ts +++ b/packages/databricks-vscode/src/bundle/models/BundleValidateModel.ts @@ -14,9 +14,6 @@ import {WorkspaceFolderManager} from "../../vscode-objs/WorkspaceFolderManager"; export type BundleValidateState = { clusterId?: string; remoteRootPath?: string; - // The bundle deployment engine ("terraform" | "direct"). Not modelled in the - // generated BundleSchema, so read off the validate output directly. - engine?: string; } & BundleTarget; export class BundleValidateModel extends BaseModelWithStateCache { @@ -90,8 +87,6 @@ export class BundleValidateModel extends BaseModelWithStateCache { return { - ...EnvVarGenerators.getEnvVarsForCli(this.extensionContext), + ...EnvVarGenerators.getEnvVarsForCli(), ...EnvVarGenerators.getProxyEnvVars(), }; } @@ -965,10 +962,7 @@ export class CliWrapper { ) { // Add python executable to PATH const executable = await this.pythonExtension?.getPythonExecutable(); - const cliEnvVars = EnvVarGenerators.getEnvVarsForCli( - this.extensionContext, - configfilePath - ); + const cliEnvVars = EnvVarGenerators.getEnvVarsForCli(configfilePath); let shellPath = cliEnvVars.PATH; if (executable) { shellPath = `${path.dirname(executable)}${ @@ -1032,9 +1026,8 @@ export class CliWrapper { "--include-locations", "--target", target, - // Forces the CLI to regenerate local terraform state and pull the remote state. - // Regenerating terraform state is useful when we want to ensure that the provider version - // used in the local state matches the bundled version we supply with the extension. + // Skips the local cache and loads the deployment state from the + // remote workspace, so the summary reflects the latest deploy. "--force-pull", ], workspaceFolder, @@ -1054,7 +1047,6 @@ export class CliWrapper { getBundleInitEnvVars(authProvider: AuthProvider) { return removeUndefinedKeys({ ...EnvVarGenerators.getEnvVarsForCli( - this.extensionContext, workspaceConfigs.databrickscfgLocation ), ...EnvVarGenerators.getProxyEnvVars(), @@ -1072,7 +1064,6 @@ export class CliWrapper { getSshConnectEnvVars(authProvider: AuthProvider) { return removeUndefinedKeys({ ...EnvVarGenerators.getEnvVarsForCli( - this.extensionContext, workspaceConfigs.databrickscfgLocation ), ...EnvVarGenerators.getProxyEnvVars(), @@ -1116,7 +1107,6 @@ export class CliWrapper { getSetupLocalEnvVars(authProvider: AuthProvider, target?: string) { return removeUndefinedKeys({ ...EnvVarGenerators.getEnvVarsForCli( - this.extensionContext, workspaceConfigs.databrickscfgLocation ), ...EnvVarGenerators.getProxyEnvVars(), @@ -1261,10 +1251,7 @@ export class CliWrapper { options: SpawnOptionsWithoutStdio; }> { const env: Record = removeUndefinedKeys({ - ...EnvVarGenerators.getEnvVarsForCli( - this.extensionContext, - configfilePath - ), + ...EnvVarGenerators.getEnvVarsForCli(configfilePath), ...EnvVarGenerators.getProxyEnvVars(), ...authProvider.toEnv(), ...((await this._bundleVariableModel?.getEnvVariables()) ?? {}), @@ -1301,7 +1288,7 @@ export class CliWrapper { } const env = { - ...EnvVarGenerators.getEnvVarsForCli(this.extensionContext), + ...EnvVarGenerators.getEnvVarsForCli(), ...EnvVarGenerators.getProxyEnvVars(), }; const overrideResult = await getCliVersion(overridePath, env); diff --git a/packages/databricks-vscode/src/cli/cliProcess.ts b/packages/databricks-vscode/src/cli/cliProcess.ts index d87fddc50..632b46b52 100644 --- a/packages/databricks-vscode/src/cli/cliProcess.ts +++ b/packages/databricks-vscode/src/cli/cliProcess.ts @@ -68,7 +68,7 @@ const realTerminatePrimitives: TerminatePrimitives = { * tree with `taskkill /T /F` (always forceful — `signal` is moot there); on * POSIX the child is a process-group leader (spawned detached), so signalling * the negated pid tears down the whole group — including any grandchild a - * direct-child signal would orphan (e.g. `terraform`, `uv`) — with a fallback + * direct-child signal would orphan (e.g. `uv`) — with a fallback * to a direct kill if the group is already gone. */ export function terminateProcessTree( diff --git a/packages/databricks-vscode/src/configuration/auth/AuthProvider.test.ts b/packages/databricks-vscode/src/configuration/auth/AuthProvider.test.ts index 21374ed74..f7000867a 100644 --- a/packages/databricks-vscode/src/configuration/auth/AuthProvider.test.ts +++ b/packages/databricks-vscode/src/configuration/auth/AuthProvider.test.ts @@ -18,7 +18,7 @@ describe(__filename, () => { ); } - it("should expose DATABRICKS_CLI_PATH so the SDK/Terraform provider can locate the bundled CLI", () => { + it("should expose DATABRICKS_CLI_PATH so the SDK can locate the bundled CLI", () => { const env = createProvider("dev").toEnv(); assert.equal(env["DATABRICKS_CLI_PATH"], cliPath); diff --git a/packages/databricks-vscode/src/configuration/auth/AuthProvider.ts b/packages/databricks-vscode/src/configuration/auth/AuthProvider.ts index bcb724fc8..a2d9eb477 100644 --- a/packages/databricks-vscode/src/configuration/auth/AuthProvider.ts +++ b/packages/databricks-vscode/src/configuration/auth/AuthProvider.ts @@ -350,7 +350,7 @@ export class DatabricksCliAuthProvider extends AuthProvider { const env: Record = { DATABRICKS_HOST: this.host.toString(), DATABRICKS_AUTH_TYPE: "databricks-cli", - // Point the SDK/Terraform provider at the bundled CLI so they don't + // Point the SDK at the bundled CLI so it doesn't // fall back to searching PATH (and fail with "databricks CLI not // found") in subprocesses that don't inherit our resolved path. DATABRICKS_CLI_PATH: this.cliPath, diff --git a/packages/databricks-vscode/src/extension.ts b/packages/databricks-vscode/src/extension.ts index 98eac2300..b74f10e0d 100644 --- a/packages/databricks-vscode/src/extension.ts +++ b/packages/databricks-vscode/src/extension.ts @@ -36,7 +36,6 @@ import { HostUtils, PackageJsonUtils, ProxyAgent, - TerraformUtils, UrlUtils, UtilsCommands, } from "./utils"; @@ -97,7 +96,6 @@ import { } from "./bundle"; import {showWhatsNewPopup} from "./whatsNewPopup"; import {BundleValidateModel} from "./bundle/models/BundleValidateModel"; -import {BundleEngineManager} from "./bundle/BundleEngineManager"; import {ConfigModel} from "./configuration/models/ConfigModel"; import {OverrideableConfigModel} from "./configuration/models/OverrideableConfigModel"; import {BundlePreValidateModel} from "./bundle/models/BundlePreValidateModel"; @@ -113,7 +111,6 @@ import {DatabricksDebugConfigurationProvider} from "./run/DatabricksDebugConfigu import {BundleVariableModel} from "./bundle/models/BundleVariableModel"; import {BundleVariableTreeDataProvider} from "./ui/bundle-variables/BundleVariableTreeDataProvider"; import {ConfigurationTreeViewManager} from "./ui/configuration-view/ConfigurationTreeViewManager"; -import {getCLIDependenciesEnvVars} from "./utils/envVarGenerators"; import {EnvironmentCommands} from "./language/EnvironmentCommands"; import {PackageManagerTelemetry} from "./language/PackageManagerTelemetry"; import {WorkspaceFolderManager} from "./vscode-objs/WorkspaceFolderManager"; @@ -127,9 +124,6 @@ import { } from "./ui/unity-catalog/UnityCatalogTreeDataProvider"; import {registerDetailPanel} from "./ui/unity-catalog/registerDetailPanel"; -// eslint-disable-next-line @typescript-eslint/no-require-imports -const packageJson = require("../package.json"); - const customWhenContext = new CustomWhenContext(); /** @@ -577,27 +571,6 @@ export async function activate( packageMetadata.version ); - // We always use bundled terraform and databricks provider. - // Updating environment collection means that the variables will be set in all terminals. - // If users use different CLI version in their terminal it will only pick the variables if - // the dependency versions (that we set together with bin and config paths) match the internal versions of the CLI. - const cliDeps = getCLIDependenciesEnvVars(context); - for (const [key, value] of Object.entries(cliDeps)) { - logging.NamedLogger.getOrCreate(Loggers.Extension).debug( - `Setting env var ${key}=${value}` - ); - context.environmentVariableCollection.replace(key, value); - } - TerraformUtils.updateTerraformCliConfig( - context, - packageJson.terraformMetadata - ).catch((e) => { - logging.NamedLogger.getOrCreate(Loggers.Extension).error( - "Failed to update terraform cli config", - e - ); - }); - logging.NamedLogger.getOrCreate(Loggers.Extension).debug("Metadata", { metadata: packageMetadata, }); @@ -780,12 +753,6 @@ export async function activate( stateStorage ); - const bundleEngineManager = new BundleEngineManager( - bundleValidateModel, - stateStorage, - telemetry - ); - const connectionManager = new ConnectionManager( cli, configModel, @@ -819,7 +786,6 @@ export async function activate( bundlePreValidateModel, bundleRemoteStateModel, configModel, - bundleEngineManager, connectionManager, connectionManager.onDidChangeState(async () => { telemetry.setMetadata( diff --git a/packages/databricks-vscode/src/telemetry/constants.ts b/packages/databricks-vscode/src/telemetry/constants.ts index 7e7e8034d..e46d94e77 100644 --- a/packages/databricks-vscode/src/telemetry/constants.ts +++ b/packages/databricks-vscode/src/telemetry/constants.ts @@ -19,7 +19,6 @@ export enum Events { BUNDLE_RUN = "bundleRun", BUNDLE_INIT = "bundleInit", BUNDLE_SUB_PROJECTS = "bundleSubProjects", - BUNDLE_TERRAFORM_ENGINE_WARNING = "bundleTerraformEngineWarning", CONNECTION_STATE_CHANGED = "connectionStateChanged", COMPUTE_SELECTED = "computeSelected", WORKFLOW_RUN = "workflowRun", @@ -46,16 +45,6 @@ export type ManualLoginSource = | "command" | "api"; export type BundleRunResourceType = "pipelines" | "jobs"; -/** - * What the user did with the Terraform-engine deprecation warning: - * - `'guide'` — opened the migration guide. - * - `'hidden'` — chose "Don't show again" (persisted opt-out for the workspace). - * - `'dismissed'` — closed it without either. - */ -export type BundleTerraformEngineWarningAction = - | "guide" - | "hidden" - | "dismissed"; export type BundleRunType = | "run" | "validate" @@ -385,16 +374,6 @@ export class EventTypes { comment: "The resource type", }, }; - [Events.BUNDLE_TERRAFORM_ENGINE_WARNING]: EventType<{ - action: BundleTerraformEngineWarningAction; - }> = { - comment: - "Surfaced the deprecation warning for the Terraform bundle deployment engine. Recorded once per surfacing (at most once per session per workspace, until the user opts out), so the count tracks how many users still have bundles on the Terraform engine.", - action: { - comment: - "What the user did with the warning: 'guide' chose to open the migration guide, 'hidden' chose \"Don't show again\" (persisted opt-out for the workspace), 'dismissed' closed it without either.", - }, - }; [Events.AITOOLS_INSTALL]: EventType< { result: AiToolsInstallResult; diff --git a/packages/databricks-vscode/src/utils/envVarGenerators.ts b/packages/databricks-vscode/src/utils/envVarGenerators.ts index 5cdcb0ec8..3207c7d71 100644 --- a/packages/databricks-vscode/src/utils/envVarGenerators.ts +++ b/packages/databricks-vscode/src/utils/envVarGenerators.ts @@ -1,9 +1,8 @@ import {Loggers} from "../logger"; import {readFile} from "fs/promises"; -import {ExtensionContext, Uri} from "vscode"; +import {Uri} from "vscode"; import {logging, Headers} from "@databricks/sdk-experimental"; import {ConnectionManager} from "../configuration/ConnectionManager"; -import {TerraformMetadata} from "./terraformUtils"; import {workspaceConfigs} from "../vscode-objs/WorkspaceConfigs"; import {mergeNoProxy} from "./network/proxyAgent"; @@ -11,7 +10,6 @@ import {mergeNoProxy} from "./network/proxyAgent"; const packageJson = require("../../package.json"); const extensionVersion = packageJson.version; -const terraformMetadata = packageJson.terraformMetadata as TerraformMetadata; //Get env variables from user's .env file export async function getUserEnvVars(userEnvPath: Uri) { @@ -181,10 +179,7 @@ export function getProxyEnvVars() { }; } -export function getEnvVarsForCli( - extensionContext: ExtensionContext, - configfilePath?: string -) { +export function getEnvVarsForCli(configfilePath?: string) { /* eslint-disable @typescript-eslint/naming-convention */ return { HOME: process.env.HOME, @@ -194,25 +189,6 @@ export function getEnvVarsForCli( DATABRICKS_OUTPUT_FORMAT: "json", DATABRICKS_CLI_UPSTREAM: "databricks-vscode", DATABRICKS_CLI_UPSTREAM_VERSION: extensionVersion, - ...getCLIDependenciesEnvVars(extensionContext), - }; - /* eslint-enable @typescript-eslint/naming-convention */ -} - -export function getCLIDependenciesEnvVars(extensionContext: ExtensionContext) { - if (!terraformMetadata) { - return {}; - } - /* eslint-disable @typescript-eslint/naming-convention */ - return { - DATABRICKS_TF_VERSION: terraformMetadata.version, - DATABRICKS_TF_EXEC_PATH: extensionContext.asAbsolutePath( - terraformMetadata.execRelPath - ), - DATABRICKS_TF_PROVIDER_VERSION: terraformMetadata.providerVersion, - DATABRICKS_TF_CLI_CONFIG_FILE: extensionContext.asAbsolutePath( - terraformMetadata.terraformCliConfigRelPath - ), }; /* eslint-enable @typescript-eslint/naming-convention */ } diff --git a/packages/databricks-vscode/src/utils/index.ts b/packages/databricks-vscode/src/utils/index.ts index 6e6b26ba1..8a084dab1 100644 --- a/packages/databricks-vscode/src/utils/index.ts +++ b/packages/databricks-vscode/src/utils/index.ts @@ -5,6 +5,5 @@ export * as UtilsCommands from "./UtilsCommands"; export * as PackageJsonUtils from "./packageJsonUtils"; export * as EnvVarGenerators from "./envVarGenerators"; export * as DateUtils from "./DateUtils"; -export * as TerraformUtils from "./terraformUtils"; export * as HostUtils from "./hostUtils"; export {ProxyAgent} from "./network"; diff --git a/packages/databricks-vscode/src/utils/terraformUtils.ts b/packages/databricks-vscode/src/utils/terraformUtils.ts deleted file mode 100644 index b0276f523..000000000 --- a/packages/databricks-vscode/src/utils/terraformUtils.ts +++ /dev/null @@ -1,44 +0,0 @@ -import {writeFile} from "fs/promises"; -import {ExtensionContext} from "vscode"; - -export type TerraformMetadataFromCli = { - version: string; - providerVersion: string; - providerSource: string; - providerHost: string; -}; - -export type TerraformMetadataExtra = { - execRelPath: string; - providersMirrorRelPath: string; - terraformCliConfigRelPath: string; -}; - -export type TerraformMetadata = TerraformMetadataFromCli & - TerraformMetadataExtra; - -function getTerraformCliConfig(mirrorPath: string) { - return `disable_checkpoint = true -provider_installation { - filesystem_mirror { - path = "${mirrorPath.replace(/\\/g, "\\\\")}" - } -}`; -} - -export async function updateTerraformCliConfig( - context: ExtensionContext, - terraformMetadata?: TerraformMetadata -) { - if (!terraformMetadata) { - return; - } - const configPath = context.asAbsolutePath( - terraformMetadata.terraformCliConfigRelPath - ); - const providersMirrorPath = context.asAbsolutePath( - terraformMetadata.providersMirrorRelPath - ); - const config = getTerraformCliConfig(providersMirrorPath); - await writeFile(configPath, config, {encoding: "utf-8"}); -} diff --git a/packages/databricks-vscode/src/vscode-objs/StateStorage.ts b/packages/databricks-vscode/src/vscode-objs/StateStorage.ts index 169ade48e..bb0f8809b 100644 --- a/packages/databricks-vscode/src/vscode-objs/StateStorage.ts +++ b/packages/databricks-vscode/src/vscode-objs/StateStorage.ts @@ -118,15 +118,6 @@ const StorageConfigurations = { defaultValue: false, }), - // Set when the user picks "Don't show again" on the deprecation warning for - // the Terraform bundle deployment engine, so it stays silenced for this - // workspace. A plain dismissal leaves it false so the warning can resurface - // on a later session (see BundleEngineManager). - "databricks.bundle.hideTerraformEngineWarning": withType()({ - location: "workspace", - defaultValue: false, - }), - // The ~/.databrickscfg profile of the Unity Gateway sign-in, with its host // and workspace id. Global because that sign-in is per user, separate from // each project's bundle sign-in. Both are checked on restore, since the diff --git a/yarn.lock b/yarn.lock index 7966b9d44..5671e18c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4089,7 +4089,6 @@ __metadata: esbuild: ^0.28.1 eslint: ^10.6.0 eslint-plugin-local-rules: ^3.0.2 - extract-zip: ^2.0.1 fs-extra: ^11.2.0 glob: ^13.0.6 highlight.js: ^11.10.0