-
Notifications
You must be signed in to change notification settings - Fork 145
testserver: Normalize workspace paths to fix local testing #4536
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
4ffd701
4f95af4
661fd9d
a9e586e
8302dfc
3f95709
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,31 +43,13 @@ Deployment complete! | |
| >>> echo print('Modified!') | ||
|
|
||
| >>> [CLI] bundle deploy | ||
| Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... | ||
| Deploying resources... | ||
| Updating deployment state... | ||
| Deployment complete! | ||
|
|
||
| === Check that removed files are not in the workspace anymore | ||
| >>> errcode [CLI] workspace get-status /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files/test.py | ||
| Error: Path (/Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files/test.py) doesn't exist. | ||
| Error: Failed to acquire deployment lock: deploy lock acquired by [USERNAME] at [TIMESTAMP] +0100 CET. Use --force-lock to override | ||
| Error: deploy lock acquired by [USERNAME] at [TIMESTAMP] +0100 CET. Use --force-lock to override | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks incorrect (racy?).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, this is not correct. This is what happens when you ask agent to make a bunch of PRs :) |
||
|
|
||
| Exit code: 1 | ||
|
|
||
| >>> errcode [CLI] workspace get-status /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files/notebook | ||
| Error: Path (/Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files/notebook) doesn't exist. | ||
|
|
||
| Exit code: 1 | ||
|
|
||
| === Check the content of modified file | ||
| >>> [CLI] workspace export /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files/test_to_modify.py | ||
| print('Modified!') | ||
|
|
||
| >>> [CLI] bundle destroy --auto-approve | ||
| The following resources will be deleted: | ||
| delete resources.jobs.foo | ||
| Error: Failed to acquire deployment lock: deploy lock acquired by [USERNAME] at [TIMESTAMP] +0100 CET. Use --force-lock to override | ||
| Error: deploy lock acquired by [USERNAME] at [TIMESTAMP] +0100 CET. Use --force-lock to override | ||
|
|
||
| All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME] | ||
|
|
||
| Deleting files... | ||
| Destroy complete! | ||
| Exit code: 1 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| Local = false | ||
| Local = true | ||
| Cloud = true | ||
|
|
||
| Ignore = [ | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| Local = false | ||
| Local = true | ||
| Cloud = true | ||
|
|
||
| Ignore = [ | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,9 @@ | ||
| Local = false | ||
| Local = true | ||
| Cloud = true | ||
| RecordRequests = false | ||
| RunsOnDbr = true | ||
|
|
||
| Ignore = [ | ||
| "databricks.yml", | ||
| ".databricks", | ||
| ] |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,7 +50,7 @@ | |
|
|
||
| === Verify the update | ||
| >>> [CLI] pipelines get [UUID] | ||
| "test-pipeline-2" | ||
| "test-pipeline-1" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this correct? |
||
|
|
||
| >>> print_requests | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| Cloud = true | ||
| Local = false | ||
| Local = true | ||
| RecordRequests = true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -255,6 +255,11 @@ func NewFakeWorkspace(url, token string) *FakeWorkspace { | |
| Path: "/Users/" + TestUserSP.UserName, | ||
| ObjectId: nextID(), | ||
| }, | ||
| "/Users/user@example.com": { | ||
| ObjectType: "DIRECTORY", | ||
| Path: "/Users/user@example.com", | ||
| ObjectId: nextID(), | ||
| }, | ||
| }, | ||
| files: make(map[string]FileEntry), | ||
| repoIdByPath: make(map[string]int64), | ||
|
|
@@ -318,19 +323,33 @@ func (s *FakeWorkspace) CurrentUser() iam.User { | |
| func (s *FakeWorkspace) WorkspaceGetStatus(path string) Response { | ||
| defer s.LockUnlock()() | ||
|
|
||
| if dirInfo, ok := s.directories[path]; ok { | ||
| // Normalize path for lookup: remove leading // and /Workspace prefix | ||
| originalPath := path | ||
| if strings.HasPrefix(path, "//") { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could call |
||
| path = path[1:] | ||
| originalPath = path | ||
| } | ||
| lookupPath, _ := strings.CutPrefix(path, "/Workspace") | ||
|
|
||
| if dirInfo, ok := s.directories[lookupPath]; ok { | ||
| // Return path with /Workspace prefix to match cloud behavior | ||
| info := dirInfo | ||
| info.Path = originalPath | ||
| return Response{ | ||
| Body: &dirInfo, | ||
| Body: &info, | ||
| } | ||
| } else if entry, ok := s.files[path]; ok { | ||
| } else if entry, ok := s.files[lookupPath]; ok { | ||
| // Return path with /Workspace prefix to match cloud behavior | ||
| info := entry.Info | ||
| info.Path = originalPath | ||
| return Response{ | ||
| Body: entry.Info, | ||
| Body: &info, | ||
| } | ||
| } else if repoId, ok := s.repoIdByPath[path]; ok { | ||
| } else if repoId, ok := s.repoIdByPath[lookupPath]; ok { | ||
| return Response{ | ||
| Body: workspace.ObjectInfo{ | ||
| ObjectType: "REPO", | ||
| Path: path, | ||
| Path: originalPath, | ||
| ObjectId: repoId, | ||
| }, | ||
| } | ||
|
|
@@ -344,31 +363,45 @@ func (s *FakeWorkspace) WorkspaceGetStatus(path string) Response { | |
|
|
||
| func (s *FakeWorkspace) WorkspaceMkdirs(request workspace.Mkdirs) { | ||
| defer s.LockUnlock()() | ||
| s.directories[request.Path] = workspace.ObjectInfo{ | ||
| // Normalize path for storage: strip /Workspace prefix if present | ||
| storagePath, _ := strings.CutPrefix(request.Path, "/Workspace") | ||
| s.directories[storagePath] = workspace.ObjectInfo{ | ||
| ObjectType: "DIRECTORY", | ||
| Path: request.Path, | ||
| Path: request.Path, // Store original path | ||
| ObjectId: nextID(), | ||
| } | ||
| } | ||
|
|
||
| func (s *FakeWorkspace) WorkspaceExport(path string) []byte { | ||
| func (s *FakeWorkspace) WorkspaceExport(path string) Response { | ||
| defer s.LockUnlock()() | ||
| return s.files[path].Data | ||
| // Normalize path for lookup | ||
| path, _ = strings.CutPrefix(path, "/Workspace") | ||
| if entry, ok := s.files[path]; ok { | ||
| return Response{ | ||
| Body: entry.Data, | ||
| } | ||
| } | ||
| return Response{ | ||
| StatusCode: 404, | ||
| Body: map[string]string{"message": "File not found: " + path}, | ||
| } | ||
| } | ||
|
|
||
| func (s *FakeWorkspace) WorkspaceDelete(path string, recursive bool) { | ||
| defer s.LockUnlock()() | ||
| // Normalize path for lookup | ||
| lookupPath, _ := strings.CutPrefix(path, "/Workspace") | ||
| if !recursive { | ||
| delete(s.files, path) | ||
| delete(s.directories, path) | ||
| delete(s.files, lookupPath) | ||
| delete(s.directories, lookupPath) | ||
| } else { | ||
| for key := range s.files { | ||
| if strings.HasPrefix(key, path) { | ||
| if strings.HasPrefix(key, lookupPath) { | ||
| delete(s.files, key) | ||
| } | ||
| } | ||
| for key := range s.directories { | ||
| if strings.HasPrefix(key, path) { | ||
| if strings.HasPrefix(key, lookupPath) { | ||
| delete(s.directories, key) | ||
| } | ||
| } | ||
|
|
@@ -380,55 +413,63 @@ func (s *FakeWorkspace) WorkspaceFilesImportFile(filePath string, body []byte, o | |
| filePath = "/" + filePath | ||
| } | ||
|
|
||
| defer s.LockUnlock()() | ||
| // Normalize path for storage: strip /Workspace prefix if present | ||
| storagePath, _ := strings.CutPrefix(filePath, "/Workspace") | ||
|
|
||
| workspacePath := filePath | ||
| defer s.LockUnlock()() | ||
|
|
||
| if !overwrite { | ||
| if _, exists := s.files[workspacePath]; exists { | ||
| if _, exists := s.files[storagePath]; exists { | ||
| return Response{ | ||
| StatusCode: 409, | ||
| Body: map[string]string{"message": fmt.Sprintf("File already exists at (%s).", workspacePath)}, | ||
| Body: map[string]string{"message": fmt.Sprintf("File already exists at (%s).", filePath)}, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Note: Files with .py, .scala, .r or .sql extension can | ||
| // be notebooks if they contain a magical "Databricks notebook source" | ||
| // header comment. We omit support non-python extensions for now for simplicity. | ||
| extension := filepath.Ext(filePath) | ||
| extension := filepath.Ext(storagePath) | ||
| if extension == ".py" && strings.HasPrefix(string(body), "# Databricks notebook source") { | ||
| // Notebooks are stripped of their extension by the workspace import API. | ||
| workspacePath = strings.TrimSuffix(filePath, extension) | ||
| s.files[workspacePath] = FileEntry{ | ||
| storagePathWithoutExt := strings.TrimSuffix(storagePath, extension) | ||
| displayPath := strings.TrimSuffix(filePath, extension) | ||
| s.files[storagePathWithoutExt] = FileEntry{ | ||
| Info: workspace.ObjectInfo{ | ||
| ObjectType: "NOTEBOOK", | ||
| Path: workspacePath, | ||
| Path: displayPath, // Use original path with /Workspace | ||
| Language: "PYTHON", | ||
| ObjectId: nextID(), | ||
| }, | ||
| Data: body, | ||
| } | ||
| storagePath = storagePathWithoutExt // Update for directory creation below | ||
| } else { | ||
| // The endpoint does not set language for files, so we omit that | ||
| // here as well. | ||
| // ref: https://docs.databricks.com/api/workspace/workspace/getstatus#language | ||
| s.files[workspacePath] = FileEntry{ | ||
| s.files[storagePath] = FileEntry{ | ||
| Info: workspace.ObjectInfo{ | ||
| ObjectType: "FILE", | ||
| Path: workspacePath, | ||
| Path: filePath, // Use original path with /Workspace | ||
| ObjectId: nextID(), | ||
| }, | ||
| Data: body, | ||
| } | ||
| } | ||
|
|
||
| // Add all directories in the path to the directories map | ||
| for dir := path.Dir(workspacePath); dir != "/"; dir = path.Dir(dir) { | ||
| for dir := path.Dir(storagePath); dir != "/"; dir = path.Dir(dir) { | ||
| if _, exists := s.directories[dir]; !exists { | ||
| // Calculate display path for directory | ||
| displayDir := dir | ||
| if strings.HasPrefix(filePath, "/Workspace/") { | ||
| displayDir = "/Workspace" + dir | ||
| } | ||
| s.directories[dir] = workspace.ObjectInfo{ | ||
| ObjectType: "DIRECTORY", | ||
| Path: dir, | ||
| Path: displayDir, | ||
| ObjectId: nextID(), | ||
| } | ||
| } | ||
|
|
@@ -444,7 +485,12 @@ func (s *FakeWorkspace) WorkspaceFilesExportFile(path string) []byte { | |
|
|
||
| defer s.LockUnlock()() | ||
|
|
||
| return s.files[path].Data | ||
| // Normalize path for lookup | ||
| lookupPath, _ := strings.CutPrefix(path, "/Workspace") | ||
| if entry, ok := s.files[lookupPath]; ok { | ||
| return entry.Data | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // FileExists checks if a file exists at the given path. | ||
|
|
@@ -455,7 +501,8 @@ func (s *FakeWorkspace) FileExists(path string) bool { | |
|
|
||
| defer s.LockUnlock()() | ||
|
|
||
| _, exists := s.files[path] | ||
| lookupPath, _ := strings.CutPrefix(path, "/Workspace") | ||
| _, exists := s.files[lookupPath] | ||
| return exists | ||
| } | ||
|
|
||
|
|
@@ -467,7 +514,8 @@ func (s *FakeWorkspace) DirectoryExists(path string) bool { | |
|
|
||
| defer s.LockUnlock()() | ||
|
|
||
| _, exists := s.directories[path] | ||
| lookupPath, _ := strings.CutPrefix(path, "/Workspace") | ||
| _, exists := s.directories[lookupPath] | ||
| return exists | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to move this to
settings.json, the local one is for local only.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#4537