From 9a1d908f79ea7f8825eedaf2edc915dfb862e9b4 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Fri, 27 Jan 2023 16:57:39 +0100 Subject: [PATCH] Add function to opportunistically load a bundle (#180) It is not an error if a bundle cannot be found for this category. This sets the stage for using bundle configuration in non-bundle commands. --- bundle/bundle.go | 23 +++++++++++++++-- bundle/bundle_test.go | 39 ++++++++++++++++++++++++++++ bundle/root.go | 59 +++++++++++++++++++++++++++++++------------ bundle/root_test.go | 12 ++++----- cmd/bundle/root.go | 2 +- 5 files changed, 110 insertions(+), 25 deletions(-) diff --git a/bundle/bundle.go b/bundle/bundle.go index 48891c44..02a02c06 100644 --- a/bundle/bundle.go +++ b/bundle/bundle.go @@ -41,8 +41,10 @@ func Load(path string) (*Bundle, error) { return bundle, nil } -func LoadFromRoot() (*Bundle, error) { - root, err := getRoot() +// MustLoad returns a bundle configuration. +// It returns an error if a bundle was not found or could not be loaded. +func MustLoad() (*Bundle, error) { + root, err := mustGetRoot() if err != nil { return nil, err } @@ -50,6 +52,23 @@ func LoadFromRoot() (*Bundle, error) { return Load(root) } +// TryLoad returns a bundle configuration if there is one, but doesn't fail if there isn't one. +// It returns an error if a bundle was found but could not be loaded. +// It returns a `nil` bundle if a bundle was not found. +func TryLoad() (*Bundle, error) { + root, err := tryGetRoot() + if err != nil { + return nil, err + } + + // No root is fine in this function. + if root == "" { + return nil, nil + } + + return Load(root) +} + func (b *Bundle) WorkspaceClient() *databricks.WorkspaceClient { b.clientOnce.Do(func() { var err error diff --git a/bundle/bundle_test.go b/bundle/bundle_test.go index 0aa58c0a..6d6ee022 100644 --- a/bundle/bundle_test.go +++ b/bundle/bundle_test.go @@ -39,3 +39,42 @@ func TestBundleCacheDir(t *testing.T) { assert.NoError(t, err) assert.True(t, strings.HasPrefix(cacheDir, projectDir)) } + +func TestBundleMustLoadSuccess(t *testing.T) { + t.Setenv(envBundleRoot, "./tests/basic") + b, err := MustLoad() + require.NoError(t, err) + assert.Equal(t, "./tests/basic", b.Config.Path) +} + +func TestBundleMustLoadFailureWithEnv(t *testing.T) { + t.Setenv(envBundleRoot, "./tests/doesntexist") + _, err := MustLoad() + require.Error(t, err, "not a directory") +} + +func TestBundleMustLoadFailureIfNotFound(t *testing.T) { + chdir(t, t.TempDir()) + _, err := MustLoad() + require.Error(t, err, "unable to find bundle root") +} + +func TestBundleTryLoadSuccess(t *testing.T) { + t.Setenv(envBundleRoot, "./tests/basic") + b, err := TryLoad() + require.NoError(t, err) + assert.Equal(t, "./tests/basic", b.Config.Path) +} + +func TestBundleTryLoadFailureWithEnv(t *testing.T) { + t.Setenv(envBundleRoot, "./tests/doesntexist") + _, err := TryLoad() + require.Error(t, err, "not a directory") +} + +func TestBundleTryLoadOkIfNotFound(t *testing.T) { + chdir(t, t.TempDir()) + b, err := TryLoad() + assert.NoError(t, err) + assert.Nil(t, b) +} diff --git a/bundle/root.go b/bundle/root.go index 3b4d7914..3339663e 100644 --- a/bundle/root.go +++ b/bundle/root.go @@ -10,29 +10,56 @@ import ( const envBundleRoot = "BUNDLE_ROOT" -// getRoot returns the bundle root. -// If the `BUNDLE_ROOT` environment variable is set, we assume its value -// to be a valid bundle root. Otherwise we try to find it by traversing -// the path and looking for a project configuration file. -func getRoot() (string, error) { +// getRootEnv returns the value of the `BUNDLE_ROOT` environment variable +// if it set and is a directory. If the environment variable is set but +// is not a directory, it returns an error. If the environment variable is +// not set, it returns an empty string. +func getRootEnv() (string, error) { path, ok := os.LookupEnv(envBundleRoot) - if ok { - stat, err := os.Stat(path) - if err == nil && !stat.IsDir() { - err = fmt.Errorf("not a directory") - } - if err != nil { - return "", fmt.Errorf(`invalid bundle root %s="%s": %w`, envBundleRoot, path, err) - } - return path, nil + if !ok { + return "", nil } + stat, err := os.Stat(path) + if err == nil && !stat.IsDir() { + err = fmt.Errorf("not a directory") + } + if err != nil { + return "", fmt.Errorf(`invalid bundle root %s="%s": %w`, envBundleRoot, path, err) + } + return path, nil +} + +// getRootWithTraversal returns the bundle root by traversing the filesystem +// from the working directory to the root looking for a configuration file. +func getRootWithTraversal() (string, error) { wd, err := os.Getwd() if err != nil { return "", err } - path, err = folders.FindDirWithLeaf(wd, config.FileName) + path, err := folders.FindDirWithLeaf(wd, config.FileName) if err != nil { - return "", fmt.Errorf(`unable to locate bundle root`) + return "", fmt.Errorf(`unable to locate bundle root: %s not found`, config.FileName) } return path, nil } + +// mustGetRoot returns a bundle root or an error if one cannot be found. +func mustGetRoot() (string, error) { + path, err := getRootEnv() + if path != "" || err != nil { + return path, err + } + return getRootWithTraversal() +} + +// tryGetRoot returns a bundle root or an empty string if one cannot be found. +func tryGetRoot() (string, error) { + // Note: an invalid value in the environment variable is still an error. + path, err := getRootEnv() + if path != "" || err != nil { + return path, err + } + // Note: traversal failing means the bundle root cannot be found. + path, _ = getRootWithTraversal() + return path, nil +} diff --git a/bundle/root_test.go b/bundle/root_test.go index 323a2a79..35c2ce96 100644 --- a/bundle/root_test.go +++ b/bundle/root_test.go @@ -34,7 +34,7 @@ func TestRootFromEnv(t *testing.T) { t.Setenv(envBundleRoot, dir) // It should pull the root from the environment variable. - root, err := getRoot() + root, err := mustGetRoot() require.NoError(t, err) require.Equal(t, root, dir) } @@ -44,7 +44,7 @@ func TestRootFromEnvDoesntExist(t *testing.T) { t.Setenv(envBundleRoot, filepath.Join(dir, "doesntexist")) // It should pull the root from the environment variable. - _, err := getRoot() + _, err := mustGetRoot() require.Errorf(t, err, "invalid bundle root") } @@ -56,7 +56,7 @@ func TestRootFromEnvIsFile(t *testing.T) { t.Setenv(envBundleRoot, f.Name()) // It should pull the root from the environment variable. - _, err = getRoot() + _, err = mustGetRoot() require.Errorf(t, err, "invalid bundle root") } @@ -65,7 +65,7 @@ func TestRootIfEnvIsEmpty(t *testing.T) { t.Setenv(envBundleRoot, dir) // It should pull the root from the environment variable. - _, err := getRoot() + _, err := mustGetRoot() require.Errorf(t, err, "invalid bundle root") } @@ -87,7 +87,7 @@ func TestRootLookup(t *testing.T) { // It should find the project root from $PWD. wd := chdir(t, "./a/b/c") - root, err := getRoot() + root, err := mustGetRoot() require.NoError(t, err) require.Equal(t, wd, root) } @@ -99,6 +99,6 @@ func TestRootLookupError(t *testing.T) { // It can't find a project root from a temporary directory. _ = chdir(t, t.TempDir()) - _, err := getRoot() + _, err := mustGetRoot() require.ErrorContains(t, err, "unable to locate bundle root") } diff --git a/cmd/bundle/root.go b/cmd/bundle/root.go index b8e95948..ec2715fa 100644 --- a/cmd/bundle/root.go +++ b/cmd/bundle/root.go @@ -17,7 +17,7 @@ var rootCmd = &cobra.Command{ // and configures it on the command's context. func ConfigureBundle(cmd *cobra.Command, args []string) error { ctx := cmd.Context() - b, err := bundle.LoadFromRoot() + b, err := bundle.MustLoad() if err != nil { return err }