mirror of https://github.com/databricks/cli.git
Add library for spawning a daemon
This commit is contained in:
parent
2d09636611
commit
d86ccf6b49
|
@ -75,5 +75,9 @@ func New(ctx context.Context) *cobra.Command {
|
|||
cli.AddCommand(sync.New())
|
||||
cli.AddCommand(version.New())
|
||||
|
||||
cli.AddCommand(&cobra.Command{
|
||||
Use: "stream-",
|
||||
})
|
||||
|
||||
return cli
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package selftest
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
PrintStdinParentPid = "DATABRICKS_CLI_PRINT_STDIN_PARENT_PID"
|
||||
)
|
||||
|
||||
// TODO CONTINUE: Write command that wait for each other via the PID.
|
||||
// Ensure to check the process name as the PID otherwise can be reused pretty
|
||||
// quick.
|
||||
|
||||
func newPrintStdin() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "print-stdin",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if os.Getenv(PrintStdinParentPid) != "" {
|
||||
return nil
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package selftest
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func New() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "selftest",
|
||||
Short: "Non functional CLI commands that are useful for testing",
|
||||
}
|
||||
|
||||
cmd.AddCommand(newPrintStdin())
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Daemon struct {
|
||||
// If provided, the child process will create a pid file at this path.
|
||||
PidFilePath string
|
||||
|
||||
// Environment variables to set in the child process.
|
||||
Env []string
|
||||
|
||||
// Arguments to pass to the child process.
|
||||
Args []string
|
||||
|
||||
cmd *exec.Cmd
|
||||
stdin io.WriteCloser
|
||||
}
|
||||
|
||||
func (d *Daemon) Start() error {
|
||||
cli, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.cmd = exec.Command(cli, d.Args...)
|
||||
d.cmd.Env = d.Env
|
||||
d.cmd.SysProcAttr = sysProcAttr()
|
||||
|
||||
d.stdin, err = d.cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get stdin pipe: %w", err)
|
||||
}
|
||||
|
||||
err = d.cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.PidFilePath != "" {
|
||||
err = os.WriteFile(d.PidFilePath, []byte(strconv.Itoa(d.cmd.Process.Pid)), 0o644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write pid file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Daemon) Release() error {
|
||||
if d.PidFilePath != "" {
|
||||
err := os.Remove(d.PidFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove pid file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if d.stdin != nil {
|
||||
err := d.stdin.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to close stdin: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if d.cmd == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return d.cmd.Process.Release()
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
//go:build linux || darwin
|
||||
|
||||
package daemon
|
||||
|
||||
import "syscall"
|
||||
|
||||
// References:
|
||||
// 1. linux: https://go.dev/src/syscall/exec_linux.go
|
||||
// 2. macos (arm): https://go.dev/src/syscall/exec_libc2.go
|
||||
func sysProcAttr() *syscall.SysProcAttr {
|
||||
return &syscall.SysProcAttr{
|
||||
// Create a new session for the child process. This ensures that the daemon
|
||||
// is not terminated when the parent session is closed. This can happen
|
||||
// for example when a ssh session is terminated.
|
||||
// TODO: Test this.
|
||||
Setsid: true,
|
||||
Noctty: true,
|
||||
|
||||
// Start a new process group for the child process. This ensures that
|
||||
// termination signals to the parent's process group are not propagated to
|
||||
// the child process.
|
||||
Setpgid: true,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
//go:build windows
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func sysProcAttr() *syscall.SysProcAttr {
|
||||
return &syscall.SysProcAttr{
|
||||
CreationFlags: windows.CREATE_NEW_PROCESS_GROUP | windows.DETACHED_PROCESS | windows.CREATE_NO_WINDOW,
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue