Skip to content

Commit 46ca9b5

Browse files
denikIsaac
andcommitted
bundle: report a directory at a state path as "not a file"
The workspace exports a directory as a ZIP archive even with direct_download=true, so since the state read stopped calling get-status first, a directory there failed with a JSON parse error on the archive. Recognize the ZIP header and say what is wrong. The test server now exports directories the same way. Co-authored-by: Isaac <no-reply@databricks.com>
1 parent 4244322 commit 46ca9b5

7 files changed

Lines changed: 56 additions & 0 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
bundle:
2+
name: directory-at-state-path-$UNIQUE_NAME
3+
4+
resources:
5+
jobs:
6+
foo:
7+
name: foo
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Cloud = true
2+
EnvMatrix.DMS = ["", "true"]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
>>> musterr [CLI] bundle plan
3+
Error: reading resources.json: not a file: the workspace has a directory at this path
4+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
envsubst < databricks.yml.tmpl > databricks.yml
2+
3+
# Something else created a directory where the CLI keeps its state file.
4+
state_path=$($CLI bundle validate -o json | jq -r .workspace.state_path)
5+
# MSYS_NO_PATHCONV stops Git Bash on Windows from rewriting the /Workspace/... argument into a local path.
6+
MSYS_NO_PATHCONV=1 $CLI workspace mkdirs "$state_path/resources.json"
7+
8+
cleanup() {
9+
MSYS_NO_PATHCONV=1 $CLI workspace delete --recursive "$($CLI bundle validate -o json | jq -r .workspace.root_path)"
10+
rm -f "$OUT_REQUESTS"
11+
}
12+
trap cleanup EXIT
13+
14+
trace musterr $CLI bundle plan
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Exercises the real workspace's export of a directory, which returns a ZIP archive.
2+
Cloud = true

‎bundle/statemgmt/state_pull.go‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package statemgmt
22

33
import (
4+
"bytes"
45
"context"
56
"encoding/json"
67
"errors"
@@ -26,6 +27,9 @@ import (
2627

2728
type AlwaysPull bool
2829

30+
// zipMagic starts every ZIP archive; a state file is JSON and never does.
31+
var zipMagic = []byte("PK\x03\x04")
32+
2933
type StateDesc struct {
3034
Serial int `json:"serial"`
3135
Lineage string `json:"lineage"`
@@ -113,6 +117,11 @@ func _filerRead(ctx context.Context, f filer.Filer, path string) (*StateDesc, er
113117
state := &StateDesc{}
114118
err = json.Unmarshal(content, state)
115119
if err != nil {
120+
// The workspace exports a directory as a ZIP archive rather than failing, so a directory
121+
// at the state path reads fine and only shows up here.
122+
if bytes.HasPrefix(content, zipMagic) {
123+
return nil, errors.New("not a file: the workspace has a directory at this path")
124+
}
116125
return nil, fmt.Errorf("parsing state: %w", err)
117126
}
118127

‎libs/testserver/fake_workspace.go‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,9 +845,27 @@ func (s *FakeWorkspace) WorkspaceMkdirs(request workspace.Mkdirs) {
845845

846846
func (s *FakeWorkspace) WorkspaceExport(path string) []byte {
847847
defer s.LockUnlock()()
848+
if _, isDir := s.directories[path]; isDir {
849+
return exportDirectory(path)
850+
}
848851
return s.files[path].Data
849852
}
850853

854+
// exportDirectory mimics the real API, which exports a directory as a ZIP archive even with
855+
// direct_download=true. Only the directory's own entry is included: callers just need to see
856+
// that they got an archive rather than a file.
857+
func exportDirectory(dirPath string) []byte {
858+
var buf bytes.Buffer
859+
zw := zip.NewWriter(&buf)
860+
if _, err := zw.Create(path.Base(dirPath) + "/"); err != nil {
861+
panic(err)
862+
}
863+
if err := zw.Close(); err != nil {
864+
panic(err)
865+
}
866+
return buf.Bytes()
867+
}
868+
851869
// WorkspaceDelete implements POST /api/2.0/workspace/delete. As in the real API, a
852870
// non-recursive delete of a directory that still has children fails instead of removing
853871
// it, which is what lets a caller delete a directory only if it is empty.

0 commit comments

Comments
 (0)