diff --git a/internal/init_test.go b/internal/init_test.go new file mode 100644 index 00000000..a2eda983 --- /dev/null +++ b/internal/init_test.go @@ -0,0 +1,15 @@ +package internal + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAccBundleInitErrorOnUnknownFields(t *testing.T) { + t.Log(GetEnvOrSkipTest(t, "CLOUD_ENV")) + + tmpDir := t.TempDir() + _, _, err := RequireErrorRun(t, "bundle", "init", "./testdata/init/field-does-not-exist", "--output-dir", tmpDir) + assert.EqualError(t, err, "failed to compute file content for bar.tmpl. variable \"does_not_exist\" not defined") +} diff --git a/internal/testdata/init/field-does-not-exist/databricks_template_schema.json b/internal/testdata/init/field-does-not-exist/databricks_template_schema.json new file mode 100644 index 00000000..c37fc089 --- /dev/null +++ b/internal/testdata/init/field-does-not-exist/databricks_template_schema.json @@ -0,0 +1,8 @@ +{ + "properties": { + "foo": { + "type": "string", + "default": "abc" + } + } +} diff --git a/internal/testdata/init/field-does-not-exist/template/bar.tmpl b/internal/testdata/init/field-does-not-exist/template/bar.tmpl new file mode 100644 index 00000000..95f8d250 --- /dev/null +++ b/internal/testdata/init/field-does-not-exist/template/bar.tmpl @@ -0,0 +1,3 @@ +{{.foo}} +{{.does_not_exist}} +hello, world diff --git a/libs/template/renderer.go b/libs/template/renderer.go index f674ea0f..09ccc3f5 100644 --- a/libs/template/renderer.go +++ b/libs/template/renderer.go @@ -8,6 +8,7 @@ import ( "os" "path" "path/filepath" + "regexp" "slices" "sort" "strings" @@ -102,6 +103,12 @@ func (r *renderer) executeTemplate(templateDefinition string) (string, error) { return "", err } + // The template execution will error instead of printing on unknown + // map keys if the "missingkey=error" option is set. + // We do this here instead of doing this once for r.baseTemplate because + // the Template.Clone() method does not clone options. + tmpl = tmpl.Option("missingkey=error") + // Parse the template text tmpl, err = tmpl.Parse(templateDefinition) if err != nil { @@ -112,6 +119,20 @@ func (r *renderer) executeTemplate(templateDefinition string) (string, error) { result := strings.Builder{} err = tmpl.Execute(&result, r.config) if err != nil { + // Parse and return a more readable error for missing values that are used + // by the template definition but are not provided in the passed config. + target := &template.ExecError{} + if errors.As(err, target) { + captureRegex := regexp.MustCompile(`map has no entry for key "(.*)"`) + matches := captureRegex.FindStringSubmatch(target.Err.Error()) + if len(matches) != 2 { + return "", err + } + return "", template.ExecError{ + Name: target.Name, + Err: fmt.Errorf("variable %q not defined", matches[1]), + } + } return "", err } return result.String(), nil diff --git a/libs/template/renderer_test.go b/libs/template/renderer_test.go index 21dd1e4f..8f8a8291 100644 --- a/libs/template/renderer_test.go +++ b/libs/template/renderer_test.go @@ -189,6 +189,18 @@ My email is {{template "email"}} assert.Contains(t, statement, `My email is hrithik.roshan@databricks.com`) } +func TestRendererExecuteTemplateWithUnknownProperty(t *testing.T) { + templateText := `{{.does_not_exist}}` + + r := renderer{ + config: map[string]any{}, + baseTemplate: template.New("base"), + } + + _, err := r.executeTemplate(templateText) + assert.ErrorContains(t, err, "variable \"does_not_exist\" not defined") +} + func TestRendererIsSkipped(t *testing.T) { skipPatterns := []string{"a*", "*yz", "def", "a/b/*"}