mirror of https://github.com/databricks/cli.git
Allow synchronization to a directory inside a repo (#213)
Before this commit this would error saying that the repo doesn't exist yet. With this commit it creates the directory, but only after checking that the repo exists.
This commit is contained in:
parent
7bf212e54a
commit
584c8d1b0b
|
@ -461,3 +461,71 @@ func TestAccIncrementalSyncPythonNotebookDelete(t *testing.T) {
|
||||||
f.Remove(t)
|
f.Remove(t)
|
||||||
assertSync.remoteDirContent(ctx, "", append(repoFiles, ".gitignore"))
|
assertSync.remoteDirContent(ctx, "", append(repoFiles, ".gitignore"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAccSyncEnsureRemotePathIsUsableIfRepoDoesntExist(t *testing.T) {
|
||||||
|
t.Log(GetEnvOrSkipTest(t, "CLOUD_ENV"))
|
||||||
|
|
||||||
|
wsc := databricks.Must(databricks.NewWorkspaceClient())
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
me, err := wsc.CurrentUser.Me(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Hypothetical repo path doesn't exist.
|
||||||
|
nonExistingRepoPath := fmt.Sprintf("/Repos/%s/%s", me.UserName, RandomName("doesnt-exist-"))
|
||||||
|
err = sync.EnsureRemotePathIsUsable(ctx, wsc, nonExistingRepoPath)
|
||||||
|
assert.ErrorContains(t, err, " does not exist; please create it first")
|
||||||
|
|
||||||
|
// Paths nested under a hypothetical repo path should yield the same error.
|
||||||
|
nestedPath := path.Join(nonExistingRepoPath, "nested/directory")
|
||||||
|
err = sync.EnsureRemotePathIsUsable(ctx, wsc, nestedPath)
|
||||||
|
assert.ErrorContains(t, err, " does not exist; please create it first")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccSyncEnsureRemotePathIsUsableIfRepoExists(t *testing.T) {
|
||||||
|
t.Log(GetEnvOrSkipTest(t, "CLOUD_ENV"))
|
||||||
|
|
||||||
|
wsc := databricks.Must(databricks.NewWorkspaceClient())
|
||||||
|
ctx := context.Background()
|
||||||
|
_, remoteRepoPath := setupRepo(t, wsc, ctx)
|
||||||
|
|
||||||
|
// Repo itself is usable.
|
||||||
|
err := sync.EnsureRemotePathIsUsable(ctx, wsc, remoteRepoPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Path nested under repo path is usable.
|
||||||
|
nestedPath := path.Join(remoteRepoPath, "nested/directory")
|
||||||
|
err = sync.EnsureRemotePathIsUsable(ctx, wsc, nestedPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify that the directory has been created.
|
||||||
|
info, err := wsc.Workspace.GetStatusByPath(ctx, nestedPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, workspace.ObjectTypeDirectory, info.ObjectType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccSyncEnsureRemotePathIsUsableInWorkspace(t *testing.T) {
|
||||||
|
t.Log(GetEnvOrSkipTest(t, "CLOUD_ENV"))
|
||||||
|
|
||||||
|
wsc := databricks.Must(databricks.NewWorkspaceClient())
|
||||||
|
ctx := context.Background()
|
||||||
|
me, err := wsc.CurrentUser.Me(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
remotePath := fmt.Sprintf("/Users/%s/%s", me.UserName, RandomName("ensure-path-exists-test-"))
|
||||||
|
err = sync.EnsureRemotePathIsUsable(ctx, wsc, remotePath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Clean up directory after test.
|
||||||
|
defer func() {
|
||||||
|
err := wsc.Workspace.Delete(ctx, workspace.Delete{
|
||||||
|
Path: remotePath,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Verify that the directory has been created.
|
||||||
|
info, err := wsc.Workspace.GetStatusByPath(ctx, remotePath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, workspace.ObjectTypeDirectory, info.ObjectType)
|
||||||
|
}
|
||||||
|
|
|
@ -49,15 +49,24 @@ func checkPathNestedUnderBasePaths(me *scim.User, p string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensureRemotePathIsUsable checks if the specified path is nested under
|
func repoPathForPath(me *scim.User, remotePath string) string {
|
||||||
|
base := path.Clean(fmt.Sprintf("/Repos/%s", me.UserName))
|
||||||
|
remotePath = path.Clean(remotePath)
|
||||||
|
for strings.HasPrefix(path.Dir(remotePath), base) && path.Dir(remotePath) != base {
|
||||||
|
remotePath = path.Dir(remotePath)
|
||||||
|
}
|
||||||
|
return remotePath
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnsureRemotePathIsUsable checks if the specified path is nested under
|
||||||
// expected base paths and if it is a directory or repository.
|
// expected base paths and if it is a directory or repository.
|
||||||
func ensureRemotePathIsUsable(ctx context.Context, wsc *databricks.WorkspaceClient, path string) error {
|
func EnsureRemotePathIsUsable(ctx context.Context, wsc *databricks.WorkspaceClient, remotePath string) error {
|
||||||
me, err := wsc.CurrentUser.Me(ctx)
|
me, err := wsc.CurrentUser.Me(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = checkPathNestedUnderBasePaths(me, path)
|
err = checkPathNestedUnderBasePaths(me, remotePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -65,27 +74,29 @@ func ensureRemotePathIsUsable(ctx context.Context, wsc *databricks.WorkspaceClie
|
||||||
// Ensure that the remote path exists.
|
// Ensure that the remote path exists.
|
||||||
// If it is a repo, it has to exist.
|
// If it is a repo, it has to exist.
|
||||||
// If it is a workspace path, it may not exist.
|
// If it is a workspace path, it may not exist.
|
||||||
info, err := wsc.Workspace.GetStatusByPath(ctx, path)
|
info, err := wsc.Workspace.GetStatusByPath(ctx, remotePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// We only deal with 404s below.
|
// We only deal with 404s below.
|
||||||
if !apierr.IsMissing(err) {
|
if !apierr.IsMissing(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
// If the path is nested under a repo, the repo has to exist.
|
||||||
case strings.HasPrefix(path, "/Repos/"):
|
if strings.HasPrefix(remotePath, "/Repos/") {
|
||||||
return fmt.Errorf("%s does not exist; please create it first", path)
|
repoPath := repoPathForPath(me, remotePath)
|
||||||
case strings.HasPrefix(path, "/Users/"):
|
_, err = wsc.Workspace.GetStatusByPath(ctx, repoPath)
|
||||||
// The workspace path doesn't exist. Create it and try again.
|
if err != nil && apierr.IsMissing(err) {
|
||||||
err = wsc.Workspace.MkdirsByPath(ctx, path)
|
return fmt.Errorf("%s does not exist; please create it first", repoPath)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to create directory at %s: %w", path, err)
|
|
||||||
}
|
}
|
||||||
info, err = wsc.Workspace.GetStatusByPath(ctx, path)
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
// The workspace path doesn't exist. Create it and try again.
|
||||||
}
|
err = wsc.Workspace.MkdirsByPath(ctx, remotePath)
|
||||||
default:
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to create directory at %s: %w", remotePath, err)
|
||||||
|
}
|
||||||
|
info, err = wsc.Workspace.GetStatusByPath(ctx, remotePath)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,5 +116,5 @@ func ensureRemotePathIsUsable(ctx context.Context, wsc *databricks.WorkspaceClie
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("%s points to a %s", path, strings.ToLower(info.ObjectType.String()))
|
return fmt.Errorf("%s points to a %s", remotePath, strings.ToLower(info.ObjectType.String()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,3 +37,19 @@ func TestPathNestedUnderBasePaths(t *testing.T) {
|
||||||
assert.NoError(t, checkPathNestedUnderBasePaths(&me, "/Users/jane@doe.com/./foo"))
|
assert.NoError(t, checkPathNestedUnderBasePaths(&me, "/Users/jane@doe.com/./foo"))
|
||||||
assert.NoError(t, checkPathNestedUnderBasePaths(&me, "/Users/jane@doe.com/foo/bar/qux"))
|
assert.NoError(t, checkPathNestedUnderBasePaths(&me, "/Users/jane@doe.com/foo/bar/qux"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPathToRepoPath(t *testing.T) {
|
||||||
|
me := scim.User{
|
||||||
|
UserName: "jane@doe.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "/Repos/jane@doe.com/foo", repoPathForPath(&me, "/Repos/jane@doe.com/foo/bar/qux"))
|
||||||
|
assert.Equal(t, "/Repos/jane@doe.com/foo", repoPathForPath(&me, "/Repos/jane@doe.com/foo/bar"))
|
||||||
|
assert.Equal(t, "/Repos/jane@doe.com/foo", repoPathForPath(&me, "/Repos/jane@doe.com/foo"))
|
||||||
|
|
||||||
|
// We expect this function to be called with a path nested under the user's repo path.
|
||||||
|
// If this is not the case it should return the input verbatim (albeit cleaned).
|
||||||
|
assert.Equal(t, "/Repos/jane@doe.com", repoPathForPath(&me, "/Repos/jane@doe.com"))
|
||||||
|
assert.Equal(t, "/Repos/hello@world.com/foo/bar/qux", repoPathForPath(&me, "/Repos/hello@world.com/foo/bar/qux"))
|
||||||
|
assert.Equal(t, "/Repos/hello@world.com/foo/bar/qux", repoPathForPath(&me, "/Repos/hello@world.com/foo/bar/qux/."))
|
||||||
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ func New(ctx context.Context, opts SyncOptions) (*Sync, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that the remote path we're about to synchronize to is valid and allowed.
|
// Verify that the remote path we're about to synchronize to is valid and allowed.
|
||||||
err = ensureRemotePathIsUsable(ctx, opts.WorkspaceClient, opts.RemotePath)
|
err = EnsureRemotePathIsUsable(ctx, opts.WorkspaceClient, opts.RemotePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue