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) + }) + } +}