Skip to content

Commit bbd42c0

Browse files
committed
Postpone directory creation until upload start
This ensure that "bundle validate" does not create remote directory. Extend empty_bundle_test.go to check if remote path is created prematurely
1 parent 0ad790e commit bbd42c0

File tree

4 files changed

+70
-26
lines changed

4 files changed

+70
-26
lines changed

internal/bundle/empty_bundle_test.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,38 @@ import (
88

99
"github.com/databricks/cli/internal/acc"
1010
"github.com/google/uuid"
11+
"github.com/stretchr/testify/assert"
1112
"github.com/stretchr/testify/require"
1213
)
1314

1415
func TestAccEmptyBundleDeploy(t *testing.T) {
15-
ctx, _ := acc.WorkspaceTest(t)
16+
ctx, w := acc.WorkspaceTest(t)
17+
18+
uniqueId := uuid.New().String()
19+
me, err := w.W.CurrentUser.Me(ctx)
20+
require.NoError(t, err)
21+
remoteRoot := fmt.Sprintf("/Workspace/Users/%s/.bundle/%s", me.UserName, uniqueId)
1622

1723
// create empty bundle
1824
tmpDir := t.TempDir()
1925
f, err := os.Create(filepath.Join(tmpDir, "databricks.yml"))
2026
require.NoError(t, err)
2127

2228
bundleRoot := fmt.Sprintf(`bundle:
23-
name: %s`, uuid.New().String())
29+
name: %s`, uniqueId)
2430
_, err = f.WriteString(bundleRoot)
2531
require.NoError(t, err)
2632
f.Close()
2733

34+
_, err = w.W.Workspace.GetStatusByPath(ctx, remoteRoot)
35+
assert.ErrorContains(t, err, "doesn't exist")
36+
37+
mustValidateBundle(t, ctx, tmpDir)
38+
39+
// regression: "bundle validate" must not create a directory
40+
_, err = w.W.Workspace.GetStatusByPath(ctx, remoteRoot)
41+
require.ErrorContains(t, err, "doesn't exist")
42+
2843
// deploy empty bundle
2944
err = deployBundle(t, ctx, tmpDir)
3045
require.NoError(t, err)
@@ -33,4 +48,9 @@ func TestAccEmptyBundleDeploy(t *testing.T) {
3348
err = destroyBundle(t, ctx, tmpDir)
3449
require.NoError(t, err)
3550
})
51+
52+
// verify that remoteRoot was actually relevant location to test
53+
_, err = w.W.Workspace.GetStatusByPath(ctx, remoteRoot)
54+
assert.NoError(t, err)
55+
3656
}

internal/sync_test.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -509,13 +509,15 @@ func TestAccSyncEnsureRemotePathIsUsableIfRepoDoesntExist(t *testing.T) {
509509

510510
// Hypothetical repo path doesn't exist.
511511
nonExistingRepoPath := fmt.Sprintf("/Repos/%s/%s", me.UserName, RandomName("doesnt-exist-"))
512-
err = sync.EnsureRemotePathIsUsable(ctx, wsc, nonExistingRepoPath, nil)
512+
remoteExists, err := sync.EnsureRemotePathIsUsable(ctx, wsc, nonExistingRepoPath, nil)
513513
assert.ErrorContains(t, err, " does not exist; please create it first")
514+
assert.False(t, remoteExists)
514515

515516
// Paths nested under a hypothetical repo path should yield the same error.
516517
nestedPath := path.Join(nonExistingRepoPath, "nested/directory")
517-
err = sync.EnsureRemotePathIsUsable(ctx, wsc, nestedPath, nil)
518+
remoteExists, err = sync.EnsureRemotePathIsUsable(ctx, wsc, nestedPath, nil)
518519
assert.ErrorContains(t, err, " does not exist; please create it first")
520+
assert.False(t, remoteExists)
519521
}
520522

521523
func TestAccSyncEnsureRemotePathIsUsableIfRepoExists(t *testing.T) {
@@ -526,13 +528,15 @@ func TestAccSyncEnsureRemotePathIsUsableIfRepoExists(t *testing.T) {
526528
_, remoteRepoPath := setupRepo(t, wsc, ctx)
527529

528530
// Repo itself is usable.
529-
err := sync.EnsureRemotePathIsUsable(ctx, wsc, remoteRepoPath, nil)
531+
remoteExists, err := sync.EnsureRemotePathIsUsable(ctx, wsc, remoteRepoPath, nil)
530532
assert.NoError(t, err)
533+
assert.True(t, remoteExists)
531534

532535
// Path nested under repo path is usable.
533536
nestedPath := path.Join(remoteRepoPath, "nested/directory")
534-
err = sync.EnsureRemotePathIsUsable(ctx, wsc, nestedPath, nil)
537+
remoteExists, err = sync.EnsureRemotePathIsUsable(ctx, wsc, nestedPath, nil)
535538
assert.NoError(t, err)
539+
assert.False(t, remoteExists)
536540

537541
// Verify that the directory has been created.
538542
info, err := wsc.Workspace.GetStatusByPath(ctx, nestedPath)
@@ -549,8 +553,9 @@ func TestAccSyncEnsureRemotePathIsUsableInWorkspace(t *testing.T) {
549553
require.NoError(t, err)
550554

551555
remotePath := fmt.Sprintf("/Users/%s/%s", me.UserName, RandomName("ensure-path-exists-test-"))
552-
err = sync.EnsureRemotePathIsUsable(ctx, wsc, remotePath, me)
556+
remoteExists, err := sync.EnsureRemotePathIsUsable(ctx, wsc, remotePath, me)
553557
assert.NoError(t, err)
558+
assert.False(t, remoteExists)
554559

555560
// Clean up directory after test.
556561
defer func() {
@@ -560,8 +565,8 @@ func TestAccSyncEnsureRemotePathIsUsableInWorkspace(t *testing.T) {
560565
assert.NoError(t, err)
561566
}()
562567

563-
// Verify that the directory has been created.
568+
// Verify that the directory has not been created.
564569
info, err := wsc.Workspace.GetStatusByPath(ctx, remotePath)
565-
require.NoError(t, err)
570+
require.ErrorContains(t, err, "not exist")
566571
require.Equal(t, workspace.ObjectTypeDirectory, info.ObjectType)
567572
}

libs/sync/path.go

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,16 @@ func repoPathForPath(me *iam.User, remotePath string) string {
2424

2525
// EnsureRemotePathIsUsable checks if the specified path is nested under
2626
// expected base paths and if it is a directory or repository.
27-
func EnsureRemotePathIsUsable(ctx context.Context, wsc *databricks.WorkspaceClient, remotePath string, me *iam.User) error {
27+
// Returns (doesRemoteExist, error)
28+
func EnsureRemotePathIsUsable(ctx context.Context, wsc *databricks.WorkspaceClient, remotePath string, me *iam.User) (bool, error) {
2829
var err error
2930

3031
// TODO: we should cache CurrentUser.Me at the SDK level
3132
// for now we let clients pass in any existing user they might already have
3233
if me == nil {
3334
me, err = wsc.CurrentUser.Me(ctx)
3435
if err != nil {
35-
return err
36+
return false, err
3637
}
3738
}
3839

@@ -43,27 +44,20 @@ func EnsureRemotePathIsUsable(ctx context.Context, wsc *databricks.WorkspaceClie
4344
if err != nil {
4445
// We only deal with 404s below.
4546
if !apierr.IsMissing(err) {
46-
return err
47+
return false, err
4748
}
4849

4950
// If the path is nested under a repo, the repo has to exist.
5051
if strings.HasPrefix(remotePath, "/Repos/") {
5152
repoPath := repoPathForPath(me, remotePath)
5253
_, err = wsc.Workspace.GetStatusByPath(ctx, repoPath)
5354
if err != nil && apierr.IsMissing(err) {
54-
return fmt.Errorf("%s does not exist; please create it first", repoPath)
55+
return false, fmt.Errorf("%s does not exist; please create it first", repoPath)
5556
}
5657
}
5758

58-
// The workspace path doesn't exist. Create it and try again.
59-
err = wsc.Workspace.MkdirsByPath(ctx, remotePath)
60-
if err != nil {
61-
return fmt.Errorf("unable to create directory at %s: %w", remotePath, err)
62-
}
63-
info, err = wsc.Workspace.GetStatusByPath(ctx, remotePath)
64-
if err != nil {
65-
return err
66-
}
59+
return false, nil
60+
6761
}
6862

6963
log.Debugf(
@@ -77,10 +71,23 @@ func EnsureRemotePathIsUsable(ctx context.Context, wsc *databricks.WorkspaceClie
7771
// We expect the object at path to be a directory or a repo.
7872
switch info.ObjectType {
7973
case workspace.ObjectTypeDirectory:
80-
return nil
74+
return true, nil
8175
case workspace.ObjectTypeRepo:
82-
return nil
76+
return true, nil
8377
}
8478

85-
return fmt.Errorf("%s points to a %s", remotePath, strings.ToLower(info.ObjectType.String()))
79+
return true, fmt.Errorf("%s points to a %s", remotePath, strings.ToLower(info.ObjectType.String()))
80+
}
81+
82+
func createRemotePath(ctx context.Context, wsc *databricks.WorkspaceClient, remotePath string) error {
83+
// The workspace path doesn't exist. Create it and try again.
84+
err := wsc.Workspace.MkdirsByPath(ctx, remotePath)
85+
if err != nil {
86+
return fmt.Errorf("unable to create directory at %s: %w", remotePath, err)
87+
}
88+
_, err = wsc.Workspace.GetStatusByPath(ctx, remotePath)
89+
if err != nil {
90+
return err
91+
}
92+
return nil
8693
}

libs/sync/sync.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ type Sync struct {
5959
// WaitGroup is automatically created when an output handler is provided in the SyncOptions.
6060
// Close call is required to ensure the output handler goroutine handles all events in time.
6161
outputWaitGroup *stdsync.WaitGroup
62+
63+
// If this flag is not set, we'll create remote directory before starting upload
64+
remoteExists bool
6265
}
6366

6467
// New initializes and returns a new [Sync] instance.
@@ -84,7 +87,7 @@ func New(ctx context.Context, opts SyncOptions) (*Sync, error) {
8487
}
8588

8689
// Verify that the remote path we're about to synchronize to is valid and allowed.
87-
err = EnsureRemotePathIsUsable(ctx, opts.WorkspaceClient, opts.RemotePath, opts.CurrentUser)
90+
remoteExists, err := EnsureRemotePathIsUsable(ctx, opts.WorkspaceClient, opts.RemotePath, opts.CurrentUser)
8891
if err != nil {
8992
return nil, err
9093
}
@@ -141,6 +144,7 @@ func New(ctx context.Context, opts SyncOptions) (*Sync, error) {
141144
notifier: notifier,
142145
outputWaitGroup: outputWaitGroup,
143146
seq: 0,
147+
remoteExists: remoteExists,
144148
}, nil
145149
}
146150

@@ -180,6 +184,14 @@ func (s *Sync) notifyComplete(ctx context.Context, d diff) {
180184
// Returns the list of files tracked (and synchronized) by the syncer during the run,
181185
// and an error if any occurred.
182186
func (s *Sync) RunOnce(ctx context.Context) ([]fileset.File, error) {
187+
if !s.remoteExists {
188+
err := createRemotePath(ctx, s.WorkspaceClient, s.RemotePath)
189+
if err != nil {
190+
return nil, err
191+
}
192+
s.remoteExists = true
193+
}
194+
183195
files, err := s.GetFileList(ctx)
184196
if err != nil {
185197
return files, err

0 commit comments

Comments
 (0)