commands/helpers/proxy_exec.go (134 lines of code) (raw):
package helpers
import (
"debug/buildinfo"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime/debug"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
"gitlab.com/ajwalker/phrasestream/addmask"
"gitlab.com/gitlab-org/gitlab-runner/commands/helpers/internal/store"
"gitlab.com/gitlab-org/gitlab-runner/common"
)
var (
stdout = io.Writer(os.Stdout)
stderr = io.Writer(os.Stderr)
)
type ProxyExecCommand struct {
Bootstrap bool `long:"bootstrap" description:"bootstrap helper binary"`
TempDir string `long:"temp-dir" description:"temporary directory"`
}
type Proxy struct {
store *store.Store
addmask *addmask.AddMask
}
func NewProxy(dir string, stdout, stderr io.Writer) (*Proxy, error) {
db, err := store.Open(dir)
if err != nil {
return nil, err
}
pe := &Proxy{store: db}
pe.addmask, err = addmask.New(db, stdout, stderr)
if err != nil {
return nil, err
}
return pe, nil
}
func (p *Proxy) Stdout() io.Writer {
return p.addmask.Get(0)
}
func (p *Proxy) Stderr() io.Writer {
return p.addmask.Get(1)
}
func (p *Proxy) Close() error {
p.store.Close()
return p.addmask.Close()
}
func (c *ProxyExecCommand) Execute(cliContext *cli.Context) {
args := cliContext.Args()
if len(args) == 0 {
logrus.Fatal("gitlab-runner-helper exec expected args")
}
dst := os.Getenv("RUNNER_TEMP_PROJECT_DIR")
if dst == "" {
dst = c.TempDir
}
if c.Bootstrap {
if err := bootstrap(dst); err != nil {
logrus.Fatalln("bootstrapping", err)
}
}
proxy, err := NewProxy(dst, stdout, stderr)
if err != nil {
logrus.Fatalln("creating exec proxy", err)
}
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = proxy.Stdout()
cmd.Stderr = proxy.Stderr()
err = errors.Join(
cmd.Run(),
proxy.Close(),
)
if err != nil {
logrus.Error(err)
var exitError *exec.ExitError
if errors.As(err, &exitError) {
os.Exit(exitError.ExitCode())
}
}
}
func bootstrap(dst string) error {
src, _ := os.Executable()
_ = os.MkdirAll(dst, 0o777)
pathname := filepath.Join(dst, "gitlab-runner-helper")
_, err := os.Stat(pathname)
if err == nil {
// if the path exists, check to see if it's identical by comparing build info
buildInfoDst, err := buildinfo.ReadFile(pathname)
if err != nil {
return fmt.Errorf("reading build info of existing binary: %w", err)
}
buildInfoSrc, ok := debug.ReadBuildInfo()
if ok && buildInfoDst.String() == buildInfoSrc.String() {
return nil
}
}
if err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("checking helper install: %w", err)
}
fsrc, err := os.Open(src)
if err != nil {
return fmt.Errorf("opening helper: %w", err)
}
defer fsrc.Close()
fdst, err := os.CreateTemp(dst, "")
if err != nil {
return fmt.Errorf("creating temp file: %w", err)
}
defer os.RemoveAll(fdst.Name())
defer fdst.Close()
if _, err := io.Copy(fdst, fsrc); err != nil {
return fmt.Errorf("copying helper: %w", err)
}
if err := fdst.Close(); err != nil {
return fmt.Errorf("closing helper: %w", err)
}
if err := os.Rename(fdst.Name(), pathname); err != nil {
return fmt.Errorf("renaming helper: %w", err)
}
if err := os.Chmod(pathname, 0o777); err != nil {
return fmt.Errorf("changing helper permissions: %w", err)
}
return nil
}
func init() {
common.RegisterCommand2(
"proxy-exec",
"execute internal commands (internal)",
&ProxyExecCommand{},
)
}