2022-07-07 18:56:59 +00:00
|
|
|
package sync
|
|
|
|
|
|
|
|
import (
|
2023-01-19 14:57:41 +00:00
|
|
|
"context"
|
2022-07-07 18:56:59 +00:00
|
|
|
"fmt"
|
|
|
|
"log"
|
2023-01-19 14:57:41 +00:00
|
|
|
"strings"
|
2022-07-07 18:56:59 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/databricks/bricks/cmd/root"
|
2022-12-12 13:31:06 +00:00
|
|
|
"github.com/databricks/bricks/cmd/sync/repofiles"
|
2022-07-07 18:56:59 +00:00
|
|
|
"github.com/databricks/bricks/git"
|
|
|
|
"github.com/databricks/bricks/project"
|
2023-01-19 14:57:41 +00:00
|
|
|
"github.com/databricks/databricks-sdk-go"
|
|
|
|
"github.com/databricks/databricks-sdk-go/apierr"
|
|
|
|
"github.com/databricks/databricks-sdk-go/service/scim"
|
|
|
|
"github.com/databricks/databricks-sdk-go/service/workspace"
|
2022-07-07 18:56:59 +00:00
|
|
|
"github.com/spf13/cobra"
|
|
|
|
)
|
|
|
|
|
2023-01-19 14:57:41 +00:00
|
|
|
func matchesBasePaths(me *scim.User, path string) error {
|
|
|
|
basePaths := []string{
|
|
|
|
fmt.Sprintf("/Users/%s/", me.UserName),
|
|
|
|
fmt.Sprintf("/Repos/%s/", me.UserName),
|
|
|
|
}
|
|
|
|
basePathMatch := false
|
|
|
|
for _, basePath := range basePaths {
|
|
|
|
if strings.HasPrefix(path, basePath) {
|
|
|
|
basePathMatch = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !basePathMatch {
|
|
|
|
return fmt.Errorf("path must be nested under %s or %s", basePaths[0], basePaths[1])
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ensureRemotePathIsUsable checks if the specified path is nested under
|
|
|
|
// expected base paths and if it is a directory or repository.
|
|
|
|
func ensureRemotePathIsUsable(ctx context.Context, wsc *databricks.WorkspaceClient, me *scim.User, path string) error {
|
|
|
|
err := matchesBasePaths(me, path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure that the remote path exits.
|
|
|
|
// If it is a repo, it has to exist.
|
|
|
|
// If it is a workspace path, it may not exist.
|
|
|
|
info, err := wsc.Workspace.GetStatusByPath(ctx, path)
|
|
|
|
if err != nil {
|
|
|
|
// We only deal with 404s below.
|
|
|
|
if !apierr.IsMissing(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case strings.HasPrefix(path, "/Repos/"):
|
|
|
|
return fmt.Errorf("%s does not exist; please create it first", path)
|
|
|
|
case strings.HasPrefix(path, "/Users/"):
|
|
|
|
// The workspace path doesn't exist. Create it and try again.
|
|
|
|
err = wsc.Workspace.MkdirsByPath(ctx, path)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to create directory at %s: %w", path, err)
|
|
|
|
}
|
|
|
|
info, err = wsc.Workspace.GetStatusByPath(ctx, path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf(
|
|
|
|
"[DEBUG] Path %s has type %s (ID: %d)",
|
|
|
|
info.Path,
|
|
|
|
strings.ToLower(info.ObjectType.String()),
|
|
|
|
info.ObjectId,
|
|
|
|
)
|
|
|
|
|
|
|
|
// We expect the object at path to be a directory or a repo.
|
|
|
|
switch info.ObjectType {
|
|
|
|
case workspace.ObjectTypeDirectory:
|
|
|
|
return nil
|
|
|
|
case workspace.ObjectTypeRepo:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Errorf("%s points to a %s", path, strings.ToLower(info.ObjectType.String()))
|
|
|
|
}
|
|
|
|
|
2022-07-07 18:56:59 +00:00
|
|
|
// syncCmd represents the sync command
|
|
|
|
var syncCmd = &cobra.Command{
|
|
|
|
Use: "sync",
|
|
|
|
Short: "run syncs for the project",
|
2022-09-16 09:06:58 +00:00
|
|
|
|
|
|
|
PreRunE: project.Configure,
|
2022-07-07 18:56:59 +00:00
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
ctx := cmd.Context()
|
2022-09-16 13:18:46 +00:00
|
|
|
prj := project.Get(ctx)
|
|
|
|
wsc := prj.WorkspacesClient()
|
2022-09-14 15:50:29 +00:00
|
|
|
|
2023-01-19 14:57:41 +00:00
|
|
|
me, err := prj.Me()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-09-14 15:50:29 +00:00
|
|
|
if *remotePath == "" {
|
|
|
|
repositoryName, err := git.RepositoryName()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
*remotePath = fmt.Sprintf("/Repos/%s/%s", me.UserName, repositoryName)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("[INFO] Remote file sync location: %v", *remotePath)
|
2023-01-19 14:57:41 +00:00
|
|
|
err = ensureRemotePathIsUsable(ctx, wsc, me, *remotePath)
|
2022-07-07 18:56:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-09-14 15:50:29 +00:00
|
|
|
|
2022-09-16 13:18:46 +00:00
|
|
|
root := prj.Root()
|
2022-12-12 13:31:06 +00:00
|
|
|
repoFiles := repofiles.Create(*remotePath, root, wsc)
|
|
|
|
syncCallback := syncCallback(ctx, repoFiles)
|
|
|
|
err = spawnWatchdog(ctx, *interval, syncCallback, *remotePath)
|
2022-09-14 15:50:29 +00:00
|
|
|
return err
|
2022-07-07 18:56:59 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// project files polling interval
|
|
|
|
var interval *time.Duration
|
|
|
|
|
2022-09-14 15:50:29 +00:00
|
|
|
var remotePath *string
|
|
|
|
|
2022-09-19 14:47:55 +00:00
|
|
|
var persistSnapshot *bool
|
|
|
|
|
2022-07-07 18:56:59 +00:00
|
|
|
func init() {
|
|
|
|
root.RootCmd.AddCommand(syncCmd)
|
|
|
|
interval = syncCmd.Flags().Duration("interval", 1*time.Second, "project files polling interval")
|
2022-09-14 15:50:29 +00:00
|
|
|
remotePath = syncCmd.Flags().String("remote-path", "", "remote path to store repo in. eg: /Repos/me@example.com/test-repo")
|
2022-09-19 14:47:55 +00:00
|
|
|
persistSnapshot = syncCmd.Flags().Bool("persist-snapshot", true, "whether to store local snapshots of sync state")
|
2022-07-07 18:56:59 +00:00
|
|
|
}
|