2024-08-21 15:33:25 +00:00
|
|
|
package mutator
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"path/filepath"
|
|
|
|
|
|
|
|
"github.com/databricks/cli/bundle"
|
|
|
|
"github.com/databricks/cli/libs/diag"
|
|
|
|
"github.com/databricks/cli/libs/dyn"
|
|
|
|
"github.com/databricks/cli/libs/vfs"
|
|
|
|
)
|
|
|
|
|
|
|
|
type syncInferRoot struct{}
|
|
|
|
|
|
|
|
// SyncInferRoot is a mutator that infers the root path of all files to synchronize by looking at the
|
|
|
|
// paths in the sync configuration. The sync root may be different from the bundle root
|
|
|
|
// when the user intends to synchronize files outside the bundle root.
|
|
|
|
//
|
|
|
|
// The sync root can be equivalent to or an ancestor of the bundle root, but not a descendant.
|
|
|
|
// That is, the sync root must contain the bundle root.
|
|
|
|
//
|
|
|
|
// This mutator requires all sync-related paths and patterns to be relative to the bundle root path.
|
|
|
|
// This is done by the [RewriteSyncPaths] mutator, which must run before this mutator.
|
|
|
|
func SyncInferRoot() bundle.Mutator {
|
|
|
|
return &syncInferRoot{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *syncInferRoot) Name() string {
|
|
|
|
return "SyncInferRoot"
|
|
|
|
}
|
|
|
|
|
|
|
|
// computeRoot finds the innermost path that contains the specified path.
|
|
|
|
// It traverses up the root path until it finds the innermost path.
|
|
|
|
// If the path does not exist, it returns an empty string.
|
|
|
|
//
|
|
|
|
// See "sync_infer_root_internal_test.go" for examples.
|
|
|
|
func (m *syncInferRoot) computeRoot(path string, root string) string {
|
|
|
|
for !filepath.IsLocal(path) {
|
|
|
|
// Break if we have reached the root of the filesystem.
|
|
|
|
dir := filepath.Dir(root)
|
|
|
|
if dir == root {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the sync path as we navigate up the directory tree.
|
|
|
|
path = filepath.Join(filepath.Base(root), path)
|
|
|
|
|
|
|
|
// Move up the directory tree.
|
|
|
|
root = dir
|
|
|
|
}
|
|
|
|
|
|
|
|
return filepath.Clean(root)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *syncInferRoot) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
|
|
|
var diags diag.Diagnostics
|
|
|
|
|
|
|
|
// Use the bundle root path as the starting point for inferring the sync root path.
|
2024-09-27 10:03:05 +00:00
|
|
|
bundleRootPath := filepath.Clean(b.BundleRootPath)
|
2024-08-21 15:33:25 +00:00
|
|
|
|
|
|
|
// Infer the sync root path by looking at each one of the sync paths.
|
|
|
|
// Every sync path must be a descendant of the final sync root path.
|
|
|
|
syncRootPath := bundleRootPath
|
|
|
|
for _, path := range b.Config.Sync.Paths {
|
|
|
|
computedPath := m.computeRoot(path, bundleRootPath)
|
|
|
|
if computedPath == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update sync root path if the computed root path is an ancestor of the current sync root path.
|
|
|
|
if len(computedPath) < len(syncRootPath) {
|
|
|
|
syncRootPath = computedPath
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The new sync root path can only be an ancestor of the previous root path.
|
|
|
|
// Compute the relative path from the sync root to the bundle root.
|
|
|
|
rel, err := filepath.Rel(syncRootPath, bundleRootPath)
|
|
|
|
if err != nil {
|
|
|
|
return diag.FromErr(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If during computation of the sync root path we hit the root of the filesystem,
|
|
|
|
// then one or more of the sync paths are outside the filesystem.
|
|
|
|
// Check if this happened by verifying that none of the paths escape the root
|
|
|
|
// when joined with the sync root path.
|
|
|
|
for i, path := range b.Config.Sync.Paths {
|
|
|
|
if filepath.IsLocal(filepath.Join(rel, path)) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
diags = append(diags, diag.Diagnostic{
|
|
|
|
Severity: diag.Error,
|
|
|
|
Summary: fmt.Sprintf("invalid sync path %q", path),
|
|
|
|
Locations: b.Config.GetLocations(fmt.Sprintf("sync.paths[%d]", i)),
|
|
|
|
Paths: []dyn.Path{dyn.NewPath(dyn.Key("sync"), dyn.Key("paths"), dyn.Index(i))},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if diags.HasError() {
|
|
|
|
return diags
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update all paths in the sync configuration to be relative to the sync root.
|
|
|
|
for i, p := range b.Config.Sync.Paths {
|
|
|
|
b.Config.Sync.Paths[i] = filepath.Join(rel, p)
|
|
|
|
}
|
|
|
|
for i, p := range b.Config.Sync.Include {
|
|
|
|
b.Config.Sync.Include[i] = filepath.Join(rel, p)
|
|
|
|
}
|
|
|
|
for i, p := range b.Config.Sync.Exclude {
|
|
|
|
b.Config.Sync.Exclude[i] = filepath.Join(rel, p)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Configure the sync root path.
|
|
|
|
b.SyncRoot = vfs.MustNew(syncRootPath)
|
|
|
|
b.SyncRootPath = syncRootPath
|
|
|
|
return nil
|
|
|
|
}
|