diff --git a/cmd/bundle/init.go b/cmd/bundle/init.go new file mode 100644 index 00000000000..ad9f3419546 --- /dev/null +++ b/cmd/bundle/init.go @@ -0,0 +1,60 @@ +package bundle + +import ( + "encoding/json" + "os" + "path/filepath" + + "github.com/databricks/cli/libs/git" + "github.com/databricks/cli/libs/template" + "github.com/spf13/cobra" +) + +var initCmd = &cobra.Command{ + Use: "init TEMPLATE_PATH", + Short: "Initialize Template", + Long: `Initialize template`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + templateURL := args[0] + tmpDir := os.TempDir() + templateDir := filepath.Join(tmpDir, templateURL) + ctx := cmd.Context() + + err := os.MkdirAll(templateDir, 0755) + if err != nil { + return err + } + + // TODO: should we delete this directory once we are done with it? + // It's a destructive action that can be risky + err = git.Clone(ctx, templateURL, "", templateDir) + if err != nil { + return err + } + + // TODO: substitute to a read config method that respects the schema + // and prompts for input variables + b, err := os.ReadFile(configFile) + if err != nil { + return err + } + config := make(map[string]any) + err = json.Unmarshal(b, &config) + if err != nil { + return err + } + return template.Materialize(ctx, config, templateDir, projectDir) + }, +} + +var configFile string +var projectDir string + +func init() { + initCmd.Flags().StringVar(&configFile, "config-file", "", "Input parameters for template initialization.") + initCmd.Flags().StringVar(&projectDir, "project-dir", "", "The project will be initialized in this directory.") + initCmd.MarkFlagRequired("config-file") + initCmd.MarkFlagRequired("output-dir") + AddCommand(initCmd) +} diff --git a/libs/template/file.go b/libs/template/file.go new file mode 100644 index 00000000000..41d25a9d899 --- /dev/null +++ b/libs/template/file.go @@ -0,0 +1,96 @@ +package template + +import ( + "context" + "io" + "io/fs" + "os" + "path/filepath" + + "github.com/databricks/cli/libs/filer" +) + +// Interface for an in memory representation of a file +type file interface { + // Full path of the file, in the os native format. For example /foo/bar on + // Unix and C:\foo\bar on windows + Path() string + + // Unix like file path relative to the "root" of the instantiated project. Is used to + // evaluate whether the file should be skipped by comparing it to a list of + // skip glob patterns + RelPath() string + + // This function writes this file onto the disk + PersistToDisk() error +} + +type fileCommon struct { + // Root path for the project instance. This path uses the system's default + // file separator. For example /foo/bar on Unix and C:\foo\bar on windows + root string + + // Unix like relPath for the file (using '/' as the separator). This path + // is relative to the root. Using unix like relative paths enables skip patterns + // to work across both windows and unix based operating systems. + relPath string + + // Permissions bits for the file + perm fs.FileMode +} + +func (f *fileCommon) Path() string { + return filepath.Join(f.root, filepath.FromSlash(f.relPath)) +} + +func (f *fileCommon) RelPath() string { + return f.relPath +} + +type copyFile struct { + *fileCommon + + ctx context.Context + + // Path of the source file that should be copied over. + srcPath string + + // Filer to use to read source path + srcFiler filer.Filer +} + +func (f *copyFile) PersistToDisk() error { + path := f.Path() + err := os.MkdirAll(filepath.Dir(path), 0755) + if err != nil { + return err + } + srcFile, err := f.srcFiler.Read(f.ctx, f.srcPath) + if err != nil { + return err + } + defer srcFile.Close() + dstFile, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, f.perm) + if err != nil { + return err + } + defer dstFile.Close() + _, err = io.Copy(dstFile, srcFile) + return err +} + +type inMemoryFile struct { + *fileCommon + + content []byte +} + +func (f *inMemoryFile) PersistToDisk() error { + path := f.Path() + + err := os.MkdirAll(filepath.Dir(path), 0755) + if err != nil { + return err + } + return os.WriteFile(path, f.content, f.perm) +} diff --git a/libs/template/file_test.go b/libs/template/file_test.go new file mode 100644 index 00000000000..1dcd81d3915 --- /dev/null +++ b/libs/template/file_test.go @@ -0,0 +1,137 @@ +package template + +import ( + "context" + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/databricks/cli/libs/filer" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTemplateFileCommonPathForWindows(t *testing.T) { + if runtime.GOOS != "windows" { + t.SkipNow() + } + f := &fileCommon{ + root: `c:\a\b\c`, + relPath: "d/e", + } + assert.Equal(t, `c:\a\b\c\d\e`, f.Path()) + assert.Equal(t, `d/e`, f.RelPath()) +} + +func TestTemplateFileCommonPathForUnix(t *testing.T) { + if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { + t.SkipNow() + } + f := &fileCommon{ + root: `a/b/c`, + relPath: "d/e", + } + assert.Equal(t, `a/b/c/d/e`, f.Path()) + assert.Equal(t, `d/e`, f.RelPath()) +} + +func TestTemplateFileInMemoryFilePersistToDisk(t *testing.T) { + if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { + t.SkipNow() + } + tmpDir := t.TempDir() + + f := &inMemoryFile{ + fileCommon: &fileCommon{ + root: tmpDir, + relPath: "a/b/c", + perm: 0755, + }, + content: []byte("123"), + } + err := f.PersistToDisk() + assert.NoError(t, err) + + assertFileContent(t, filepath.Join(tmpDir, "a/b/c"), "123") + assertFilePermissions(t, filepath.Join(tmpDir, "a/b/c"), 0755) +} + +func TestTemplateFileCopyFilePersistToDisk(t *testing.T) { + if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { + t.SkipNow() + } + tmpDir := t.TempDir() + + templateFiler, err := filer.NewLocalClient(tmpDir) + require.NoError(t, err) + os.WriteFile(filepath.Join(tmpDir, "source"), []byte("qwerty"), 0644) + + f := ©File{ + ctx: context.Background(), + fileCommon: &fileCommon{ + root: tmpDir, + relPath: "a/b/c", + perm: 0644, + }, + srcPath: "source", + srcFiler: templateFiler, + } + err = f.PersistToDisk() + assert.NoError(t, err) + + assertFileContent(t, filepath.Join(tmpDir, "a/b/c"), "qwerty") + assertFilePermissions(t, filepath.Join(tmpDir, "a/b/c"), 0644) +} + +// we have separate tests for windows because of differences in valid +// fs.FileMode values we can use for different operating systems. +func TestTemplateFileInMemoryFilePersistToDiskForWindows(t *testing.T) { + if runtime.GOOS != "windows" { + t.SkipNow() + } + tmpDir := t.TempDir() + + f := &inMemoryFile{ + fileCommon: &fileCommon{ + root: tmpDir, + relPath: "a/b/c", + perm: 0666, + }, + content: []byte("123"), + } + err := f.PersistToDisk() + assert.NoError(t, err) + + assertFileContent(t, filepath.Join(tmpDir, "a/b/c"), "123") + assertFilePermissions(t, filepath.Join(tmpDir, "a/b/c"), 0666) +} + +// we have separate tests for windows because of differences in valid +// fs.FileMode values we can use for different operating systems. +func TestTemplateFileCopyFilePersistToDiskForWindows(t *testing.T) { + if runtime.GOOS != "windows" { + t.SkipNow() + } + tmpDir := t.TempDir() + + templateFiler, err := filer.NewLocalClient(tmpDir) + require.NoError(t, err) + os.WriteFile(filepath.Join(tmpDir, "source"), []byte("qwerty"), 0666) + + f := ©File{ + ctx: context.Background(), + fileCommon: &fileCommon{ + root: tmpDir, + relPath: "a/b/c", + perm: 0666, + }, + srcPath: "source", + srcFiler: templateFiler, + } + err = f.PersistToDisk() + assert.NoError(t, err) + + assertFileContent(t, filepath.Join(tmpDir, "a/b/c"), "qwerty") + assertFilePermissions(t, filepath.Join(tmpDir, "a/b/c"), 0666) +} diff --git a/libs/template/helpers_test.go b/libs/template/helpers_test.go index 51c470efca7..8e0561eb8fb 100644 --- a/libs/template/helpers_test.go +++ b/libs/template/helpers_test.go @@ -20,7 +20,7 @@ func TestTemplatePrintStringWithoutProcessing(t *testing.T) { assert.NoError(t, err) assert.Len(t, r.files, 1) - cleanContent := strings.Trim(string(r.files[0].content), "\n\r") + cleanContent := strings.Trim(string(r.files[0].(*inMemoryFile).content), "\n\r") assert.Equal(t, `{{ fail "abc" }}`, cleanContent) } @@ -35,7 +35,7 @@ func TestTemplateRegexpCompileFunction(t *testing.T) { assert.NoError(t, err) assert.Len(t, r.files, 1) - content := string(r.files[0].content) + content := string(r.files[0].(*inMemoryFile).content) assert.Contains(t, content, "0:food") assert.Contains(t, content, "1:fool") } diff --git a/libs/template/materialize.go b/libs/template/materialize.go new file mode 100644 index 00000000000..ca67e89f554 --- /dev/null +++ b/libs/template/materialize.go @@ -0,0 +1,24 @@ +package template + +import ( + "context" + "path/filepath" +) + +const libraryDirName = "library" +const templateDirName = "template" + +func Materialize(ctx context.Context, config map[string]any, templateRoot, instanceRoot string) error { + templatePath := filepath.Join(templateRoot, templateDirName) + libraryPath := filepath.Join(templateRoot, libraryDirName) + + r, err := newRenderer(ctx, config, templatePath, libraryPath, instanceRoot) + if err != nil { + return err + } + err = r.walk() + if err != nil { + return err + } + return r.persistToDisk() +} diff --git a/libs/template/renderer.go b/libs/template/renderer.go index 8502a92889d..841166129b6 100644 --- a/libs/template/renderer.go +++ b/libs/template/renderer.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io" - "io/fs" "os" "path" "path/filepath" @@ -18,32 +17,7 @@ import ( "golang.org/x/exp/slices" ) -type inMemoryFile struct { - // Root path for the project instance. This path uses the system's default - // file separator. For example /foo/bar on Unix and C:\foo\bar on windows - root string - - // Unix like relPath for the file (using '/' as the separator). This path - // is relative to the root. Using unix like relative paths enables skip patterns - // to work across both windows and unix based operating systems. - relPath string - content []byte - perm fs.FileMode -} - -func (f *inMemoryFile) fullPath() string { - return filepath.Join(f.root, filepath.FromSlash(f.relPath)) -} - -func (f *inMemoryFile) persistToDisk() error { - path := f.fullPath() - - err := os.MkdirAll(filepath.Dir(path), 0755) - if err != nil { - return err - } - return os.WriteFile(path, f.content, f.perm) -} +const templateExtension = ".tmpl" // Renders a databricks template as a project type renderer struct { @@ -60,7 +34,7 @@ type renderer struct { baseTemplate *template.Template // List of in memory files generated from template - files []*inMemoryFile + files []file // Glob patterns for files and directories to skip. There are three possible // outcomes for skip: @@ -111,7 +85,7 @@ func newRenderer(ctx context.Context, config map[string]any, templateRoot, libra ctx: ctx, config: config, baseTemplate: tmpl, - files: make([]*inMemoryFile, 0), + files: make([]file, 0), skipPatterns: make([]string, 0), templateFiler: templateFiler, instanceRoot: instanceRoot, @@ -142,16 +116,13 @@ func (r *renderer) executeTemplate(templateDefinition string) (string, error) { return result.String(), nil } -func (r *renderer) computeFile(relPathTemplate string) (*inMemoryFile, error) { +func (r *renderer) computeFile(relPathTemplate string) (file, error) { // read template file contents templateReader, err := r.templateFiler.Read(r.ctx, relPathTemplate) if err != nil { return nil, err } - contentTemplate, err := io.ReadAll(templateReader) - if err != nil { - return nil, err - } + defer templateReader.Close() // read file permissions info, err := r.templateFiler.Stat(r.ctx, relPathTemplate) @@ -160,7 +131,26 @@ func (r *renderer) computeFile(relPathTemplate string) (*inMemoryFile, error) { } perm := info.Mode().Perm() + // If file name does not specify the `.tmpl` extension, then it is copied + // over as is, without treating it as a template + if !strings.HasSuffix(relPathTemplate, templateExtension) { + return ©File{ + fileCommon: &fileCommon{ + root: r.instanceRoot, + relPath: relPathTemplate, + perm: perm, + }, + ctx: r.ctx, + srcPath: relPathTemplate, + srcFiler: r.templateFiler, + }, nil + } + // execute the contents of the file as a template + contentTemplate, err := io.ReadAll(templateReader) + if err != nil { + return nil, err + } content, err := r.executeTemplate(string(contentTemplate)) // Capture errors caused by the "fail" helper function if target := (&ErrFail{}); errors.As(err, target) { @@ -171,16 +161,19 @@ func (r *renderer) computeFile(relPathTemplate string) (*inMemoryFile, error) { } // Execute relative path template to get materialized path for the file + relPathTemplate = strings.TrimSuffix(relPathTemplate, templateExtension) relPath, err := r.executeTemplate(relPathTemplate) if err != nil { return nil, err } return &inMemoryFile{ - root: r.instanceRoot, - relPath: relPath, + fileCommon: &fileCommon{ + root: r.instanceRoot, + relPath: relPath, + perm: perm, + }, content: []byte(content), - perm: perm, }, nil } @@ -206,11 +199,11 @@ func (r *renderer) walk() error { if err != nil { return err } - isSkipped, err := r.isSkipped(instanceDirectory) + match, err := isSkipped(instanceDirectory, r.skipPatterns) if err != nil { return err } - if isSkipped { + if match { logger.Infof(r.ctx, "skipping directory: %s", instanceDirectory) continue } @@ -255,7 +248,7 @@ func (r *renderer) walk() error { if err != nil { return err } - logger.Infof(r.ctx, "added file to list of in memory files: %s", f.relPath) + logger.Infof(r.ctx, "added file to list of in memory files: %s", f.Path()) r.files = append(r.files, f) } @@ -266,14 +259,14 @@ func (r *renderer) walk() error { func (r *renderer) persistToDisk() error { // Accumulate files which we will persist, skipping files whose path matches // any of the skip patterns - filesToPersist := make([]*inMemoryFile, 0) + filesToPersist := make([]file, 0) for _, file := range r.files { - isSkipped, err := r.isSkipped(file.relPath) + match, err := isSkipped(file.RelPath(), r.skipPatterns) if err != nil { return err } - if isSkipped { - log.Infof(r.ctx, "skipping file: %s", file.relPath) + if match { + log.Infof(r.ctx, "skipping file: %s", file.Path()) continue } filesToPersist = append(filesToPersist, file) @@ -281,7 +274,7 @@ func (r *renderer) persistToDisk() error { // Assert no conflicting files exist for _, file := range filesToPersist { - path := file.fullPath() + path := file.Path() _, err := os.Stat(path) if err == nil { return fmt.Errorf("failed to persist to disk, conflict with existing file: %s", path) @@ -293,7 +286,7 @@ func (r *renderer) persistToDisk() error { // Persist files to disk for _, file := range filesToPersist { - err := file.persistToDisk() + err := file.PersistToDisk() if err != nil { return err } @@ -301,8 +294,8 @@ func (r *renderer) persistToDisk() error { return nil } -func (r *renderer) isSkipped(filePath string) (bool, error) { - for _, pattern := range r.skipPatterns { +func isSkipped(filePath string, patterns []string) (bool, error) { + for _, pattern := range patterns { isMatch, err := path.Match(pattern, filePath) if err != nil { return false, err diff --git a/libs/template/renderer_test.go b/libs/template/renderer_test.go index 468c607f4fe..89c2424600e 100644 --- a/libs/template/renderer_test.go +++ b/libs/template/renderer_test.go @@ -3,6 +3,7 @@ package template import ( "context" "fmt" + "io" "io/fs" "os" "path/filepath" @@ -89,59 +90,58 @@ My email is {{template "email"}} } func TestRendererIsSkipped(t *testing.T) { - r := renderer{ - skipPatterns: []string{"a*", "*yz", "def", "a/b/*"}, - } + + skipPatterns := []string{"a*", "*yz", "def", "a/b/*"} // skipped paths - isSkipped, err := r.isSkipped("abc") + match, err := isSkipped("abc", skipPatterns) require.NoError(t, err) - assert.True(t, isSkipped) + assert.True(t, match) - isSkipped, err = r.isSkipped("abcd") + match, err = isSkipped("abcd", skipPatterns) require.NoError(t, err) - assert.True(t, isSkipped) + assert.True(t, match) - isSkipped, err = r.isSkipped("a") + match, err = isSkipped("a", skipPatterns) require.NoError(t, err) - assert.True(t, isSkipped) + assert.True(t, match) - isSkipped, err = r.isSkipped("xxyz") + match, err = isSkipped("xxyz", skipPatterns) require.NoError(t, err) - assert.True(t, isSkipped) + assert.True(t, match) - isSkipped, err = r.isSkipped("yz") + match, err = isSkipped("yz", skipPatterns) require.NoError(t, err) - assert.True(t, isSkipped) + assert.True(t, match) - isSkipped, err = r.isSkipped("a/b/c") + match, err = isSkipped("a/b/c", skipPatterns) require.NoError(t, err) - assert.True(t, isSkipped) + assert.True(t, match) // NOT skipped paths - isSkipped, err = r.isSkipped(".") + match, err = isSkipped(".", skipPatterns) require.NoError(t, err) - assert.False(t, isSkipped) + assert.False(t, match) - isSkipped, err = r.isSkipped("y") + match, err = isSkipped("y", skipPatterns) require.NoError(t, err) - assert.False(t, isSkipped) + assert.False(t, match) - isSkipped, err = r.isSkipped("z") + match, err = isSkipped("z", skipPatterns) require.NoError(t, err) - assert.False(t, isSkipped) + assert.False(t, match) - isSkipped, err = r.isSkipped("defg") + match, err = isSkipped("defg", skipPatterns) require.NoError(t, err) - assert.False(t, isSkipped) + assert.False(t, match) - isSkipped, err = r.isSkipped("cat") + match, err = isSkipped("cat", skipPatterns) require.NoError(t, err) - assert.False(t, isSkipped) + assert.False(t, match) - isSkipped, err = r.isSkipped("a/b/c/d") + match, err = isSkipped("a/b/c/d", skipPatterns) require.NoError(t, err) - assert.False(t, isSkipped) + assert.False(t, match) } func TestRendererPersistToDisk(t *testing.T) { @@ -152,30 +152,38 @@ func TestRendererPersistToDisk(t *testing.T) { ctx: ctx, instanceRoot: tmpDir, skipPatterns: []string{"a/b/c", "mn*"}, - files: []*inMemoryFile{ - { - root: tmpDir, - relPath: "a/b/c", + files: []file{ + &inMemoryFile{ + fileCommon: &fileCommon{ + root: tmpDir, + relPath: "a/b/c", + perm: 0444, + }, content: nil, - perm: 0444, }, - { - root: tmpDir, - relPath: "mno", + &inMemoryFile{ + fileCommon: &fileCommon{ + root: tmpDir, + relPath: "mno", + perm: 0444, + }, content: nil, - perm: 0444, }, - { - root: tmpDir, - relPath: "a/b/d", + &inMemoryFile{ + fileCommon: &fileCommon{ + root: tmpDir, + relPath: "a/b/d", + perm: 0444, + }, content: []byte("123"), - perm: 0444, }, - { - root: tmpDir, - relPath: "mmnn", + &inMemoryFile{ + fileCommon: &fileCommon{ + root: tmpDir, + relPath: "mmnn", + perm: 0444, + }, content: []byte("456"), - perm: 0444, }, }, } @@ -204,8 +212,23 @@ func TestRendererWalk(t *testing.T) { getContent := func(r *renderer, path string) string { for _, f := range r.files { - if f.relPath == path { - return strings.Trim(string(f.content), "\r\n") + switch v := f.(type) { + case *inMemoryFile: + if v.relPath != path { + continue + } + return strings.Trim(string(v.content), "\r\n") + case *copyFile: + if v.relPath != path { + continue + } + r, err := r.templateFiler.Read(context.Background(), v.srcPath) + require.NoError(t, err) + b, err := io.ReadAll(r) + require.NoError(t, err) + return strings.Trim(string(b), "\r\n") + default: + require.FailNow(t, "execution should not reach here") } } require.FailNow(t, "file is absent: "+path) @@ -241,7 +264,7 @@ func TestRendererSkipsDirsEagerly(t *testing.T) { assert.NoError(t, err) assert.Len(t, r.files, 1) - content := string(r.files[0].content) + content := string(r.files[0].(*inMemoryFile).content) assert.Equal(t, "I should be the only file created", strings.Trim(content, "\r\n")) } @@ -314,48 +337,12 @@ func TestRendererInMemoryFileFullPathForWindows(t *testing.T) { t.SkipNow() } f := &inMemoryFile{ - root: `c:\a\b\c`, - relPath: "d/e", - } - assert.Equal(t, `c:\a\b\c\d\e`, f.fullPath()) -} - -func TestRendererInMemoryFilePersistToDiskSetsExecutableBit(t *testing.T) { - if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { - t.SkipNow() - } - tmpDir := t.TempDir() - - f := &inMemoryFile{ - root: tmpDir, - relPath: "a/b/c", - content: []byte("123"), - perm: 0755, - } - err := f.persistToDisk() - assert.NoError(t, err) - - assertFileContent(t, filepath.Join(tmpDir, "a/b/c"), "123") - assertFilePermissions(t, filepath.Join(tmpDir, "a/b/c"), 0755) -} - -func TestRendererInMemoryFilePersistToDiskForWindows(t *testing.T) { - if runtime.GOOS != "windows" { - t.SkipNow() - } - tmpDir := t.TempDir() - - f := &inMemoryFile{ - root: tmpDir, - relPath: "a/b/c", - content: []byte("123"), - perm: 0666, + fileCommon: &fileCommon{ + root: `c:\a\b\c`, + relPath: "d/e", + }, } - err := f.persistToDisk() - assert.NoError(t, err) - - assertFileContent(t, filepath.Join(tmpDir, "a/b/c"), "123") - assertFilePermissions(t, filepath.Join(tmpDir, "a/b/c"), 0666) + assert.Equal(t, `c:\a\b\c\d\e`, f.Path()) } func TestRendererReadsPermissionsBits(t *testing.T) { @@ -373,8 +360,18 @@ func TestRendererReadsPermissionsBits(t *testing.T) { getPermissions := func(r *renderer, path string) fs.FileMode { for _, f := range r.files { - if f.relPath == path { - return f.perm + switch v := f.(type) { + case *inMemoryFile: + if v.relPath == path { + return v.perm + } + + case *copyFile: + if v.relPath == path { + return v.perm + } + default: + require.FailNow(t, "execution should not reach here") } } require.FailNow(t, "file is absent: "+path) @@ -396,12 +393,14 @@ func TestRendererErrorOnConflictingFile(t *testing.T) { r := renderer{ skipPatterns: []string{}, - files: []*inMemoryFile{ - { - root: tmpDir, - relPath: "a", + files: []file{ + &inMemoryFile{ + fileCommon: &fileCommon{ + root: tmpDir, + relPath: "a", + perm: 0444, + }, content: []byte("123"), - perm: 0444, }, }, } @@ -421,12 +420,14 @@ func TestRendererNoErrorOnConflictingFileIfSkipped(t *testing.T) { r := renderer{ ctx: ctx, skipPatterns: []string{"a"}, - files: []*inMemoryFile{ - { - root: tmpDir, - relPath: "a", + files: []file{ + &inMemoryFile{ + fileCommon: &fileCommon{ + root: tmpDir, + relPath: "a", + perm: 0444, + }, content: []byte("123"), - perm: 0444, }, }, } @@ -436,3 +437,18 @@ func TestRendererNoErrorOnConflictingFileIfSkipped(t *testing.T) { assert.NoError(t, err) assert.Len(t, r.files, 1) } + +func TestRendererNonTemplatesAreCreatedAsCopyFiles(t *testing.T) { + ctx := context.Background() + tmpDir := t.TempDir() + + r, err := newRenderer(ctx, nil, "./testdata/copy-file-walk/template", "./testdata/copy-file-walk/library", tmpDir) + require.NoError(t, err) + + err = r.walk() + assert.NoError(t, err) + + assert.Len(t, r.files, 1) + assert.Equal(t, r.files[0].(*copyFile).srcPath, "not-a-template") + assert.Equal(t, r.files[0].Path(), filepath.Join(tmpDir, "not-a-template")) +} diff --git a/libs/template/testdata/copy-file-walk/template/not-a-template b/libs/template/testdata/copy-file-walk/template/not-a-template new file mode 100644 index 00000000000..8baef1b4abc --- /dev/null +++ b/libs/template/testdata/copy-file-walk/template/not-a-template @@ -0,0 +1 @@ +abc diff --git a/libs/template/testdata/email/template/my_email b/libs/template/testdata/email/template/my_email.tmpl similarity index 100% rename from libs/template/testdata/email/template/my_email rename to libs/template/testdata/email/template/my_email.tmpl diff --git a/libs/template/testdata/executable-bit-read/template/not-a-script b/libs/template/testdata/executable-bit-read/template/not-a-script.tmpl similarity index 100% rename from libs/template/testdata/executable-bit-read/template/not-a-script rename to libs/template/testdata/executable-bit-read/template/not-a-script.tmpl diff --git a/libs/template/testdata/executable-bit-read/template/script.sh b/libs/template/testdata/executable-bit-read/template/script.sh.tmpl similarity index 100% rename from libs/template/testdata/executable-bit-read/template/script.sh rename to libs/template/testdata/executable-bit-read/template/script.sh.tmpl diff --git a/libs/template/testdata/fail/template/hello b/libs/template/testdata/fail/template/hello.tmpl similarity index 100% rename from libs/template/testdata/fail/template/hello rename to libs/template/testdata/fail/template/hello.tmpl diff --git a/libs/template/testdata/skip-all-files-in-cwd/template/file3 b/libs/template/testdata/skip-all-files-in-cwd/template/file3.tmpl similarity index 100% rename from libs/template/testdata/skip-all-files-in-cwd/template/file3 rename to libs/template/testdata/skip-all-files-in-cwd/template/file3.tmpl diff --git a/libs/template/testdata/skip-dir-eagerly/template/dir1/file1 b/libs/template/testdata/skip-dir-eagerly/template/dir1/file1.tmpl similarity index 100% rename from libs/template/testdata/skip-dir-eagerly/template/dir1/file1 rename to libs/template/testdata/skip-dir-eagerly/template/dir1/file1.tmpl diff --git a/libs/template/testdata/skip-dir-eagerly/template/file2 b/libs/template/testdata/skip-dir-eagerly/template/file2.tmpl similarity index 100% rename from libs/template/testdata/skip-dir-eagerly/template/file2 rename to libs/template/testdata/skip-dir-eagerly/template/file2.tmpl diff --git a/libs/template/testdata/skip-is-relative/template/dir1/dir2/file3 b/libs/template/testdata/skip-is-relative/template/dir1/dir2/file3.tmpl similarity index 100% rename from libs/template/testdata/skip-is-relative/template/dir1/dir2/file3 rename to libs/template/testdata/skip-is-relative/template/dir1/dir2/file3.tmpl diff --git a/libs/template/testdata/skip-is-relative/template/dir1/file2 b/libs/template/testdata/skip-is-relative/template/dir1/file2.tmpl similarity index 100% rename from libs/template/testdata/skip-is-relative/template/dir1/file2 rename to libs/template/testdata/skip-is-relative/template/dir1/file2.tmpl diff --git a/libs/template/testdata/skip-is-relative/template/file1 b/libs/template/testdata/skip-is-relative/template/file1.tmpl similarity index 100% rename from libs/template/testdata/skip-is-relative/template/file1 rename to libs/template/testdata/skip-is-relative/template/file1.tmpl diff --git a/libs/template/testdata/skip/template/file1 b/libs/template/testdata/skip/template/file1.tmpl similarity index 100% rename from libs/template/testdata/skip/template/file1 rename to libs/template/testdata/skip/template/file1.tmpl diff --git a/libs/template/testdata/skip/template/file2 b/libs/template/testdata/skip/template/file2.tmpl similarity index 100% rename from libs/template/testdata/skip/template/file2 rename to libs/template/testdata/skip/template/file2.tmpl diff --git a/libs/template/testdata/walk/template/dir2/file4 b/libs/template/testdata/walk/template/dir2/file4.tmpl similarity index 100% rename from libs/template/testdata/walk/template/dir2/file4 rename to libs/template/testdata/walk/template/dir2/file4.tmpl