From a794490b643af03d01be581734b685a78b07f759 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Thu, 16 Jan 2025 16:42:06 +0100 Subject: [PATCH] feat: Read variables from file --- bundle/config/root.go | 16 ++++++++++ bundle/config/root_test.go | 50 ++++++++++++++++++++++++++++++ bundle/config/variable/variable.go | 8 +++-- cmd/bundle/utils/utils.go | 42 +++++++++++++++++++++++-- cmd/bundle/variables.go | 1 + 5 files changed, 112 insertions(+), 5 deletions(-) diff --git a/bundle/config/root.go b/bundle/config/root.go index 21804110a..89c9b2233 100644 --- a/bundle/config/root.go +++ b/bundle/config/root.go @@ -260,6 +260,22 @@ func (r *Root) InitializeVariables(vars []string) error { return nil } +// Initializes variables parsed from variable file +// Variables can have any type of value, including complex types +func (r *Root) InitializeAnyTypeVariables(vars map[string]any) error { + for name, val := range vars { + if _, ok := r.Variables[name]; !ok { + return fmt.Errorf("variable %s has not been defined", name) + } + + err := r.Variables[name].Set(val) + if err != nil { + return fmt.Errorf("failed to assign %s to %s: %s", val, name, err) + } + } + return nil +} + func (r *Root) Merge(other *Root) error { // Merge dynamic configuration values. return r.Mutate(func(root dyn.Value) (dyn.Value, error) { diff --git a/bundle/config/root_test.go b/bundle/config/root_test.go index 42fae49d9..27339f997 100644 --- a/bundle/config/root_test.go +++ b/bundle/config/root_test.go @@ -51,6 +51,56 @@ func TestInitializeVariables(t *testing.T) { assert.Equal(t, "456", (root.Variables["bar"].Value)) } +func TestInitializeAnyTypeVariables(t *testing.T) { + root := &Root{ + Variables: map[string]*variable.Variable{ + "string": { + Default: "default", + Description: "string variable", + }, + "int": { + Default: 0, + Description: "int variable", + }, + "complex": { + Default: []map[string]int{{"a": 1}, {"b": 2}}, + Description: "complex variable", + Type: variable.VariableTypeComplex, + }, + "unused": { + Default: "should remain default", + Description: "unused variable", + }, + }, + } + + err := root.InitializeAnyTypeVariables(map[string]any{ + "string": "value", + "int": 1, + "complex": []map[string]int{{"c": 3}}, + }) + assert.NoError(t, err) + assert.Equal(t, "value", (root.Variables["string"].Value)) + assert.Equal(t, 1, (root.Variables["int"].Value)) + assert.Equal(t, []map[string]int{{"c": 3}}, (root.Variables["complex"].Value)) +} + +func TestInitializeAnyTypeVariablesUndeclared(t *testing.T) { + root := &Root{ + Variables: map[string]*variable.Variable{ + "string": { + Default: "default", + Description: "string variable", + }, + }, + } + + err := root.InitializeAnyTypeVariables(map[string]any{ + "not_declared": "value", + }) + assert.ErrorContains(t, err, "variable not_declared has not been defined") +} + func TestInitializeVariablesWithAnEqualSignInValue(t *testing.T) { root := &Root{ Variables: map[string]*variable.Variable{ diff --git a/bundle/config/variable/variable.go b/bundle/config/variable/variable.go index 95a68cfeb..34f58f19f 100644 --- a/bundle/config/variable/variable.go +++ b/bundle/config/variable/variable.go @@ -36,9 +36,11 @@ type Variable struct { // This field stores the resolved value for the variable. The variable are // resolved in the following priority order (from highest to lowest) // - // 1. Command line flag. For example: `--var="foo=bar"` - // 2. Target variable. eg: BUNDLE_VAR_foo=bar - // 3. Default value as defined in the applicable environments block + // 1. Command line flag, one of these is used + // a. Variable value obtained from arguments, example: `--var="foo=bar"` + // b. Variable value obtained from the file, example: `--vars-file-path="/path/to/file"` + // 2. Environment variable. eg: BUNDLE_VAR_foo=bar + // 3. Default value as defined in the applicable targets block // 4. Default value defined in variable definition // 5. Throw error, since if no default value is defined, then the variable // is required diff --git a/cmd/bundle/utils/utils.go b/cmd/bundle/utils/utils.go index ce3774cf5..fca19e03e 100644 --- a/cmd/bundle/utils/utils.go +++ b/cmd/bundle/utils/utils.go @@ -2,6 +2,9 @@ package utils import ( "context" + "encoding/json" + "fmt" + "os" "github.com/databricks/cli/bundle" "github.com/databricks/cli/cmd/root" @@ -16,6 +19,30 @@ func configureVariables(cmd *cobra.Command, b *bundle.Bundle, variables []string }) } +func configureVariablesFromFile(cmd *cobra.Command, b *bundle.Bundle, filePath string) diag.Diagnostics { + return bundle.ApplyFunc(cmd.Context(), b, func(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { + f, err := os.ReadFile(filePath) + if err != nil { + return diag.FromErr(fmt.Errorf("failed to read variables file: %w", err)) + } + + vars := map[string]any{} + err = json.Unmarshal(f, &vars) + if err != nil { + return diag.FromErr(fmt.Errorf("failed to parse variables file: %w", err)) + } + + if len(vars) > 0 { + err = b.Config.InitializeAnyTypeVariables(vars) + if err != nil { + return diag.FromErr(err) + } + } + + return nil + }) +} + func ConfigureBundleWithVariables(cmd *cobra.Command) (*bundle.Bundle, diag.Diagnostics) { // Load bundle config and apply target b, diags := root.MustConfigureBundle(cmd) @@ -27,9 +54,20 @@ func ConfigureBundleWithVariables(cmd *cobra.Command) (*bundle.Bundle, diag.Diag if err != nil { return b, diag.FromErr(err) } + variableFilePath, err := cmd.Flags().GetString("vars-file-path") + if err != nil { + return b, diag.FromErr(err) + } - // Initialize variables by assigning them values passed as command line flags - diags = diags.Extend(configureVariables(cmd, b, variables)) + if len(variables) > 0 && variableFilePath != "" { + return b, diag.Errorf("cannot specify both --var and --vars-file-path flags") + } else if len(variables) > 0 { + // Initialize variables by assigning them values passed as command line flags + diags = diags.Extend(configureVariables(cmd, b, variables)) + } else if variableFilePath != "" { + // Initialize variables by loading them from a file + diags = diags.Extend(configureVariablesFromFile(cmd, b, variableFilePath)) + } return b, diags } diff --git a/cmd/bundle/variables.go b/cmd/bundle/variables.go index f8f5167ea..8251e28fa 100644 --- a/cmd/bundle/variables.go +++ b/cmd/bundle/variables.go @@ -6,4 +6,5 @@ import ( func initVariableFlag(cmd *cobra.Command) { cmd.PersistentFlags().StringSlice("var", []string{}, `set values for variables defined in bundle config. Example: --var="foo=bar"`) + cmd.PersistentFlags().String("vars-file-path", "", `file path to a JSON file containing variables. Example: --vars-file-path="/path/to/vars.json"`) }