Skip to content

Commit 092bc46

Browse files
committed
Use filer for materializing template files
1 parent 9e7aaa3 commit 092bc46

File tree

10 files changed

+147
-197
lines changed

10 files changed

+147
-197
lines changed

libs/filer/filer.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,22 @@ import (
77
"io/fs"
88
)
99

10+
// WriteMode captures intent when writing a file.
11+
//
12+
// The first 9 bits are reserved for the [fs.FileMode] permission bits.
13+
// These are used only by the local filer implementation and have
14+
// no effect for the other implementations.
1015
type WriteMode int
1116

17+
// writeModePerm is a mask to extract permission bits from a WriteMode.
18+
const writeModePerm = WriteMode(fs.ModePerm)
19+
1220
const (
13-
OverwriteIfExists WriteMode = 1 << iota
21+
OverwriteIfExists WriteMode = writeModePerm + 1<<iota
1422
CreateParentDirectories
1523
)
1624

25+
// DeleteMode captures intent when deleting a file.
1726
type DeleteMode int
1827

1928
const (

libs/filer/filer_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package filer
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestWriteMode(t *testing.T) {
10+
assert.Equal(t, 512, int(OverwriteIfExists))
11+
}

libs/filer/local_client.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,31 @@ func (w *LocalClient) Write(ctx context.Context, name string, reader io.Reader,
2828
return err
2929
}
3030

31+
// Retrieve permission mask from the [WriteMode], if present.
32+
perm := fs.FileMode(0644)
33+
for _, m := range mode {
34+
bits := m & writeModePerm
35+
if bits != 0 {
36+
perm = fs.FileMode(bits)
37+
}
38+
}
39+
3140
flags := os.O_WRONLY | os.O_CREATE
3241
if slices.Contains(mode, OverwriteIfExists) {
3342
flags |= os.O_TRUNC
3443
} else {
3544
flags |= os.O_EXCL
3645
}
3746

38-
f, err := os.OpenFile(absPath, flags, 0644)
47+
f, err := os.OpenFile(absPath, flags, perm)
3948
if errors.Is(err, fs.ErrNotExist) && slices.Contains(mode, CreateParentDirectories) {
4049
// Create parent directories if they don't exist.
4150
err = os.MkdirAll(filepath.Dir(absPath), 0755)
4251
if err != nil {
4352
return err
4453
}
4554
// Try again.
46-
f, err = os.OpenFile(absPath, flags, 0644)
55+
f, err = os.OpenFile(absPath, flags, perm)
4756
}
4857

4958
if err != nil {

libs/template/config_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func TestTemplateConfigAssignValuesFromDefaultValues(t *testing.T) {
8383
c, err := newConfig(ctx, os.DirFS(testDir), "schema.json")
8484
require.NoError(t, err)
8585

86-
r, err := newRenderer(ctx, nil, nil, os.DirFS("."), "./testdata/empty/template", "./testdata/empty/library", t.TempDir())
86+
r, err := newRenderer(ctx, nil, nil, os.DirFS("."), "./testdata/empty/template", "./testdata/empty/library")
8787
require.NoError(t, err)
8888

8989
err = c.assignDefaultValues(r)
@@ -102,7 +102,7 @@ func TestTemplateConfigAssignValuesFromTemplatedDefaultValues(t *testing.T) {
102102
c, err := newConfig(ctx, os.DirFS(testDir), "schema.json")
103103
require.NoError(t, err)
104104

105-
r, err := newRenderer(ctx, nil, nil, os.DirFS("."), path.Join(testDir, "template/template"), path.Join(testDir, "template/library"), t.TempDir())
105+
r, err := newRenderer(ctx, nil, nil, os.DirFS("."), path.Join(testDir, "template/template"), path.Join(testDir, "template/library"))
106106
require.NoError(t, err)
107107

108108
// Note: only the string value is templated.

libs/template/file.go

Lines changed: 26 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,36 @@
11
package template
22

33
import (
4+
"bytes"
45
"context"
5-
"io"
66
"io/fs"
7-
"os"
8-
"path/filepath"
97
"slices"
8+
9+
"github.com/databricks/cli/libs/filer"
1010
)
1111

1212
// Interface representing a file to be materialized from a template into a project
1313
// instance
1414
type file interface {
15-
// Destination path for file. This is where the file will be created when
16-
// PersistToDisk is called.
17-
DstPath() *destinationPath
15+
// Path of the file relative to the root of the instantiated template.
16+
// This is where the file is written to when persisting the template to disk.
17+
// Must be slash-separated.
18+
RelPath() string
1819

1920
// Write file to disk at the destination path.
20-
Write(ctx context.Context) error
21+
Write(ctx context.Context, out filer.Filer) error
2122

2223
// contents returns the file contents as a byte slice.
2324
// This is used for testing purposes.
2425
contents() ([]byte, error)
2526
}
2627

27-
type destinationPath struct {
28-
// Root path for the project instance. This path uses the system's default
29-
// file separator. For example /foo/bar on Unix and C:\foo\bar on windows
30-
root string
31-
32-
// Unix like file path relative to the "root" of the instantiated project. Is used to
33-
// evaluate whether the file should be skipped by comparing it to a list of
34-
// skip glob patterns.
35-
relPath string
36-
}
37-
38-
// Absolute path of the file, in the os native format. For example /foo/bar on
39-
// Unix and C:\foo\bar on windows
40-
func (f *destinationPath) absPath() string {
41-
return filepath.Join(f.root, filepath.FromSlash(f.relPath))
42-
}
43-
4428
type copyFile struct {
45-
ctx context.Context
46-
4729
// Permissions bits for the destination file
4830
perm fs.FileMode
4931

50-
dstPath *destinationPath
32+
// Destination path for the file.
33+
relPath string
5134

5235
// [fs.FS] rooted at template root. Used to read srcPath.
5336
srcFS fs.FS
@@ -56,55 +39,40 @@ type copyFile struct {
5639
srcPath string
5740
}
5841

59-
func (f *copyFile) DstPath() *destinationPath {
60-
return f.dstPath
42+
func (f *copyFile) RelPath() string {
43+
return f.relPath
6144
}
6245

63-
func (f *copyFile) Write(ctx context.Context) error {
64-
path := f.DstPath().absPath()
65-
err := os.MkdirAll(filepath.Dir(path), 0755)
66-
if err != nil {
67-
return err
68-
}
69-
srcFile, err := f.srcFS.Open(f.srcPath)
70-
if err != nil {
71-
return err
72-
}
73-
defer srcFile.Close()
74-
dstFile, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, f.perm)
46+
func (f *copyFile) Write(ctx context.Context, out filer.Filer) error {
47+
src, err := f.srcFS.Open(f.srcPath)
7548
if err != nil {
7649
return err
7750
}
78-
defer dstFile.Close()
79-
_, err = io.Copy(dstFile, srcFile)
80-
return err
51+
defer src.Close()
52+
return out.Write(ctx, f.relPath, src, filer.CreateParentDirectories, filer.WriteMode(f.perm))
8153
}
8254

8355
func (f *copyFile) contents() ([]byte, error) {
8456
return fs.ReadFile(f.srcFS, f.srcPath)
8557
}
8658

8759
type inMemoryFile struct {
88-
dstPath *destinationPath
89-
90-
content []byte
91-
9260
// Permissions bits for the destination file
9361
perm fs.FileMode
94-
}
9562

96-
func (f *inMemoryFile) DstPath() *destinationPath {
97-
return f.dstPath
63+
// Destination path for the file.
64+
relPath string
65+
66+
// Contents of the file.
67+
content []byte
9868
}
9969

100-
func (f *inMemoryFile) Write(ctx context.Context) error {
101-
path := f.DstPath().absPath()
70+
func (f *inMemoryFile) RelPath() string {
71+
return f.relPath
72+
}
10273

103-
err := os.MkdirAll(filepath.Dir(path), 0755)
104-
if err != nil {
105-
return err
106-
}
107-
return os.WriteFile(path, f.content, f.perm)
74+
func (f *inMemoryFile) Write(ctx context.Context, out filer.Filer) error {
75+
return out.Write(ctx, f.relPath, bytes.NewReader(f.content), filer.CreateParentDirectories, filer.WriteMode(f.perm))
10876
}
10977

11078
func (f *inMemoryFile) contents() ([]byte, error) {

libs/template/file_test.go

Lines changed: 12 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"runtime"
99
"testing"
1010

11+
"github.com/databricks/cli/libs/filer"
1112
"github.com/stretchr/testify/assert"
1213
"github.com/stretchr/testify/require"
1314
)
@@ -16,14 +17,14 @@ func testInMemoryFile(t *testing.T, ctx context.Context, perm fs.FileMode) {
1617
tmpDir := t.TempDir()
1718

1819
f := &inMemoryFile{
19-
dstPath: &destinationPath{
20-
root: tmpDir,
21-
relPath: "a/b/c",
22-
},
2320
perm: perm,
21+
relPath: "a/b/c",
2422
content: []byte("123"),
2523
}
26-
err := f.Write(ctx)
24+
25+
out, err := filer.NewLocalClient(tmpDir)
26+
require.NoError(t, err)
27+
err = f.Write(ctx, out)
2728
assert.NoError(t, err)
2829

2930
assertFileContent(t, filepath.Join(tmpDir, "a/b/c"), "123")
@@ -36,44 +37,21 @@ func testCopyFile(t *testing.T, ctx context.Context, perm fs.FileMode) {
3637
require.NoError(t, err)
3738

3839
f := &copyFile{
39-
ctx: context.Background(),
40-
dstPath: &destinationPath{
41-
root: tmpDir,
42-
relPath: "a/b/c",
43-
},
4440
perm: perm,
45-
srcPath: "source",
41+
relPath: "a/b/c",
4642
srcFS: os.DirFS(tmpDir),
43+
srcPath: "source",
4744
}
48-
err = f.Write(ctx)
45+
46+
out, err := filer.NewLocalClient(tmpDir)
47+
require.NoError(t, err)
48+
err = f.Write(ctx, out)
4949
assert.NoError(t, err)
5050

5151
assertFileContent(t, filepath.Join(tmpDir, "a/b/c"), "qwerty")
5252
assertFilePermissions(t, filepath.Join(tmpDir, "a/b/c"), perm)
5353
}
5454

55-
func TestTemplateFileDestinationPath(t *testing.T) {
56-
if runtime.GOOS == "windows" {
57-
t.SkipNow()
58-
}
59-
f := &destinationPath{
60-
root: `a/b/c`,
61-
relPath: "d/e",
62-
}
63-
assert.Equal(t, `a/b/c/d/e`, f.absPath())
64-
}
65-
66-
func TestTemplateFileDestinationPathForWindows(t *testing.T) {
67-
if runtime.GOOS != "windows" {
68-
t.SkipNow()
69-
}
70-
f := &destinationPath{
71-
root: `c:\a\b\c`,
72-
relPath: "d/e",
73-
}
74-
assert.Equal(t, `c:\a\b\c\d\e`, f.absPath())
75-
}
76-
7755
func TestTemplateInMemoryFilePersistToDisk(t *testing.T) {
7856
if runtime.GOOS == "windows" {
7957
t.SkipNow()

0 commit comments

Comments
 (0)