mirror of https://github.com/databricks/cli.git
using stdlib walk and added passing file bits
This commit is contained in:
parent
74627160cb
commit
4ce4b3aea1
|
@ -2,6 +2,7 @@ package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -10,9 +11,10 @@ import (
|
||||||
|
|
||||||
// Executes the template by applying config on it. Returns the materialized config
|
// Executes the template by applying config on it. Returns the materialized config
|
||||||
// as a string
|
// as a string
|
||||||
func executeTemplate(config map[string]any, templateDefination string) (string, error) {
|
// TODO: test this function
|
||||||
|
func executeTemplate(config map[string]any, templateDefinition string) (string, error) {
|
||||||
// configure template with helper functions
|
// configure template with helper functions
|
||||||
tmpl, err := template.New("").Funcs(HelperFuncs).Parse(templateDefination)
|
tmpl, err := template.New("").Funcs(HelperFuncs).Parse(templateDefinition)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -26,7 +28,8 @@ func executeTemplate(config map[string]any, templateDefination string) (string,
|
||||||
return result.String(), nil
|
return result.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateFile(config map[string]any, pathTemplate, contentTemplate string) error {
|
// TODO: test this function
|
||||||
|
func generateFile(config map[string]any, pathTemplate, contentTemplate string, perm fs.FileMode) error {
|
||||||
// compute file content
|
// compute file content
|
||||||
fileContent, err := executeTemplate(config, contentTemplate)
|
fileContent, err := executeTemplate(config, contentTemplate)
|
||||||
if errors.Is(err, errSkipThisFile) {
|
if errors.Is(err, errSkipThisFile) {
|
||||||
|
@ -50,41 +53,41 @@ func generateFile(config map[string]any, pathTemplate, contentTemplate string) e
|
||||||
}
|
}
|
||||||
|
|
||||||
// write content to file
|
// write content to file
|
||||||
return os.WriteFile(path, []byte(fileContent), 0644)
|
return os.WriteFile(path, []byte(fileContent), perm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func walkFileTree(config map[string]any, templatePath, instancePath string) error {
|
func walkFileTree(config map[string]any, templateRoot, instanceRoot string) error {
|
||||||
entries, err := os.ReadDir(templatePath)
|
return filepath.WalkDir(templateRoot, func(path string, d fs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
for _, entry := range entries {
|
|
||||||
if entry.IsDir() {
|
|
||||||
// compute directory name
|
|
||||||
dirName, err := executeTemplate(config, entry.Name())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// recusively generate files and directories inside inside our newly generated
|
|
||||||
// directory from the template defination
|
|
||||||
err = walkFileTree(config, filepath.Join(templatePath, entry.Name()), filepath.Join(instancePath, dirName))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// case: materialize a template file with it's contents
|
|
||||||
b, err := os.ReadFile(filepath.Join(templatePath, entry.Name()))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
contentTemplate := string(b)
|
|
||||||
fileNameTemplate := entry.Name()
|
|
||||||
err = generateFile(config, filepath.Join(instancePath, fileNameTemplate), contentTemplate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil
|
// skip if current entry is a directory
|
||||||
|
if d.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// read template file to get the templatized content for the file
|
||||||
|
b, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
contentTemplate := string(b)
|
||||||
|
|
||||||
|
// get relative path to the template file, This forms the template for the
|
||||||
|
// path to the file
|
||||||
|
relPathTemplate, err := filepath.Rel(templateRoot, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get info about the template file. Used to ensure instance path also
|
||||||
|
// has the same permission bits
|
||||||
|
info, err := d.Info()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return generateFile(config, filepath.Join(instanceRoot, relPathTemplate), contentTemplate, info.Mode().Perm())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,9 @@ const ConfigFileName = "config.json"
|
||||||
const schemaFileName = "schema.json"
|
const schemaFileName = "schema.json"
|
||||||
const templateDirName = "template"
|
const templateDirName = "template"
|
||||||
|
|
||||||
func Materialize(templatePath, instancePath, configPath string) error {
|
func Materialize(templateRoot, instanceRoot, configPath string) error {
|
||||||
// read the file containing schema for template input parameters
|
// read the file containing schema for template input parameters
|
||||||
schema, err := ReadSchema(filepath.Join(templatePath, schemaFileName))
|
schema, err := ReadSchema(filepath.Join(templateRoot, schemaFileName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -22,5 +22,5 @@ func Materialize(templatePath, instancePath, configPath string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// materialize the template
|
// materialize the template
|
||||||
return walkFileTree(config, filepath.Join(templatePath, templateDirName), instancePath)
|
return walkFileTree(config, filepath.Join(templateRoot, templateDirName), instanceRoot)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package template
|
package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -20,6 +21,12 @@ func setupConfig(t *testing.T, config string) string {
|
||||||
return tmp
|
return tmp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertFilePerm(t *testing.T, path string, perm fs.FileMode) {
|
||||||
|
stat, err := os.Stat(path)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, stat.Mode().Perm(), perm)
|
||||||
|
}
|
||||||
|
|
||||||
func TestMaterializeEmptyDirsAreNotGenerated(t *testing.T) {
|
func TestMaterializeEmptyDirsAreNotGenerated(t *testing.T) {
|
||||||
tmp := setupConfig(t, `
|
tmp := setupConfig(t, `
|
||||||
{
|
{
|
||||||
|
@ -51,3 +58,67 @@ func TestMaterializeEmptyDirsAreNotGenerated(t *testing.T) {
|
||||||
assert.DirExists(t, filepath.Join(tmp2, "dir-not-skipped-this-time"))
|
assert.DirExists(t, filepath.Join(tmp2, "dir-not-skipped-this-time"))
|
||||||
assert.FileExists(t, filepath.Join(tmp2, "dir-not-skipped-this-time/foo"))
|
assert.FileExists(t, filepath.Join(tmp2, "dir-not-skipped-this-time/foo"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMaterializedTemplatesHaveIdenticalFilePermissionsAsTemplate(t *testing.T) {
|
||||||
|
// create template
|
||||||
|
tmp := t.TempDir()
|
||||||
|
err := os.Mkdir(filepath.Join(tmp, "my_tmpl"), 0777)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = os.WriteFile(filepath.Join(tmp, "my_tmpl", "schema.json"), []byte(`
|
||||||
|
{
|
||||||
|
"version": 0,
|
||||||
|
"properties": {
|
||||||
|
"a": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"b": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`), 0644)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// A normal file with the executable bit not flipped
|
||||||
|
err = os.Mkdir(filepath.Join(tmp, "my_tmpl", "template"), 0777)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = os.WriteFile(filepath.Join(tmp, "my_tmpl", "template", "{{.a}}"), []byte("abc"), 0600)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// A read only file
|
||||||
|
err = os.WriteFile(filepath.Join(tmp, "my_tmpl", "template", "{{.b}}"), []byte("def"), 0400)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// A read only executable file
|
||||||
|
err = os.WriteFile(filepath.Join(tmp, "my_tmpl", "template", "foo"), []byte("ghi"), 0500)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// An executable script, accessable by non user access classes
|
||||||
|
err = os.WriteFile(filepath.Join(tmp, "my_tmpl", "template", "bar"), []byte("ghi"), 0755)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// create config.json file
|
||||||
|
err = os.Mkdir(filepath.Join(tmp, "config"), 0777)
|
||||||
|
require.NoError(t, err)
|
||||||
|
configPath := filepath.Join(tmp, "config", "config.json")
|
||||||
|
err = os.WriteFile(configPath, []byte(`
|
||||||
|
{
|
||||||
|
"a": "Amsterdam",
|
||||||
|
"b": "Hague"
|
||||||
|
}`), 0644)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// create directory to initialize the template in
|
||||||
|
instanceRoot := filepath.Join(tmp, "instance")
|
||||||
|
err = os.Mkdir(instanceRoot, 0777)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// materialize the template
|
||||||
|
err = Materialize(filepath.Join(tmp, "my_tmpl"), instanceRoot, configPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// assert template files have the correct permission bits set
|
||||||
|
assertFilePerm(t, filepath.Join(instanceRoot, "Amsterdam"), 0600)
|
||||||
|
assertFilePerm(t, filepath.Join(instanceRoot, "Hague"), 0400)
|
||||||
|
assertFilePerm(t, filepath.Join(instanceRoot, "foo"), 0500)
|
||||||
|
assertFilePerm(t, filepath.Join(instanceRoot, "bar"), 0755)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue