mirror of https://github.com/databricks/cli.git
add support for library
This commit is contained in:
parent
95391f792c
commit
2486a96cd7
|
@ -0,0 +1,6 @@
|
||||||
|
{{define "email"}}shreyas.goenka@databricks.com{{end}}
|
||||||
|
{{define "get_host"}}
|
||||||
|
{{ with urlParse . }}{{-
|
||||||
|
print .Scheme `://` .Host
|
||||||
|
-}}{{end}}
|
||||||
|
{{end}}
|
|
@ -2,3 +2,6 @@ This file should only be generated for Azure
|
||||||
{{if ne .cloud_type "Azure"}}
|
{{if ne .cloud_type "Azure"}}
|
||||||
{{skipThisFile}}
|
{{skipThisFile}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{ template "email" }}
|
||||||
|
{{ template "get_host" "https://adb-xxxx.xx.azuredatabricks.net/sql/queries" }}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -9,39 +10,63 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Executes the template by applying config on it. Returns the materialized config
|
type renderer struct {
|
||||||
|
config map[string]any
|
||||||
|
|
||||||
|
baseTemplate *template.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRenderer(config map[string]any, libraryRoot string) (*renderer, error) {
|
||||||
|
tmpl, err := template.New("").Funcs(HelperFuncs).ParseGlob(filepath.Join(libraryRoot, "*"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &renderer{
|
||||||
|
config: config,
|
||||||
|
baseTemplate: tmpl,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executes the template by applying config on it. Returns the materialized template
|
||||||
// as a string
|
// as a string
|
||||||
func executeTemplate(config map[string]any, templateDefinition string) (string, error) {
|
func (r *renderer) executeTemplate(templateDefinition string) (string, error) {
|
||||||
// configure template with helper functions
|
// Create copy of base template so as to not overwrite it
|
||||||
tmpl, err := template.New("").Funcs(HelperFuncs).Parse(templateDefinition)
|
tmpl, err := r.baseTemplate.Clone()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// execute template
|
// Parse the template text
|
||||||
|
tmpl, err = tmpl.Parse(templateDefinition)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute template and get result
|
||||||
result := strings.Builder{}
|
result := strings.Builder{}
|
||||||
err = tmpl.Execute(&result, config)
|
err = tmpl.Execute(&result, r.config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return result.String(), nil
|
return result.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateFile(config map[string]any, pathTemplate, contentTemplate string, perm fs.FileMode) error {
|
func (r *renderer) generateFile(pathTemplate, contentTemplate string, perm fs.FileMode) error {
|
||||||
// compute file content
|
// compute file content
|
||||||
fileContent, err := executeTemplate(config, contentTemplate)
|
fileContent, err := r.executeTemplate(contentTemplate)
|
||||||
if errors.Is(err, errSkipThisFile) {
|
if errors.Is(err, errSkipThisFile) {
|
||||||
// skip this file
|
// skip this file
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("failed to compute file content for %s. %w", pathTemplate, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// compute the path for this file
|
// compute the path for this file
|
||||||
path, err := executeTemplate(config, pathTemplate)
|
path, err := r.executeTemplate(pathTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("failed to compute path for %s. %w", pathTemplate, err)
|
||||||
}
|
}
|
||||||
// create any intermediate directories required. Directories are lazily generated
|
// create any intermediate directories required. Directories are lazily generated
|
||||||
// only when they are required for a file.
|
// only when they are required for a file.
|
||||||
|
@ -55,7 +80,7 @@ func generateFile(config map[string]any, pathTemplate, contentTemplate string, p
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use local filer client for this function. https://github.com/databricks/cli/issues/511
|
// TODO: use local filer client for this function. https://github.com/databricks/cli/issues/511
|
||||||
func walkFileTree(config map[string]any, templateRoot, instanceRoot string) error {
|
func walkFileTree(r *renderer, templateRoot, instanceRoot string) error {
|
||||||
return filepath.WalkDir(templateRoot, func(path string, d fs.DirEntry, err error) error {
|
return filepath.WalkDir(templateRoot, func(path string, d fs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -87,6 +112,6 @@ func walkFileTree(config map[string]any, templateRoot, instanceRoot string) erro
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return generateFile(config, filepath.Join(instanceRoot, relPathTemplate), contentTemplate, info.Mode().Perm())
|
return r.generateFile(filepath.Join(instanceRoot, relPathTemplate), contentTemplate, info.Mode().Perm())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -17,22 +18,40 @@ Sheep wool is the best!
|
||||||
{{else}}
|
{{else}}
|
||||||
{{.Animal}} wool is not too bad...
|
{{.Animal}} wool is not too bad...
|
||||||
{{end}}
|
{{end}}
|
||||||
|
My email is {{template "email"}}
|
||||||
`
|
`
|
||||||
statement, err := executeTemplate(map[string]any{
|
|
||||||
|
r := renderer{
|
||||||
|
config: map[string]any{
|
||||||
"Material": "wool",
|
"Material": "wool",
|
||||||
"count": 1,
|
"count": 1,
|
||||||
"Animal": "sheep",
|
"Animal": "sheep",
|
||||||
}, templateText)
|
},
|
||||||
require.NoError(t, err)
|
baseTemplate: template.Must(template.New("base").Parse(`{{define "email"}}shreyas.goenka@databricks.com{{end}}`)),
|
||||||
assert.Equal(t, "\"1 items are made of wool\".\n\nSheep wool is the best!\n\n", statement)
|
}
|
||||||
|
|
||||||
statement, err = executeTemplate(map[string]any{
|
statement, err := r.executeTemplate(templateText)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, statement, `"1 items are made of wool"`)
|
||||||
|
assert.NotContains(t, statement, `cat wool is not too bad.."`)
|
||||||
|
assert.Contains(t, statement, "Sheep wool is the best!")
|
||||||
|
assert.Contains(t, statement, `My email is shreyas.goenka@databricks.com`)
|
||||||
|
|
||||||
|
r = renderer{
|
||||||
|
config: map[string]any{
|
||||||
"Material": "wool",
|
"Material": "wool",
|
||||||
"count": 1,
|
"count": 1,
|
||||||
"Animal": "cat",
|
"Animal": "cat",
|
||||||
}, templateText)
|
},
|
||||||
|
baseTemplate: template.Must(template.New("base").Parse(`{{define "email"}}hrithik.roshan@databricks.com{{end}}`)),
|
||||||
|
}
|
||||||
|
|
||||||
|
statement, err = r.executeTemplate(templateText)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "\"1 items are made of wool\".\n\ncat wool is not too bad...\n\n", statement)
|
assert.Contains(t, statement, `"1 items are made of wool"`)
|
||||||
|
assert.Contains(t, statement, `cat wool is not too bad...`)
|
||||||
|
assert.NotContains(t, statement, "Sheep wool is the best!")
|
||||||
|
assert.Contains(t, statement, `My email is hrithik.roshan@databricks.com`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateFile(t *testing.T) {
|
func TestGenerateFile(t *testing.T) {
|
||||||
|
@ -46,11 +65,16 @@ func TestGenerateFile(t *testing.T) {
|
||||||
{{.Animal}} wool is not too bad...
|
{{.Animal}} wool is not too bad...
|
||||||
{{end}}
|
{{end}}
|
||||||
`
|
`
|
||||||
err := generateFile(map[string]any{
|
|
||||||
|
r := renderer{
|
||||||
|
config: map[string]any{
|
||||||
"Material": "wool",
|
"Material": "wool",
|
||||||
"count": 1,
|
"count": 1,
|
||||||
"Animal": "cat",
|
"Animal": "cat",
|
||||||
}, pathTemplate, contentTemplate, 0444)
|
},
|
||||||
|
baseTemplate: template.New("base"),
|
||||||
|
}
|
||||||
|
err := r.generateFile(pathTemplate, contentTemplate, 0444)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// assert file exists
|
// assert file exists
|
||||||
|
|
|
@ -2,6 +2,7 @@ package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/url"
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,4 +14,7 @@ var HelperFuncs = template.FuncMap{
|
||||||
"skipThisFile": func() (any, error) {
|
"skipThisFile": func() (any, error) {
|
||||||
return nil, errSkipThisFile
|
return nil, errSkipThisFile
|
||||||
},
|
},
|
||||||
|
"urlParse": func(rawUrl string) (*url.URL, error) {
|
||||||
|
return url.Parse(rawUrl)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
const schemaFileName = "databricks_template_schema.json"
|
const schemaFileName = "databricks_template_schema.json"
|
||||||
const templateDirName = "template"
|
const templateDirName = "template"
|
||||||
|
const libraryDirName = "library"
|
||||||
|
|
||||||
func Materialize(templateRoot, instanceRoot, 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
|
||||||
|
@ -14,12 +15,17 @@ func Materialize(templateRoot, instanceRoot, configPath string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// read user config to initalize the template with
|
// read user config to initialize the template with
|
||||||
config, err := schema.ReadConfig(configPath)
|
config, err := schema.ReadConfig(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r, err := newRenderer(config, filepath.Join(templateRoot, libraryDirName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// materialize the template
|
// materialize the template
|
||||||
return walkFileTree(config, filepath.Join(templateRoot, templateDirName), instanceRoot)
|
return walkFileTree(r, filepath.Join(templateRoot, templateDirName), instanceRoot)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue