Skip to content
Open
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
1 change: 1 addition & 0 deletions .nextchanges/bundles/template-symlinked-directory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Report a clear error for symlinked directories in `bundle init` templates instead of failing partway through with `is a directory`. ([#6758](https://github.com/databricks/cli/pull/6758))
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}

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
@@ -0,0 +1,4 @@
Error: linked: symbolic links to directories are not supported in templates

>>> find.py linked|shared
template/shared/file.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export NO_COLOR=1

# The symlink is created here rather than committed: the acceptance harness copies
# test inputs with filepath.Walk, which reads a symlinked directory as a file.
ln -s shared template/linked

musterr $CLI bundle init .

trace find.py 'linked|shared'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
GOOS.windows = false
Ignore = ["template/linked"]
18 changes: 17 additions & 1 deletion libs/template/renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,24 @@ func (r *renderer) walk() error {
continue
}

entryPath := path.Join(currentDirectory, entry.Name())

// fs.ReadDir reports entries via Lstat, so IsDir is false for a symlink
// pointing at a directory. Such an entry is not readable as a file, and
// following it could traverse a symlink cycle, so reject it here rather
// than fail partway through materializing the template.
if entry.Type()&fs.ModeSymlink != 0 {
info, err := fs.Stat(r.srcFS, entryPath)
if err != nil {
return err
}
if info.IsDir() {
return fmt.Errorf("%s: symbolic links to directories are not supported in templates", entryPath)
}
}

// Generate in memory representation of file
f, err := r.computeFile(path.Join(currentDirectory, entry.Name()))
f, err := r.computeFile(entryPath)
if err != nil {
return err
}
Expand Down
51 changes: 51 additions & 0 deletions libs/template/renderer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,57 @@ func TestRendererWalk(t *testing.T) {
assert.Equal(t, "file four", getContent(r, "dir2/file4"))
}

func TestRendererWalkSymlinks(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("symlink creation needs privilege on Windows")
}

tests := []struct {
name string
target string
expectedErr string
}{
{
name: "symlink to file",
target: "shared/file.txt",
},
{
name: "symlink to directory",
target: "shared",
expectedErr: "linked: symbolic links to directories are not supported in templates",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := t.Context()
ctx = cmdctx.SetWorkspaceClient(ctx, nil)

templateDir := t.TempDir()
require.NoError(t, os.MkdirAll(filepath.Join(templateDir, "template", "shared"), 0o755))
require.NoError(t, os.MkdirAll(filepath.Join(templateDir, "library"), 0o755))
testutil.WriteFile(t, filepath.Join(templateDir, "template", "shared", "file.txt"), "hello")
require.NoError(t, os.Symlink(tt.target, filepath.Join(templateDir, "template", "linked")))

r, err := newRenderer(ctx, nil, nil, os.DirFS(templateDir), "template", "library")
require.NoError(t, err)

err = r.walk()
if tt.expectedErr != "" {
assert.EqualError(t, err, tt.expectedErr)
return
}

require.NoError(t, err)
paths := make([]string, 0, len(r.files))
for _, f := range r.files {
paths = append(paths, f.RelPath())
}
assert.Equal(t, []string{"linked", "shared/file.txt"}, paths)
})
}
}

func TestRendererFailFunction(t *testing.T) {
ctx := t.Context()
ctx = cmdctx.SetWorkspaceClient(ctx, nil)
Expand Down