databricks-cli/libs/daemon/daemon.go

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

118 lines
2.5 KiB
Go
Raw Normal View History

2025-02-13 18:00:13 +00:00
package daemon
import (
"fmt"
"io"
"os"
"os/exec"
"strconv"
)
2025-02-17 18:23:02 +00:00
const DatabricksCliParentPid = "DATABRICKS_CLI_PARENT_PID"
2025-02-13 18:00:13 +00:00
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
2025-02-17 22:02:17 +00:00
// Path to executable to run. If empty, the current executable is used.
Executable string
// Arguments to pass to the child process.
2025-02-13 18:00:13 +00:00
Args []string
2025-02-17 18:23:02 +00:00
// Log file to write the child process's output to.
LogFile string
2025-02-18 14:02:37 +00:00
logFile *os.File
2025-02-18 13:32:16 +00:00
cmd *exec.Cmd
stdin io.WriteCloser
2025-02-13 18:00:13 +00:00
}
func (d *Daemon) Start() error {
cli, err := os.Executable()
if err != nil {
return err
}
2025-02-17 22:02:17 +00:00
executable := d.Executable
if executable == "" {
executable = cli
}
d.cmd = exec.Command(executable, d.Args...)
2025-02-17 18:23:02 +00:00
2025-02-18 14:02:56 +00:00
// Set environment variable so that the child process knows its parent's PID.
2025-02-18 13:32:16 +00:00
// 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.
2025-02-17 18:23:02 +00:00
d.Env = append(d.Env, fmt.Sprintf("%s=%d", DatabricksCliParentPid, os.Getpid()))
2025-02-13 18:00:13 +00:00
d.cmd.Env = d.Env
2025-02-17 18:23:02 +00:00
2025-02-13 18:00:13 +00:00
d.cmd.SysProcAttr = sysProcAttr()
2025-02-17 18:23:02 +00:00
// 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 != "" {
2025-02-18 14:02:37 +00:00
d.logFile, err = os.OpenFile(d.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
2025-02-17 18:23:02 +00:00
if err != nil {
return fmt.Errorf("failed to open log file: %w", err)
}
2025-02-18 14:02:37 +00:00
d.cmd.Stdout = d.logFile
d.cmd.Stderr = d.logFile
2025-02-17 18:23:02 +00:00
}
2025-02-13 18:00:13 +00:00
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
}
2025-02-17 18:23:02 +00:00
func (d *Daemon) WriteInput(b []byte) error {
_, err := d.stdin.Write(b)
return err
}
2025-02-13 18:00:13 +00:00
2025-02-17 18:23:02 +00:00
func (d *Daemon) Release() error {
2025-02-13 18:00:13 +00:00
if d.stdin != nil {
err := d.stdin.Close()
if err != nil {
return fmt.Errorf("failed to close stdin: %w", err)
}
}
2025-02-18 14:02:37 +00:00
if d.logFile != nil {
err := d.logFile.Close()
2025-02-18 13:32:16 +00:00
if err != nil {
return fmt.Errorf("failed to close log file: %w", err)
}
}
2025-02-13 18:00:13 +00:00
if d.cmd == nil {
return nil
}
2025-02-18 13:33:41 +00:00
// 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.
2025-02-13 18:00:13 +00:00
return d.cmd.Process.Release()
}