diff --git a/acceptance/pipelines/install-pipelines-cli/output.txt b/acceptance/pipelines/install-pipelines-cli/output.txt new file mode 100644 index 0000000000..c4ed9fb91e --- /dev/null +++ b/acceptance/pipelines/install-pipelines-cli/output.txt @@ -0,0 +1,36 @@ + +=== install pipelines cli +>>> errcode [CLI] install-pipelines-cli -d ./subdir +pipelines successfully installed in directory "./subdir" + +>>> errcode ./subdir/pipelines +Pipelines CLI (stub, to be filled in) + +Usage: + pipelines [flags] + +Flags: + -h, --help help for pipelines + +=== pipelines already installed +>>> errcode [CLI] install-pipelines-cli -d ./subdir +pipelines already installed in directory "./subdir" + +=== pipelines file exists, should not overwrite +>>> errcode [CLI] install-pipelines-cli -d ./subdir +Error: cannot install pipelines CLI: "subdir/pipelines" already exists + +Exit code: 1 + +=== databricks executable called with alias +>>> errcode ./subdir/notdatabricks install-pipelines-cli -d ./subdir +pipelines successfully installed in directory "./subdir" + +>>> errcode ./subdir/pipelines +Pipelines CLI (stub, to be filled in) + +Usage: + pipelines [flags] + +Flags: + -h, --help help for pipelines diff --git a/acceptance/pipelines/install-pipelines-cli/script b/acceptance/pipelines/install-pipelines-cli/script new file mode 100644 index 0000000000..81eeb39764 --- /dev/null +++ b/acceptance/pipelines/install-pipelines-cli/script @@ -0,0 +1,25 @@ +tmpdir="./subdir" +pipelines="$tmpdir/pipelines" +mkdir -p $tmpdir + +title "install pipelines cli" +trace errcode $CLI install-pipelines-cli -d $tmpdir +trace errcode $pipelines + +title "pipelines already installed" +trace errcode $CLI install-pipelines-cli -d $tmpdir +rm -f $pipelines + +title "pipelines file exists, should not overwrite" +touch $pipelines +trace errcode $CLI install-pipelines-cli -d $tmpdir +rm -f $pipelines + +title "databricks executable called with alias" +cp $CLI $tmpdir/notdatabricks +trace errcode $tmpdir/notdatabricks install-pipelines-cli -d $tmpdir +trace errcode $pipelines + +# Cleanup +rm -f $tmpdir/notdatabricks $pipelines +rm -rf $tmpdir diff --git a/acceptance/pipelines/install-pipelines-cli/test.toml b/acceptance/pipelines/install-pipelines-cli/test.toml new file mode 100644 index 0000000000..85d92d65df --- /dev/null +++ b/acceptance/pipelines/install-pipelines-cli/test.toml @@ -0,0 +1,8 @@ +# Fix path with reverse slashes in the output for Windows. +[[Repls]] +Old = '\\\\' +New = '/' + +[[Repls]] +Old = '\\' +New = '/' diff --git a/cmd/cmd.go b/cmd/cmd.go index acd8968df4..fe458294f2 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -11,6 +11,7 @@ import ( "github.com/databricks/cli/cmd/configure" "github.com/databricks/cli/cmd/fs" "github.com/databricks/cli/cmd/labs" + "github.com/databricks/cli/cmd/pipelines" "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/cmd/selftest" "github.com/databricks/cli/cmd/sync" @@ -106,6 +107,7 @@ func New(ctx context.Context) *cobra.Command { cli.AddCommand(sync.New()) cli.AddCommand(version.New()) cli.AddCommand(selftest.New()) + cli.AddCommand(pipelines.InstallPipelinesCLI()) // Add workspace command groups, filtering out empty groups or groups with only hidden commands. allGroups := workspace.Groups() diff --git a/cmd/pipelines/install_pipelines_cli.go b/cmd/pipelines/install_pipelines_cli.go new file mode 100644 index 0000000000..b13f4890bf --- /dev/null +++ b/cmd/pipelines/install_pipelines_cli.go @@ -0,0 +1,66 @@ +package pipelines + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/databricks/cli/libs/cmdio" + "github.com/spf13/cobra" +) + +func installPipelinesSymlink(ctx context.Context, directory string) error { + path, err := os.Executable() + if err != nil { + return err + } + realPath, err := filepath.EvalSymlinks(path) + if err != nil { + return err + } + + dir := directory + if dir == "" { + dir = filepath.Dir(path) + } + pipelinesPath := filepath.Join(dir, "pipelines") + + _, err = os.Lstat(pipelinesPath) + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + return err + } + err = os.Symlink(realPath, pipelinesPath) + if err != nil { + return err + } + cmdio.LogString(ctx, fmt.Sprintf("pipelines successfully installed in directory %q", dir)) + return nil + } + + target, err := filepath.EvalSymlinks(pipelinesPath) + if err != nil { + return err + } + if realPath == target { + cmdio.LogString(ctx, fmt.Sprintf("pipelines already installed in directory %q", dir)) + return nil + } + return fmt.Errorf("cannot install pipelines CLI: %q already exists", pipelinesPath) +} + +func InstallPipelinesCLI() *cobra.Command { + var directory string + cmd := &cobra.Command{ + Use: "install-pipelines-cli", + Short: "Install Pipelines CLI", + Hidden: true, + RunE: func(cmd *cobra.Command, args []string) error { + return installPipelinesSymlink(cmd.Context(), directory) + }, + } + cmd.Flags().StringVarP(&directory, "directory", "d", "", "Directory in which to install pipelines CLI (defaults to databricks CLI's directory)") + return cmd +} diff --git a/cmd/pipelines/pipelines.go b/cmd/pipelines/pipelines.go new file mode 100644 index 0000000000..31188f3448 --- /dev/null +++ b/cmd/pipelines/pipelines.go @@ -0,0 +1,18 @@ +package pipelines + +import ( + "github.com/spf13/cobra" +) + +func New() *cobra.Command { + cmd := &cobra.Command{ + Use: "pipelines", + Short: "Pipelines CLI", + Long: "Pipelines CLI (stub, to be filled in)", + Run: func(cmd *cobra.Command, args []string) { + _ = cmd.Help() + }, + } + + return cmd +} diff --git a/main.go b/main.go index c568e6adbd..7f390d9edd 100644 --- a/main.go +++ b/main.go @@ -3,14 +3,30 @@ package main import ( "context" "os" + "path/filepath" + "strings" "github.com/databricks/cli/cmd" + "github.com/databricks/cli/cmd/pipelines" "github.com/databricks/cli/cmd/root" + "github.com/spf13/cobra" ) +// If invoked as 'pipelines' (or 'pipelines.exe' on Windows), returns pipelines-specific commands, +// otherwise returns the databricks CLI commands. This is used to allow the same +// binary to be used for both pipelines and databricks CLI commands. +func getCommand(ctx context.Context) *cobra.Command { + invokedAs := filepath.Base(os.Args[0]) + if strings.HasPrefix(invokedAs, "pipelines") { + return pipelines.New() + } + return cmd.New(ctx) +} + func main() { ctx := context.Background() - err := root.Execute(ctx, cmd.New(ctx)) + command := getCommand(ctx) + err := root.Execute(ctx, command) if err != nil { os.Exit(1) }