-
Notifications
You must be signed in to change notification settings - Fork 72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reading variables from file #2171
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work, thanks!
cmd/bundle/variables.go
Outdated
@@ -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"`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's call this --var-file
, shorter and same meaning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 or --varfile, don't have to remember to put dash.
cmd/bundle/utils/utils.go
Outdated
vars := map[string]any{} | ||
err = json.Unmarshal(f, &vars) | ||
if err != nil { | ||
return diag.FromErr(fmt.Errorf("failed to parse variables file: %w", err)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you pass a file with "foo"
in there, or a list, this will return an error without suggesting what the right format is. Could you look into using jsonloader
here? It would be nice to return a diag.Error
with details suggesting what the correct format is, if the contents doesn't match what we expect.
cmd/bundle/utils/utils.go
Outdated
// 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") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That seems like unnecessary constraint? Why not process one (e.g. file first) and then the other?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC we discussed on meeting that it may cause confusion in some edge cases but with low profit:
- is it be obvious to give priority to second arg?
- since both flags might be partial - should we merge them or not
- non-obvious type mismatch between values of these flags (
--var
is always string)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it be obvious to give priority to second arg?
What do you mean by second? Simple thing would be to have fixed priority: --var takes over --varfile. Complex but better thing would be to take into account order on the command line --varfile file1 --var ... --varfile file2, but we can leave that out of scope.
since both flags might be partial - should we merge them or not
Yes. Same as we do with env and command line parameters currently, we merge them. Command line take priority of both specify the same var.
non-obvious type mismatch between values of these flags (--var is always string)
Does not matter, we do not do any type of merging or other logic based on type. We just select the value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by second?
Yeah I meant order. I'm not sure whether it's clear to user, they should follow documentation to understand this priority
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's not too hard to implement following command-line arguments order that would be great.
Otherwise fixed order is much better that complete disable --var.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So in general my concern is whether it brings value to user because the use case of using both flags might be not so common but it may add some confusion and code becomes more complex
Variable resolution might feel non-obvious already and it will add more uncertainty
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also if we add this feature now it won't be possible to remove later because of backward-compatibility but if we restrict now we can always allow later if customers ask for that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, passing --var twice for the same argument raises an error: #2176
So to be consistent with that I change my suggestion to raise an error if variable is defined in both --var and --varfile. But I still think it should be per-variable check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's discuss in the slack thread? I don't mind adding merge but at the same time I don't want logic to be too complex because we already have multiple layers of overrides and with files added we will also have some type inconsistency between values
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed offline - we can limit the scope of this PR by erroring in case both --var and --var-file is used and we'll revisit that later.
$CLI bundle validate -o json --vars-file-path=vars.json | jq .resources.jobs.job1.job_clusters[0] | ||
|
||
# variable flag | ||
$CLI bundle validate -o json --var="cluster_key=mlops_stacks-cluster" | jq .resources.jobs.job1.job_clusters[0].job_cluster_key |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about testing
- both --var and --vars-file-path?
- file not found
- file cannot be parsed
- file has variable name that is not defined
- file has variable name that is complex but default is string and vice versa
- variable is required but it's not provided in the file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
file has variable name that is not defined
file has variable name that is complex but default is string and vice versa
variable is required but it's not provided in the file
@denik Not sure about these, they are part of generic variable handling, should they be covered here? It feels like we starting to cover edge cases in place where it shouldn't be tested, WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added tests, but would be nice to hear your opinion on testing edge cases
What I don't like in that approach is that snapshot tests are very easy to add/update but at the same time hard to debug and I've seen many times how such tests became write-only thing when people didn't want to investigate the failure and just updated the snapshot
bundle/config/root.go
Outdated
// 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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: instead of assigning to _, let's give it a name and use below to call Set() on it.
bundle/config/root_test.go
Outdated
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)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does it add on top of acceptance test? I think it can be removed/merged into acceptance test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be better to also have simple localised check? It also serve showcase purpose
Acceptance test is nice because it's top-level and is really easy to add but at the same time they are harder to debug in case of failure
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added acceptance tests, let me know what you think of keeping these as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If acceptance tests cover the same functionality, I'd remove this ones, because they have non-zero maintenance cost.
bundle/config/root_test.go
Outdated
assert.Equal(t, []map[string]int{{"c": 3}}, (root.Variables["complex"].Value)) | ||
} | ||
|
||
func TestInitializeAnyTypeVariablesUndeclared(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, it would be good to see full output, so acceptance test is preferred. That shows whether you use diagnostics correctly.
bundle/config/variable/variable.go
Outdated
// 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's keep it in simple and with ordered rule:
- Command line flag. For example: `--var="foo=bar"-2
- Command line flag with json variables: --varfile
- Target variable. eg: BUNDLE_VAR_foo=bar and the test
- ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's discuss here whether we need to merge values from flags
cmd/bundle/variables.go
Outdated
@@ -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"`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 or --varfile, don't have to remember to put dash.
cmd/bundle/utils/utils.go
Outdated
"github.com/spf13/cobra" | ||
) | ||
|
||
func GetDefaultVariableFilePath(target string) string { | ||
return ".databricks/bundle/" + target + "/vars.json" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we will live with that name for centuries :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd use variable-overrides.json
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like mentioning "overrides"
Updated
|
||
=== file cannot be parsed | ||
>>> errcode $CLI bundle validate -o json --var-file=var_files/invalid_json.json | ||
Error: failed to parse variables file: error decoding JSON at :0:0: invalid character 'o' in literal false (expecting 'a') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error: failed to parse variables file:
would be very helpful to print the file name there
Error: failed to parse variables file var_files/invalid_json.json:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good suggestion! Updated
without-defaults: | ||
|
||
# see .databricks/bundle/default_target/ for variable values | ||
with-default-variable-file: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need a target here? The default variables are read for any target, right? so it's the same as without-defaults.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made separate target to avoid affecting other tests by this default
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
E.g. I put .databrick/bundle/without-defaults/variable-overrides.json
file and previous test implicitly has this additional variable sources which may lead to confusion if anything goes wrong
trace BUNDLE_VAR_cluster_key=mlops_stacks-cluster-overriden $CLI bundle validate -o json | jq $cluster_expr | ||
|
||
# title "file cannot be parsed" | ||
# trace errcode $CLI bundle validate -o json --target invalid_json | jq $cluster_expr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Temporary disabled because it has tmpDir value in output
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The expectation is that paths to test-supplied directories are replaced by $TMPDIR
and friends.
cc @denik
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean in test runner or in bash script?
trace BUNDLE_VAR_cluster_key=mlops_stacks-cluster-overriden $CLI bundle validate -o json | jq $cluster_expr | ||
|
||
# title "file cannot be parsed" | ||
# trace errcode $CLI bundle validate -o json --target invalid_json | jq $cluster_expr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The expectation is that paths to test-supplied directories are replaced by $TMPDIR
and friends.
cc @denik
|
||
return v, nil | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The value from the file won't be observed if the value already has a value.
We can deal with this in a follow-up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean in case of errors in var file? If variable has value defined already (--var flag) that it should be fine to skip defaults from file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added test case for value from target which is overriden by file because file has higher in priority list
Do we have other cases other than --var where value is defined already?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind, I thought that setting a variable in targets would set the value directly, but it sets the default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I also started to doubt and added test to make it clear
CLI: * Added text output templates for apps list and list-deployments ([#2175](#2175)). * Fix duplicate "apps" entry in help output ([#2191](#2191)). Bundles: * Allow yaml-anchors in schema ([#2200](#2200)). * Show an error when non-yaml files used in include section ([#2201](#2201)). * Set WorktreeRoot to sync root outside git repo ([#2197](#2197)). * fix: Detailed message for using source-linked deployment with file_path specified ([#2119](#2119)). * Allow using variables in enum fields ([#2199](#2199)). * Add experimental-jobs-as-code template ([#2177](#2177)). * Reading variables from file ([#2171](#2171)). * Fixed an apps message order and added output test ([#2174](#2174)). * Default to forward slash-separated paths for path translation ([#2145](#2145)). * Include a materialized copy of built-in templates ([#2146](#2146)).
CLI: * Added text output templates for apps list and list-deployments ([#2175](#2175)). * Fix duplicate "apps" entry in help output ([#2191](#2191)). Bundles: * Allow yaml-anchors in schema ([#2200](#2200)). * Show an error when non-yaml files used in include section ([#2201](#2201)). * Set WorktreeRoot to sync root outside git repo ([#2197](#2197)). * fix: Detailed message for using source-linked deployment with file_path specified ([#2119](#2119)). * Allow using variables in enum fields ([#2199](#2199)). * Add experimental-jobs-as-code template ([#2177](#2177)). * Reading variables from file ([#2171](#2171)). * Fixed an apps message order and added output test ([#2174](#2174)). * Default to forward slash-separated paths for path translation ([#2145](#2145)). * Include a materialized copy of built-in templates ([#2146](#2146)).
Changes
New source of default values for variables - variable file
.databricks/bundle/<target>/variable-overrides.json
CLI tries to stat and read that file every time during variable initialisation phase
Tests
Acceptance tests