16306: Remove daemontools dependency.
[arvados.git] / lib / boot / supervisor.go
index 7dba9b5dcf9ae35e1712a817a805d4361cf71da5..e892d3e6239af78062936acabca66299f74a2c26 100644 (file)
@@ -21,6 +21,7 @@ import (
        "os/user"
        "path/filepath"
        "reflect"
+       "strconv"
        "strings"
        "sync"
        "syscall"
@@ -206,13 +207,13 @@ func (super *Supervisor) run(cfg *arvados.Config) error {
        } else if super.SourceVersion == "" {
                // Find current source tree version.
                var buf bytes.Buffer
-               err = super.RunProgram(super.ctx, ".", &buf, nil, "git", "diff", "--shortstat")
+               err = super.RunProgram(super.ctx, ".", runOptions{output: &buf}, "git", "diff", "--shortstat")
                if err != nil {
                        return err
                }
                dirty := buf.Len() > 0
                buf.Reset()
-               err = super.RunProgram(super.ctx, ".", &buf, nil, "git", "log", "-n1", "--format=%H")
+               err = super.RunProgram(super.ctx, ".", runOptions{output: &buf}, "git", "log", "-n1", "--format=%H")
                if err != nil {
                        return err
                }
@@ -407,7 +408,7 @@ func (super *Supervisor) installGoProgram(ctx context.Context, srcpath string) (
        if super.ClusterType == "production" {
                return binfile, nil
        }
-       err := super.RunProgram(ctx, filepath.Join(super.SourcePath, srcpath), nil, []string{"GOBIN=" + super.bindir}, "go", "install", "-ldflags", "-X git.arvados.org/arvados.git/lib/cmd.version="+super.SourceVersion+" -X main.version="+super.SourceVersion)
+       err := super.RunProgram(ctx, filepath.Join(super.SourcePath, srcpath), runOptions{env: []string{"GOBIN=" + super.bindir}}, "go", "install", "-ldflags", "-X git.arvados.org/arvados.git/lib/cmd.version="+super.SourceVersion+" -X main.version="+super.SourceVersion)
        return binfile, err
 }
 
@@ -470,6 +471,12 @@ func (super *Supervisor) lookPath(prog string) string {
        return prog
 }
 
+type runOptions struct {
+       output io.Writer // attach stdout
+       env    []string  // add/replace environment variables
+       user   string    // run as specified user
+}
+
 // RunProgram runs prog with args, using dir as working directory. If ctx is
 // cancelled while the child is running, RunProgram terminates the child, waits
 // for it to exit, then returns.
@@ -478,15 +485,12 @@ func (super *Supervisor) lookPath(prog string) string {
 //
 // Child's stdout will be written to output if non-nil, otherwise the
 // boot command's stderr.
-func (super *Supervisor) RunProgram(ctx context.Context, dir string, output io.Writer, env []string, prog string, args ...string) error {
+func (super *Supervisor) RunProgram(ctx context.Context, dir string, opts runOptions, prog string, args ...string) error {
        cmdline := fmt.Sprintf("%s", append([]string{prog}, args...))
        super.logger.WithField("command", cmdline).WithField("dir", dir).Info("executing")
 
        logprefix := prog
        {
-               if logprefix == "setuidgid" && len(args) >= 3 {
-                       logprefix = args[2]
-               }
                innerargs := args
                if logprefix == "sudo" {
                        for i := 0; i < len(args); i++ {
@@ -531,10 +535,10 @@ func (super *Supervisor) RunProgram(ctx context.Context, dir string, output io.W
        }()
        copiers.Add(1)
        go func() {
-               if output == nil {
+               if opts.output == nil {
                        io.Copy(logwriter, stdout)
                } else {
-                       io.Copy(output, stdout)
+                       io.Copy(opts.output, stdout)
                }
                copiers.Done()
        }()
@@ -544,10 +548,34 @@ func (super *Supervisor) RunProgram(ctx context.Context, dir string, output io.W
        } else {
                cmd.Dir = filepath.Join(super.SourcePath, dir)
        }
-       env = append([]string(nil), env...)
+       env := append([]string(nil), opts.env...)
        env = append(env, super.environ...)
        cmd.Env = dedupEnv(env)
 
+       if opts.user != "" {
+               // Note: We use this approach instead of "sudo"
+               // because in certain circumstances (we are pid 1 in a
+               // docker container, and our passenger child process
+               // changes to pgid 1) the intermediate sudo process
+               // notices we have the same pgid as our child and
+               // refuses to propagate signals from us to our child,
+               // so we can't signal/shutdown our passenger/rails
+               // apps. "chpst" or "setuidgid" would work, but these
+               // few lines avoid depending on runit/daemontools.
+               u, err := user.Lookup(opts.user)
+               if err != nil {
+                       return fmt.Errorf("user.Lookup(%q): %w", opts.user, err)
+               }
+               uid, _ := strconv.Atoi(u.Uid)
+               gid, _ := strconv.Atoi(u.Gid)
+               cmd.SysProcAttr = &syscall.SysProcAttr{
+                       Credential: &syscall.Credential{
+                               Uid: uint32(uid),
+                               Gid: uint32(gid),
+                       },
+               }
+       }
+
        exited := false
        defer func() { exited = true }()
        go func() {