From 24a3b90713d1fa5b49422864ae95a326bc78ed16 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Thu, 15 Dec 2022 21:28:14 +0100 Subject: [PATCH] Add "default" flag to environment block (#142) If the environment is not set through command line argument or environment variable, the bundle loads either 1) the only environment, 2) the only environment with the default flag set. --- bundle/config/environment.go | 4 + .../mutator/select_default_environment.go | 54 +++++++++++++ .../select_default_environment_test.go | 79 +++++++++++++++++++ bundle/loader/loader.go | 22 ------ cmd/bundle/environment.go | 9 +-- cmd/bundle/root.go | 21 ++++- 6 files changed, 156 insertions(+), 33 deletions(-) create mode 100644 bundle/config/mutator/select_default_environment.go create mode 100644 bundle/config/mutator/select_default_environment_test.go delete mode 100644 bundle/loader/loader.go diff --git a/bundle/config/environment.go b/bundle/config/environment.go index 131c1442..b2be187d 100644 --- a/bundle/config/environment.go +++ b/bundle/config/environment.go @@ -3,6 +3,10 @@ package config // Environment defines overrides for a single environment. // This structure is recursively merged into the root configuration. type Environment struct { + // Default marks that this environment must be used if one isn't specified + // by the user (through environment variable or command line argument). + Default bool `json:"default,omitempty"` + Bundle *Bundle `json:"bundle,omitempty"` Workspace *Workspace `json:"workspace,omitempty"` diff --git a/bundle/config/mutator/select_default_environment.go b/bundle/config/mutator/select_default_environment.go new file mode 100644 index 00000000..032c4973 --- /dev/null +++ b/bundle/config/mutator/select_default_environment.go @@ -0,0 +1,54 @@ +package mutator + +import ( + "context" + "fmt" + "strings" + + "github.com/databricks/bricks/bundle" + "golang.org/x/exp/maps" +) + +type selectDefaultEnvironment struct{} + +// SelectDefaultEnvironment merges the default environment into the root configuration. +func SelectDefaultEnvironment() bundle.Mutator { + return &selectDefaultEnvironment{} +} + +func (m *selectDefaultEnvironment) Name() string { + return "SelectDefaultEnvironment" +} + +func (m *selectDefaultEnvironment) Apply(_ context.Context, b *bundle.Bundle) ([]bundle.Mutator, error) { + if len(b.Config.Environments) == 0 { + return nil, fmt.Errorf("no environments defined") + } + + // One environment means there's only one default. + names := maps.Keys(b.Config.Environments) + if len(names) == 1 { + return []bundle.Mutator{SelectEnvironment(names[0])}, nil + } + + // Multiple environments means we look for the `default` flag. + var defaults []string + for _, name := range names { + if b.Config.Environments[name].Default { + defaults = append(defaults, name) + } + } + + // It is invalid to have multiple environments with the `default` flag set. + if len(defaults) > 1 { + return nil, fmt.Errorf("multiple environments are marked as default (%s)", strings.Join(defaults, ", ")) + } + + // If no environment has the `default` flag set, ask the user to specify one. + if len(defaults) == 0 { + return nil, fmt.Errorf("please specify environment") + } + + // One default remaining. + return []bundle.Mutator{SelectEnvironment(defaults[0])}, nil +} diff --git a/bundle/config/mutator/select_default_environment_test.go b/bundle/config/mutator/select_default_environment_test.go new file mode 100644 index 00000000..9e355822 --- /dev/null +++ b/bundle/config/mutator/select_default_environment_test.go @@ -0,0 +1,79 @@ +package mutator_test + +import ( + "context" + "testing" + + "github.com/databricks/bricks/bundle" + "github.com/databricks/bricks/bundle/config" + "github.com/databricks/bricks/bundle/config/mutator" + "github.com/stretchr/testify/assert" +) + +func TestSelectDefaultEnvironmentNoEnvironments(t *testing.T) { + bundle := &bundle.Bundle{ + Config: config.Root{ + Environments: map[string]*config.Environment{}, + }, + } + _, err := mutator.SelectDefaultEnvironment().Apply(context.Background(), bundle) + assert.ErrorContains(t, err, "no environments defined") +} + +func TestSelectDefaultEnvironmentSingleEnvironments(t *testing.T) { + bundle := &bundle.Bundle{ + Config: config.Root{ + Environments: map[string]*config.Environment{ + "foo": {}, + }, + }, + } + ms, err := mutator.SelectDefaultEnvironment().Apply(context.Background(), bundle) + assert.NoError(t, err) + assert.Len(t, ms, 1) + assert.Equal(t, "SelectEnvironment(foo)", ms[0].Name()) +} + +func TestSelectDefaultEnvironmentNoDefaults(t *testing.T) { + bundle := &bundle.Bundle{ + Config: config.Root{ + Environments: map[string]*config.Environment{ + "foo": {}, + "bar": {}, + "qux": {}, + }, + }, + } + _, err := mutator.SelectDefaultEnvironment().Apply(context.Background(), bundle) + assert.ErrorContains(t, err, "please specify environment") +} + +func TestSelectDefaultEnvironmentMultipleDefaults(t *testing.T) { + bundle := &bundle.Bundle{ + Config: config.Root{ + Environments: map[string]*config.Environment{ + "foo": {Default: true}, + "bar": {Default: true}, + "qux": {Default: true}, + }, + }, + } + _, err := mutator.SelectDefaultEnvironment().Apply(context.Background(), bundle) + assert.ErrorContains(t, err, "multiple environments are marked as default") +} + +func TestSelectDefaultEnvironmentSingleDefault(t *testing.T) { + bundle := &bundle.Bundle{ + Config: config.Root{ + Environments: map[string]*config.Environment{ + "foo": {}, + "bar": {Default: true}, + "qux": {}, + }, + }, + } + ms, err := mutator.SelectDefaultEnvironment().Apply(context.Background(), bundle) + assert.NoError(t, err) + assert.Len(t, ms, 1) + assert.Equal(t, "SelectEnvironment(bar)", ms[0].Name()) +} diff --git a/bundle/loader/loader.go b/bundle/loader/loader.go deleted file mode 100644 index 29c11d0f..00000000 --- a/bundle/loader/loader.go +++ /dev/null @@ -1,22 +0,0 @@ -package loader - -import ( - "context" - - "github.com/databricks/bricks/bundle" - "github.com/databricks/bricks/bundle/config/mutator" -) - -func ConfigureForEnvironment(ctx context.Context, env string) (context.Context, error) { - b, err := bundle.LoadFromRoot() - if err != nil { - return nil, err - } - - err = bundle.Apply(ctx, b, mutator.DefaultMutatorsForEnvironment(env)) - if err != nil { - return nil, err - } - - return bundle.Context(ctx, b), nil -} diff --git a/cmd/bundle/environment.go b/cmd/bundle/environment.go index d53328f5..4eb33822 100644 --- a/cmd/bundle/environment.go +++ b/cmd/bundle/environment.go @@ -8,8 +8,6 @@ import ( const envName = "DATABRICKS_BUNDLE_ENV" -const defaultEnvironment = "default" - // getEnvironment returns the name of the environment to operate in. func getEnvironment(cmd *cobra.Command) (value string) { // The command line flag takes precedence. @@ -22,10 +20,5 @@ func getEnvironment(cmd *cobra.Command) (value string) { } // If it's not set, use the environment variable. - value = os.Getenv(envName) - if value != "" { - return - } - - return defaultEnvironment + return os.Getenv(envName) } diff --git a/cmd/bundle/root.go b/cmd/bundle/root.go index b01f56c6..b8e95948 100644 --- a/cmd/bundle/root.go +++ b/cmd/bundle/root.go @@ -1,7 +1,8 @@ package bundle import ( - "github.com/databricks/bricks/bundle/loader" + "github.com/databricks/bricks/bundle" + "github.com/databricks/bricks/bundle/config/mutator" "github.com/databricks/bricks/cmd/root" "github.com/spf13/cobra" ) @@ -15,12 +16,26 @@ var rootCmd = &cobra.Command{ // ConfigureBundle loads the bundle configuration // and configures it on the command's context. func ConfigureBundle(cmd *cobra.Command, args []string) error { - ctx, err := loader.ConfigureForEnvironment(cmd.Context(), getEnvironment(cmd)) + ctx := cmd.Context() + b, err := bundle.LoadFromRoot() if err != nil { return err } - cmd.SetContext(ctx) + ms := mutator.DefaultMutators() + env := getEnvironment(cmd) + if env == "" { + ms = append(ms, mutator.SelectDefaultEnvironment()) + } else { + ms = append(ms, mutator.SelectEnvironment(env)) + } + + err = bundle.Apply(ctx, b, ms) + if err != nil { + return err + } + + cmd.SetContext(bundle.Context(ctx, b)) return nil }