Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 5 additions & 13 deletions experimental/ssh/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a-string-your-change-adds>
```

**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+<commit>`) 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/<you>/.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

Expand Down
17 changes: 11 additions & 6 deletions experimental/ssh/internal/client/releases.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
46 changes: 46 additions & 0 deletions experimental/ssh/internal/client/releases_test.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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)
Comment on lines +103 to +140

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Gap (Nit)] Test asserts getRelease was called, not that overwrite landed

TestUploadReleasesWithExistingBinary does fail without the skip fix: both cases seed remoteBinaryPath, so Stat succeeds, and the pre-fix loop would continue for the dev version too. That part is good, and the release case is a useful guard against “always upload.” It does not, however, assert overwrite. After uploadReleases the test only compares the uploaded slice; it never Reads anything. Setup writes "old" to {release}/databricks (the Stat path). Production writes "new" to {release}/databricks.zip with filer.OverwriteIfExists. Those are different paths. Under LocalClient, the zip path does not exist yet, so Write would succeed even without OverwriteIfExists. The case named “dev version overwrites the binary” therefore does not show that new bytes replaced old ones. LocalClient also does not unzip, so it cannot honestly assert the databricks file content anyway.

Suggestion: After a successful upload, for the dev case Read {release}/databricks.zip and assert the body is "new"; for the release case assert that zip was not created and {release}/databricks is still "old". Stronger still: also pre-seed the zip path with "old" so dropping OverwriteIfExists fails locally. Do not assert {release}/databricks became "new" in this unit test — that only happens via workspace import-file unzip.

})
}
}
Loading