mirror of https://github.com/databricks/cli.git
121 lines
2.7 KiB
Go
121 lines
2.7 KiB
Go
|
package daemon
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"os"
|
||
|
"os/exec"
|
||
|
"strconv"
|
||
|
)
|
||
|
|
||
|
const DatabricksCliParentPid = "DATABRICKS_CLI_PARENT_PID"
|
||
|
|
||
|
type Daemon struct {
|
||
|
// If provided, the child process's pid will be written in the file at this
|
||
|
// path.
|
||
|
PidFilePath string
|
||
|
|
||
|
// Environment variables to set in the child process.
|
||
|
Env []string
|
||
|
|
||
|
// Path to executable to run. If empty, the current executable is used.
|
||
|
Executable string
|
||
|
|
||
|
// Arguments to pass to the child process.
|
||
|
Args []string
|
||
|
|
||
|
// Log file to write the child process's output to.
|
||
|
LogFile string
|
||
|
|
||
|
logFile *os.File
|
||
|
cmd *exec.Cmd
|
||
|
stdin io.WriteCloser
|
||
|
}
|
||
|
|
||
|
func (d *Daemon) Start() error {
|
||
|
cli, err := os.Executable()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
executable := d.Executable
|
||
|
if executable == "" {
|
||
|
executable = cli
|
||
|
}
|
||
|
|
||
|
d.cmd = exec.Command(executable, d.Args...)
|
||
|
|
||
|
// Set environment variable so that the child process knows its parent's PID.
|
||
|
// In unix systems orphaned processes are automatically re-parented to init (pid 1)
|
||
|
// so we cannot rely on os.Getppid() to get the original parent's pid.
|
||
|
d.Env = append(d.Env, fmt.Sprintf("%s=%d", DatabricksCliParentPid, os.Getpid()))
|
||
|
d.cmd.Env = d.Env
|
||
|
|
||
|
d.cmd.SysProcAttr = sysProcAttr()
|
||
|
|
||
|
// By default redirect stdout and stderr to /dev/null.
|
||
|
d.cmd.Stdout = nil
|
||
|
d.cmd.Stderr = nil
|
||
|
|
||
|
// If a log file is provided, redirect stdout and stderr to the log file.
|
||
|
if d.LogFile != "" {
|
||
|
d.logFile, err = os.OpenFile(d.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to open log file: %w", err)
|
||
|
}
|
||
|
|
||
|
d.cmd.Stdout = d.logFile
|
||
|
d.cmd.Stderr = d.logFile
|
||
|
}
|
||
|
|
||
|
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) WriteInput(b []byte) error {
|
||
|
_, err := d.stdin.Write(b)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func (d *Daemon) Release() error {
|
||
|
if d.stdin != nil {
|
||
|
err := d.stdin.Close()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to close stdin: %w", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Note that the child process will stream it's output directly to the log file.
|
||
|
// So it's safe to close this file handle even if the child process is still running.
|
||
|
if d.logFile != nil {
|
||
|
err := d.logFile.Close()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to close log file: %w", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if d.cmd == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// The docs for [os.Process.Release] recommend calling Release if Wait is not called.
|
||
|
// It's probably not necessary but we call it just to be safe.
|
||
|
return d.cmd.Process.Release()
|
||
|
}
|