2023-04-05 14:02:17 +00:00
|
|
|
package mutator
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
2023-05-16 16:35:39 +00:00
|
|
|
"github.com/databricks/cli/bundle"
|
|
|
|
"github.com/databricks/cli/libs/notebook"
|
2023-04-05 14:02:17 +00:00
|
|
|
"github.com/databricks/databricks-sdk-go/service/jobs"
|
|
|
|
"github.com/databricks/databricks-sdk-go/service/pipelines"
|
|
|
|
)
|
|
|
|
|
|
|
|
type translatePaths struct {
|
2023-04-12 14:17:13 +00:00
|
|
|
seen map[string]string
|
2023-04-05 14:02:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TranslatePaths converts paths to local notebook files into paths in the workspace file system.
|
|
|
|
func TranslatePaths() bundle.Mutator {
|
|
|
|
return &translatePaths{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *translatePaths) Name() string {
|
|
|
|
return "TranslatePaths"
|
|
|
|
}
|
|
|
|
|
2023-04-12 14:17:13 +00:00
|
|
|
// rewritePath converts a given relative path to a stable remote workspace path.
|
|
|
|
//
|
|
|
|
// It takes these arguments:
|
|
|
|
// - The argument `dir` is the directory relative to which the given relative path is.
|
|
|
|
// - The given relative path is both passed and written back through `*p`.
|
|
|
|
// - The argument `fn` is a function that performs the actual rewriting logic.
|
|
|
|
// This logic is different between regular files or notebooks.
|
|
|
|
//
|
|
|
|
// The function returns an error if it is impossible to rewrite the given relative path.
|
|
|
|
func (m *translatePaths) rewritePath(
|
|
|
|
dir string,
|
|
|
|
b *bundle.Bundle,
|
|
|
|
p *string,
|
|
|
|
fn func(literal, localPath, remotePath string) (string, error),
|
|
|
|
) error {
|
2023-04-05 14:02:17 +00:00
|
|
|
// We assume absolute paths point to a location in the workspace
|
2023-04-12 14:17:13 +00:00
|
|
|
if path.IsAbs(filepath.ToSlash(*p)) {
|
2023-04-05 14:02:17 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-04-12 14:17:13 +00:00
|
|
|
// Local path is relative to the directory the resource was defined in.
|
|
|
|
localPath := filepath.Join(dir, filepath.FromSlash(*p))
|
|
|
|
if interp, ok := m.seen[localPath]; ok {
|
2023-04-05 14:02:17 +00:00
|
|
|
*p = interp
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-04-12 14:17:13 +00:00
|
|
|
// Remote path must be relative to the bundle root.
|
|
|
|
remotePath, err := filepath.Rel(b.Config.Path, localPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(remotePath, "..") {
|
|
|
|
return fmt.Errorf("path %s is not contained in bundle root path", localPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prefix remote path with its remote root path.
|
2023-04-12 14:54:36 +00:00
|
|
|
remotePath = path.Join(b.Config.Workspace.FilesPath, filepath.ToSlash(remotePath))
|
2023-04-12 14:17:13 +00:00
|
|
|
|
2023-04-05 14:02:17 +00:00
|
|
|
// Convert local path into workspace path via specified function.
|
2023-04-12 14:17:13 +00:00
|
|
|
interp, err := fn(*p, localPath, filepath.ToSlash(remotePath))
|
2023-04-05 14:02:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
*p = interp
|
2023-04-12 14:17:13 +00:00
|
|
|
m.seen[localPath] = interp
|
2023-04-05 14:02:17 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-04-12 14:17:13 +00:00
|
|
|
func (m *translatePaths) translateNotebookPath(literal, localPath, remotePath string) (string, error) {
|
|
|
|
nb, _, err := notebook.Detect(localPath)
|
2023-04-05 14:02:17 +00:00
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return "", fmt.Errorf("notebook %s not found", literal)
|
|
|
|
}
|
|
|
|
if err != nil {
|
2023-04-12 14:17:13 +00:00
|
|
|
return "", fmt.Errorf("unable to determine if %s is a notebook: %w", localPath, err)
|
2023-04-05 14:02:17 +00:00
|
|
|
}
|
|
|
|
if !nb {
|
2023-04-12 14:17:13 +00:00
|
|
|
return "", fmt.Errorf("file at %s is not a notebook", localPath)
|
2023-04-05 14:02:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Upon import, notebooks are stripped of their extension.
|
2023-04-12 14:17:13 +00:00
|
|
|
return strings.TrimSuffix(remotePath, filepath.Ext(localPath)), nil
|
2023-04-05 14:02:17 +00:00
|
|
|
}
|
|
|
|
|
2023-04-12 14:17:13 +00:00
|
|
|
func (m *translatePaths) translateFilePath(literal, localPath, remotePath string) (string, error) {
|
|
|
|
_, err := os.Stat(localPath)
|
2023-04-05 14:02:17 +00:00
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return "", fmt.Errorf("file %s not found", literal)
|
|
|
|
}
|
|
|
|
if err != nil {
|
2023-04-12 14:17:13 +00:00
|
|
|
return "", fmt.Errorf("unable to access %s: %w", localPath, err)
|
2023-04-05 14:02:17 +00:00
|
|
|
}
|
|
|
|
|
2023-04-12 14:17:13 +00:00
|
|
|
return remotePath, nil
|
2023-04-05 14:02:17 +00:00
|
|
|
}
|
|
|
|
|
2023-04-12 14:17:13 +00:00
|
|
|
func (m *translatePaths) translateJobTask(dir string, b *bundle.Bundle, task *jobs.JobTaskSettings) error {
|
2023-04-05 14:02:17 +00:00
|
|
|
var err error
|
|
|
|
|
|
|
|
if task.NotebookTask != nil {
|
2023-04-12 14:17:13 +00:00
|
|
|
err = m.rewritePath(dir, b, &task.NotebookTask.NotebookPath, m.translateNotebookPath)
|
2023-04-05 14:02:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if task.SparkPythonTask != nil {
|
2023-04-12 14:17:13 +00:00
|
|
|
err = m.rewritePath(dir, b, &task.SparkPythonTask.PythonFile, m.translateFilePath)
|
2023-04-05 14:02:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-04-12 14:17:13 +00:00
|
|
|
func (m *translatePaths) translatePipelineLibrary(dir string, b *bundle.Bundle, library *pipelines.PipelineLibrary) error {
|
2023-04-05 14:02:17 +00:00
|
|
|
var err error
|
|
|
|
|
|
|
|
if library.Notebook != nil {
|
2023-04-12 14:17:13 +00:00
|
|
|
err = m.rewritePath(dir, b, &library.Notebook.Path, m.translateNotebookPath)
|
2023-04-05 14:02:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-05 14:29:42 +00:00
|
|
|
if library.File != nil {
|
2023-04-12 14:17:13 +00:00
|
|
|
err = m.rewritePath(dir, b, &library.File.Path, m.translateFilePath)
|
2023-04-05 14:29:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-05 14:02:17 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-05-24 12:45:19 +00:00
|
|
|
func (m *translatePaths) Apply(_ context.Context, b *bundle.Bundle) error {
|
2023-04-05 14:02:17 +00:00
|
|
|
m.seen = make(map[string]string)
|
|
|
|
|
2023-04-12 14:17:13 +00:00
|
|
|
for key, job := range b.Config.Resources.Jobs {
|
|
|
|
dir, err := job.ConfigFileDirectory()
|
|
|
|
if err != nil {
|
2023-05-24 12:45:19 +00:00
|
|
|
return fmt.Errorf("unable to determine directory for job %s: %w", key, err)
|
2023-04-12 14:17:13 +00:00
|
|
|
}
|
|
|
|
|
2023-06-07 10:34:59 +00:00
|
|
|
// Do not translate job task paths if using git source
|
|
|
|
if job.GitSource != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-04-05 14:02:17 +00:00
|
|
|
for i := 0; i < len(job.Tasks); i++ {
|
2023-04-12 14:17:13 +00:00
|
|
|
err := m.translateJobTask(dir, b, &job.Tasks[i])
|
2023-04-05 14:02:17 +00:00
|
|
|
if err != nil {
|
2023-05-24 12:45:19 +00:00
|
|
|
return err
|
2023-04-05 14:02:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-12 14:17:13 +00:00
|
|
|
for key, pipeline := range b.Config.Resources.Pipelines {
|
|
|
|
dir, err := pipeline.ConfigFileDirectory()
|
|
|
|
if err != nil {
|
2023-05-24 12:45:19 +00:00
|
|
|
return fmt.Errorf("unable to determine directory for pipeline %s: %w", key, err)
|
2023-04-12 14:17:13 +00:00
|
|
|
}
|
|
|
|
|
2023-04-05 14:02:17 +00:00
|
|
|
for i := 0; i < len(pipeline.Libraries); i++ {
|
2023-04-12 14:17:13 +00:00
|
|
|
err := m.translatePipelineLibrary(dir, b, &pipeline.Libraries[i])
|
2023-04-05 14:02:17 +00:00
|
|
|
if err != nil {
|
2023-05-24 12:45:19 +00:00
|
|
|
return err
|
2023-04-05 14:02:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-24 12:45:19 +00:00
|
|
|
return nil
|
2023-04-05 14:02:17 +00:00
|
|
|
}
|