From 51ee98a286efb8e07114e0ef42e68415dcaf088e Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Wed, 23 Sep 2026 14:34:26 +0000 Subject: [PATCH] ssh: always re-upload dev builds of the tunnel binary uploadReleases skipped the upload whenever the binary already existed at the versioned workspace path. Dev and snapshot builds keep the same version string when rebuilt without a new commit, so the rebuilt binary was silently not uploaded and the cluster kept running the old one. Dev versions now always overwrite; release versions still skip. Co-authored-by: Isaac --- experimental/ssh/CLAUDE.md | 18 ++------ experimental/ssh/internal/client/releases.go | 17 ++++--- .../ssh/internal/client/releases_test.go | 46 +++++++++++++++++++ 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/experimental/ssh/CLAUDE.md b/experimental/ssh/CLAUDE.md index 222439b1f69..d8b4675f604 100644 --- a/experimental/ssh/CLAUDE.md +++ b/experimental/ssh/CLAUDE.md @@ -34,19 +34,11 @@ Confirm the archive carries your change before uploading, e.g.: unzip -p ./dist/databricks_cli_linux_amd64.zip databricks | strings | grep ``` -**RULE: Dev/snapshot uploads are skipped when the versioned workspace directory already exists.** -`uploadReleases` (`internal/client/releases.go`) skips the upload when the binary -is already present at the versioned workspace path. Dev builds keep the same -version string (`0.0.0-dev+`) across rebuilds, so a rebuilt server binary -is silently **not** re-uploaded and the cluster runs the stale one. Before -re-verifying a rebuilt binary, delete the versioned directory and connect with a -fresh `--name`: - -```sh -VER=$(./cli version | grep -o '0.0.0-dev+[a-f0-9]*') -databricks workspace delete --recursive \ - "/Workspace/Users//.databricks/ssh-tunnel/$VER" -``` +**RULE: Start a fresh server after rebuilding.** `uploadReleases` +(`internal/client/releases.go`) always re-uploads dev and snapshot builds, but +`connect` reuses a server that is already running for the same version, and that +server still runs the previous binary. Connect with a fresh `--name` on serverless, +or a fresh cluster on dedicated. ## Server vs. session diff --git a/experimental/ssh/internal/client/releases.go b/experimental/ssh/internal/client/releases.go index 141b4def5fd..874a66bba2b 100644 --- a/experimental/ssh/internal/client/releases.go +++ b/experimental/ssh/internal/client/releases.go @@ -16,6 +16,7 @@ import ( "time" "github.com/databricks/cli/experimental/ssh/internal/workspace" + "github.com/databricks/cli/internal/build" "github.com/databricks/cli/libs/filer" "github.com/databricks/cli/libs/log" "github.com/databricks/databricks-sdk-go" @@ -108,12 +109,16 @@ func uploadReleases(ctx context.Context, workspaceFiler filer.Filer, getRelease remoteBinaryPath := filepath.ToSlash(filepath.Join(remoteSubFolder, "databricks")) remoteArchivePath := filepath.ToSlash(filepath.Join(remoteSubFolder, "databricks.zip")) - _, err := workspaceFiler.Stat(ctx, remoteBinaryPath) - if err == nil { - log.Infof(ctx, "File %s already exists in the workspace, skipping upload", remoteBinaryPath) - continue - } else if !errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("failed to check if file %s exists in workspace: %w", remoteBinaryPath, err) + if build.IsDevelopmentVersion(version) { + log.Infof(ctx, "Development version %s, overwriting %s in the workspace", version, remoteBinaryPath) + } else { + _, err := workspaceFiler.Stat(ctx, remoteBinaryPath) + if err == nil { + log.Infof(ctx, "File %s already exists in the workspace, skipping upload", remoteBinaryPath) + continue + } else if !errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("failed to check if file %s exists in workspace: %w", remoteBinaryPath, err) + } } releaseReader, err := getRelease(ctx, arch, version, releasesDir) diff --git a/experimental/ssh/internal/client/releases_test.go b/experimental/ssh/internal/client/releases_test.go index 3955fafa996..81984741834 100644 --- a/experimental/ssh/internal/client/releases_test.go +++ b/experimental/ssh/internal/client/releases_test.go @@ -1,12 +1,16 @@ package client import ( + "context" "errors" "fmt" + "io" "net/http" + "strings" "syscall" "testing" + "github.com/databricks/cli/libs/filer" "github.com/databricks/databricks-sdk-go/apierr" "github.com/databricks/databricks-sdk-go/config" "github.com/stretchr/testify/assert" @@ -95,3 +99,45 @@ func TestNewHTTP11WorkspaceClient(t *testing.T) { // The source config is not mutated: it keeps its own (nil) transport. assert.Nil(t, src.HTTPTransport) } + +func TestUploadReleasesWithExistingBinary(t *testing.T) { + tests := []struct { + name string + version string + wantUploaded []string + }{ + { + name: "release version skips the upload", + version: "1.12.0", + wantUploaded: nil, + }, + { + name: "dev version overwrites the binary", + version: "1.12.1-dev+abcdef123456", + wantUploaded: []string{"amd64", "arm64"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := t.Context() + workspaceFiler, err := filer.NewLocalClient(t.TempDir()) + require.NoError(t, err) + for _, arch := range []string{"amd64", "arm64"} { + remoteBinaryPath := strings.TrimSuffix(getReleaseName(arch, tt.version), ".zip") + "/databricks" + err := workspaceFiler.Write(ctx, remoteBinaryPath, strings.NewReader("old"), filer.CreateParentDirectories) + require.NoError(t, err) + } + + var uploaded []string + getRelease := func(ctx context.Context, architecture, version, releasesDir string) (io.ReadCloser, error) { + uploaded = append(uploaded, architecture) + return io.NopCloser(strings.NewReader("new")), nil + } + + err = uploadReleases(ctx, workspaceFiler, getRelease, tt.version, "") + require.NoError(t, err) + assert.Equal(t, tt.wantUploaded, uploaded) + }) + } +}