Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .nextchanges/bundles/warn-for-missing-databricks-yml.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Provide an actionable error message if databricks.yml is missing or DATABRICKS_BUNDLE_ROOT is invalid ([#5953](https://github.com/databricks/cli/pull/5953)).
1 change: 1 addition & 0 deletions acceptance/bundle/root/env-not-a-directory/notadir.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This file exists so DATABRICKS_BUNDLE_ROOT can point at a non-directory.
3 changes: 3 additions & 0 deletions acceptance/bundle/root/env-not-a-directory/out.test.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions acceptance/bundle/root/env-not-a-directory/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

>>> DATABRICKS_BUNDLE_ROOT=notadir.txt [CLI] bundle validate
Error: Invalid bundle root DATABRICKS_BUNDLE_ROOT="notadir.txt": not a directory

The DATABRICKS_BUNDLE_ROOT environment variable must point to an existing directory that contains a bundle configuration file.

Found 1 error
2 changes: 2 additions & 0 deletions acceptance/bundle/root/env-not-a-directory/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# DATABRICKS_BUNDLE_ROOT points at a file rather than a directory.
musterr trace DATABRICKS_BUNDLE_ROOT=notadir.txt $CLI bundle validate
3 changes: 3 additions & 0 deletions acceptance/bundle/root/env-not-found/out.test.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions acceptance/bundle/root/env-not-found/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

>>> DATABRICKS_BUNDLE_ROOT=doesnotexist [CLI] bundle validate
Error: Invalid bundle root DATABRICKS_BUNDLE_ROOT="doesnotexist": stat doesnotexist: no such file or directory

The DATABRICKS_BUNDLE_ROOT environment variable must point to an existing directory that contains a bundle configuration file.

Found 1 error
2 changes: 2 additions & 0 deletions acceptance/bundle/root/env-not-found/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# DATABRICKS_BUNDLE_ROOT points at a path that does not exist: os.Stat fails.
musterr trace DATABRICKS_BUNDLE_ROOT=doesnotexist $CLI bundle validate
4 changes: 4 additions & 0 deletions acceptance/bundle/root/env-not-found/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Normalize the os.Stat error message from Windows.
[[Repls]]
Old = "GetFileAttributesEx doesnotexist: The system cannot find the file specified."
New = "stat doesnotexist: no such file or directory"
3 changes: 3 additions & 0 deletions acceptance/bundle/root/not-found/out.test.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions acceptance/bundle/root/not-found/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

>>> [CLI] bundle validate
Error: Unable to locate the bundle root: databricks.yml not found.

Run this command from a directory that contains a bundle, or set the DATABRICKS_BUNDLE_ROOT environment variable to the bundle root directory.
Alternatively, create a new bundle with 'databricks bundle init'.

Found 1 error
2 changes: 2 additions & 0 deletions acceptance/bundle/root/not-found/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# No databricks.yml in this directory or any parent: getRootWithTraversal fails.
musterr trace $CLI bundle validate
3 changes: 3 additions & 0 deletions acceptance/bundle/root/real-empty-dir/out.test.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions acceptance/bundle/root/real-empty-dir/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

>>> DATABRICKS_BUNDLE_ROOT=emptydir [CLI] bundle validate
Error: Invalid bundle root DATABRICKS_BUNDLE_ROOT="emptydir": databricks.yml not found

The DATABRICKS_BUNDLE_ROOT environment variable must point to an existing directory that contains a bundle configuration file.

Found 1 error
3 changes: 3 additions & 0 deletions acceptance/bundle/root/real-empty-dir/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# DATABRICKS_BUNDLE_ROOT points at an existing directory that has no databricks.yml.
mkdir emptydir
musterr trace DATABRICKS_BUNDLE_ROOT=emptydir $CLI bundle validate
1 change: 1 addition & 0 deletions acceptance/bundle/root/real-empty-dir/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ignore = ["emptydir"]
6 changes: 6 additions & 0 deletions acceptance/bundle/root/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Local = true
Cloud = false

# These errors originate in bundle/root.go before any engine is selected,
# so they are identical for both deployment engines.
EnvMatrix.DATABRICKS_BUNDLE_ENGINE = []
5 changes: 4 additions & 1 deletion acceptance/bundle/run/inline-script/no-bundle/output.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@

>>> [CLI] bundle run -- echo hello
Error: unable to locate bundle root: databricks.yml not found
Error: Unable to locate the bundle root: databricks.yml not found.

Run this command from a directory that contains a bundle, or set the DATABRICKS_BUNDLE_ROOT environment variable to the bundle root directory.
Alternatively, create a new bundle with 'databricks bundle init'.


Exit code: 1
5 changes: 4 additions & 1 deletion acceptance/bundle/run/inline-script/no-separator/output.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@

>>> [CLI] bundle run echo hello
Error: unable to locate bundle root: databricks.yml not found
Error: Unable to locate the bundle root: databricks.yml not found.

Run this command from a directory that contains a bundle, or set the DATABRICKS_BUNDLE_ROOT environment variable to the bundle root directory.
Alternatively, create a new bundle with 'databricks bundle init'.


Exit code: 1
16 changes: 10 additions & 6 deletions bundle/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,11 @@ func Load(ctx context.Context, path string) (*Bundle, error) {
// MustLoad returns a bundle configuration.
// The errors are recorded by logdiag, check with logdiag.HasError().
func MustLoad(ctx context.Context) *Bundle {
root, err := mustGetRoot(ctx)
if err != nil {
logdiag.LogError(ctx, err)
root, diags := mustGetRoot(ctx)
if diags.HasError() {
for _, d := range diags {
logdiag.LogDiag(ctx, d)
}
return nil
}

Expand All @@ -217,9 +219,11 @@ func MustLoad(ctx context.Context) *Bundle {
// The errors are recorded by logdiag, check with logdiag.HasError().
// It returns a `nil` bundle if a bundle was not found.
func TryLoad(ctx context.Context) *Bundle {
root, err := tryGetRoot(ctx)
if err != nil {
logdiag.LogError(ctx, err)
root, diags := tryGetRoot(ctx)
if diags.HasError() {
for _, d := range diags {
logdiag.LogDiag(ctx, d)
}
return nil
}

Expand Down
9 changes: 4 additions & 5 deletions bundle/bundle_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package bundle

import (
"io/fs"
"os"
"path/filepath"
"testing"
Expand Down Expand Up @@ -38,7 +37,7 @@ func tryLoad(t *testing.T) (*Bundle, []diag.Diagnostic) {

func TestLoadNotExists(t *testing.T) {
b, err := Load(t.Context(), "/doesntexist")
assert.ErrorIs(t, err, fs.ErrNotExist)
assert.ErrorContains(t, err, "databricks.yml not found")
assert.Nil(t, b)
}

Expand Down Expand Up @@ -110,7 +109,7 @@ func TestBundleMustLoadFailureWithEnv(t *testing.T) {
b, diags := mustLoad(t)
require.Nil(t, b)
require.Len(t, diags, 1, "expected diagnostics")
assert.Contains(t, diags[0].Summary, "invalid bundle root")
assert.Contains(t, diags[0].Summary, "Invalid bundle root")
assert.Equal(t, diag.Error, diags[0].Severity)
}

Expand All @@ -119,7 +118,7 @@ func TestBundleMustLoadFailureIfNotFound(t *testing.T) {
b, diags := mustLoad(t)
require.Nil(t, b)
require.Len(t, diags, 1, "expected diagnostics")
assert.Contains(t, diags[0].Summary, "unable to locate bundle root")
assert.Contains(t, diags[0].Summary, "Unable to locate the bundle root")
assert.Equal(t, diag.Error, diags[0].Severity)
}

Expand All @@ -136,7 +135,7 @@ func TestBundleTryLoadFailureWithEnv(t *testing.T) {
b, diags := tryLoad(t)
require.Nil(t, b)
require.Len(t, diags, 1, "expected diagnostics")
assert.Contains(t, diags[0].Summary, "invalid bundle root")
assert.Contains(t, diags[0].Summary, "Invalid bundle root")
assert.Equal(t, diag.Error, diags[0].Severity)
}

Expand Down
7 changes: 1 addition & 6 deletions bundle/config/filename.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ var FileNames = ConfigFileNames{

func (c ConfigFileNames) FindInPath(path string) (string, error) {
result := ""
var firstErr error

for _, file := range c {
filePath := filepath.Join(path, file)
Expand All @@ -28,15 +27,11 @@ func (c ConfigFileNames) FindInPath(path string) (string, error) {
return "", fmt.Errorf("multiple bundle root configuration files found in %s", path)
}
result = filePath
} else {
if firstErr == nil {
firstErr = err
}
}
}

if result == "" {
return "", firstErr
return "", fmt.Errorf("%s not found", c[0])
}

return result, nil
Expand Down
7 changes: 1 addition & 6 deletions bundle/config/filename_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package config
import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"

Expand Down Expand Up @@ -39,14 +38,10 @@ func TestConfigFileNames_FindInPath(t *testing.T) {
name: "file not found",
files: []string{},
expected: "",
err: "no such file or directory",
err: "databricks.yml not found",
},
}

if runtime.GOOS == "windows" {
testCases[3].err = "The system cannot find the file specified."
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
projectDir := t.TempDir()
Expand Down
47 changes: 30 additions & 17 deletions bundle/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ import (

"github.com/databricks/cli/bundle/config"
"github.com/databricks/cli/bundle/env"
"github.com/databricks/cli/libs/diag"
"github.com/databricks/cli/libs/folders"
)

// 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(ctx context.Context) (string, error) {
// if it is set and points to a directory that contains a bundle configuration
// file. If the environment variable is set but does not point to such a
// directory, it returns diagnostics. If the environment variable is not set,
// it returns an empty string.
func getRootEnv(ctx context.Context) (string, diag.Diagnostics) {
path, ok := env.Root(ctx)
if !ok {
return "", nil
Expand All @@ -24,18 +26,25 @@ func getRootEnv(ctx context.Context) (string, error) {
if err == nil && !stat.IsDir() {
err = errors.New("not a directory")
}
if err == nil {
_, err = config.FileNames.FindInPath(path)
}
if err != nil {
return "", fmt.Errorf(`invalid bundle root %s="%s": %w`, env.RootVariable, path, err)
return "", diag.Diagnostics{{
Severity: diag.Error,
Summary: fmt.Sprintf(`Invalid bundle root %s="%s": %s`, env.RootVariable, path, err),
Detail: fmt.Sprintf("The %s environment variable must point to an existing directory that contains a bundle configuration file.", env.RootVariable),
}}
}
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) {
func getRootWithTraversal() (string, diag.Diagnostics) {
wd, err := os.Getwd()
if err != nil {
return "", err
return "", diag.FromErr(err)
}

for _, file := range config.FileNames {
Expand All @@ -45,24 +54,28 @@ func getRootWithTraversal() (string, error) {
}
}

return "", fmt.Errorf(`unable to locate bundle root: %s not found`, config.FileNames[0])
return "", diag.Diagnostics{{
Severity: diag.Error,
Summary: fmt.Sprintf("Unable to locate the bundle root: %s not found.", config.FileNames[0]),
Detail: fmt.Sprintf("Run this command from a directory that contains a bundle, or set the %s environment variable to the bundle root directory.\nAlternatively, create a new bundle with 'databricks bundle init'.", env.RootVariable),
}}
}

// mustGetRoot returns a bundle root or an error if one cannot be found.
func mustGetRoot(ctx context.Context) (string, error) {
path, err := getRootEnv(ctx)
if path != "" || err != nil {
return path, err
// mustGetRoot returns a bundle root or diagnostics if one cannot be found.
func mustGetRoot(ctx context.Context) (string, diag.Diagnostics) {
path, diags := getRootEnv(ctx)
if path != "" || diags.HasError() {
return path, diags
}
return getRootWithTraversal()
}

// tryGetRoot returns a bundle root or an empty string if one cannot be found.
func tryGetRoot(ctx context.Context) (string, error) {
func tryGetRoot(ctx context.Context) (string, diag.Diagnostics) {
// Note: an invalid value in the environment variable is still an error.
path, err := getRootEnv(ctx)
if path != "" || err != nil {
return path, err
path, diags := getRootEnv(ctx)
if path != "" || diags.HasError() {
return path, diags
}
// Note: traversal failing means the bundle root cannot be found.
path, _ = getRootWithTraversal()
Expand Down
33 changes: 21 additions & 12 deletions bundle/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@ func TestRootFromEnv(t *testing.T) {
dir := t.TempDir()
t.Setenv(env.RootVariable, dir)

// It should pull the root from the environment variable.
root, err := mustGetRoot(ctx)
// The bundle root must contain a configuration file.
f, err := os.Create(filepath.Join(dir, config.FileNames[0]))
require.NoError(t, err)
f.Close()

// It should pull the root from the environment variable.
root, diags := mustGetRoot(ctx)
require.NoError(t, diags.Error())
require.Equal(t, root, dir)
}

Expand All @@ -27,8 +32,9 @@ func TestRootFromEnvDoesntExist(t *testing.T) {
t.Setenv(env.RootVariable, filepath.Join(dir, "doesntexist"))

// It should pull the root from the environment variable.
_, err := mustGetRoot(ctx)
require.Errorf(t, err, "invalid bundle root")
_, diags := mustGetRoot(ctx)
require.True(t, diags.HasError())
require.Contains(t, diags[0].Summary, "Invalid bundle root")
}

func TestRootFromEnvIsFile(t *testing.T) {
Expand All @@ -40,8 +46,9 @@ func TestRootFromEnvIsFile(t *testing.T) {
t.Setenv(env.RootVariable, f.Name())

// It should pull the root from the environment variable.
_, err = mustGetRoot(ctx)
require.Errorf(t, err, "invalid bundle root")
_, diags := mustGetRoot(ctx)
require.True(t, diags.HasError())
require.Contains(t, diags[0].Summary, "Invalid bundle root")
}

func TestRootIfEnvIsEmpty(t *testing.T) {
Expand All @@ -50,8 +57,9 @@ func TestRootIfEnvIsEmpty(t *testing.T) {
t.Setenv(env.RootVariable, dir)

// It should pull the root from the environment variable.
_, err := mustGetRoot(ctx)
require.Errorf(t, err, "invalid bundle root")
_, diags := mustGetRoot(ctx)
require.True(t, diags.HasError())
require.Contains(t, diags[0].Summary, "Invalid bundle root")
}

func TestRootLookup(t *testing.T) {
Expand Down Expand Up @@ -82,8 +90,8 @@ func TestRootLookup(t *testing.T) {

// It should find the project root from $PWD.
t.Chdir("./a/b/c")
foundRoot, err := mustGetRoot(ctx)
require.NoError(t, err)
foundRoot, diags := mustGetRoot(ctx)
require.NoError(t, diags.Error())
foundRoot, err = filepath.EvalSymlinks(foundRoot)
require.NoError(t, err)
require.Equal(t, root, foundRoot)
Expand All @@ -98,6 +106,7 @@ func TestRootLookupError(t *testing.T) {

// It can't find a project root from a temporary directory.
t.Chdir(t.TempDir())
_, err := mustGetRoot(ctx)
require.ErrorContains(t, err, "unable to locate bundle root")
_, diags := mustGetRoot(ctx)
require.True(t, diags.HasError())
require.Contains(t, diags[0].Summary, "Unable to locate the bundle root")
}
Loading