diff --git a/bundle/config/mutator/translate_paths.go b/bundle/config/mutator/translate_paths.go index 1e2484c7..5e016d8a 100644 --- a/bundle/config/mutator/translate_paths.go +++ b/bundle/config/mutator/translate_paths.go @@ -126,7 +126,33 @@ func (t *translateContext) rewritePath( func (t *translateContext) translateNotebookPath(literal, localFullPath, localRelPath, remotePath string) (string, error) { nb, _, err := notebook.DetectWithFS(t.b.SyncRoot, filepath.ToSlash(localRelPath)) if errors.Is(err, fs.ErrNotExist) { - return "", fmt.Errorf("notebook %s not found", literal) + if filepath.Ext(localFullPath) != notebook.ExtensionNone { + return "", fmt.Errorf("notebook %s not found", literal) + } + + extensions := []string{ + notebook.ExtensionPython, + notebook.ExtensionR, + notebook.ExtensionScala, + notebook.ExtensionSql, + notebook.ExtensionJupyter, + } + + // Check whether a file with a notebook extension already exists. This + // way we can provide a more targeted error message. + for _, ext := range extensions { + literalWithExt := literal + ext + localRelPathWithExt := filepath.ToSlash(localRelPath + ext) + if _, err := fs.Stat(t.b.SyncRoot, localRelPathWithExt); err == nil { + return "", fmt.Errorf(`notebook %s not found. Did you mean %s? +Local notebook references are expected to contain one of the following +file extensions: [%s]`, literal, literalWithExt, strings.Join(extensions, ", ")) + } + } + + // Return a generic error message if no matching possible file is found. + return "", fmt.Errorf(`notebook %s not found. Local notebook references are expected +to contain one of the following file extensions: [%s]`, literal, strings.Join(extensions, ", ")) } if err != nil { return "", fmt.Errorf("unable to determine if %s is a notebook: %w", localFullPath, err) diff --git a/bundle/config/mutator/translate_paths_test.go b/bundle/config/mutator/translate_paths_test.go index a2032f81..bf6ba15d 100644 --- a/bundle/config/mutator/translate_paths_test.go +++ b/bundle/config/mutator/translate_paths_test.go @@ -2,6 +2,7 @@ package mutator_test import ( "context" + "fmt" "os" "path/filepath" "runtime" @@ -508,6 +509,59 @@ func TestPipelineNotebookDoesNotExistError(t *testing.T) { assert.EqualError(t, diags.Error(), "notebook ./doesnt_exist.py not found") } +func TestPipelineNotebookDoesNotExistErrorWithoutExtension(t *testing.T) { + for _, ext := range []string{ + ".py", + ".r", + ".scala", + ".sql", + ".ipynb", + "", + } { + t.Run("case_"+ext, func(t *testing.T) { + dir := t.TempDir() + + if ext != "" { + touchEmptyFile(t, filepath.Join(dir, "foo"+ext)) + } + + b := &bundle.Bundle{ + SyncRootPath: dir, + SyncRoot: vfs.MustNew(dir), + Config: config.Root{ + Resources: config.Resources{ + Pipelines: map[string]*resources.Pipeline{ + "pipeline": { + PipelineSpec: &pipelines.PipelineSpec{ + Libraries: []pipelines.PipelineLibrary{ + { + Notebook: &pipelines.NotebookLibrary{ + Path: "./foo", + }, + }, + }, + }, + }, + }, + }, + }, + } + + bundletest.SetLocation(b, ".", []dyn.Location{{File: filepath.Join(dir, "fake.yml")}}) + diags := bundle.Apply(context.Background(), b, mutator.TranslatePaths()) + + if ext == "" { + assert.EqualError(t, diags.Error(), `notebook ./foo not found. Local notebook references are expected +to contain one of the following file extensions: [.py, .r, .scala, .sql, .ipynb]`) + } else { + assert.EqualError(t, diags.Error(), fmt.Sprintf(`notebook ./foo not found. Did you mean ./foo%s? +Local notebook references are expected to contain one of the following +file extensions: [.py, .r, .scala, .sql, .ipynb]`, ext)) + } + }) + } +} + func TestPipelineFileDoesNotExistError(t *testing.T) { dir := t.TempDir()