Esempio n. 1
0
// Execute a formula in a specified directory. MAY PANIC.
func (e *Executor) Execute(f def.Formula, j def.Job, d string, result *def.JobResult, outS, errS io.WriteCloser, journal log15.Logger) {
	// Dedicated rootfs folder to distinguish container from nsinit noise
	rootfs := filepath.Join(d, "rootfs")

	// nsinit wants to have a logfile
	logFile := filepath.Join(d, "nsinit-debug.log")

	// Prep command
	args := []string{}

	// Global options:
	// --root will place the 'nsinit' folder (holding a state.json file) in d
	// --log-file does much the same with a log file (unsure if care?)
	// --debug allegedly enables debug output in the log file
	args = append(args, "--root", d, "--log-file", logFile, "--debug")

	// Subcommand, and tell nsinit to not desire a JSON file (instead just use many flergs)
	args = append(args, "exec", "--create")

	// Use the host's networking (no bridge, no namespaces, etc)
	args = append(args, "--net=host")

	// Where our system image exists
	args = append(args, "--rootfs", rootfs)

	// Set cwd
	args = append(args, "--cwd", f.Accents.Cwd)

	// Add all desired environment variables
	for k, v := range f.Accents.Env {
		args = append(args, "--env", k+"="+v)
	}

	// Unroll command args
	args = append(args, f.Accents.Entrypoint...)

	// Prepare command to exec
	cmd := exec.Command("nsinit", args...)

	cmd.Stdin = nil
	cmd.Stdout = outS
	cmd.Stderr = errS

	// Prepare filesystem
	transmat := util.DefaultTransmat()
	assembly := util.ProvisionInputs(
		transmat,
		util.BestAssembler(),
		f.Inputs, rootfs, journal,
	)
	defer assembly.Teardown() // What ever happens: Disassemble filesystem
	util.ProvisionOutputs(f.Outputs, rootfs, journal)

	// launch execution.
	// transform gosh's typed errors to repeatr's hierarchical errors.
	// this is... not untroubled code: since we're invoking a helper that's then
	//  proxying the exec even further, most errors are fatal (the mapping here is
	//   very different than in e.g. chroot executor, and provides much less meaning).
	var proc gosh.Proc
	try.Do(func() {
		proc = gosh.ExecProcCmd(cmd)
	}).CatchAll(func(err error) {
		switch err.(type) {
		case gosh.NoSuchCommandError:
			panic(executor.ConfigError.New("nsinit binary is missing"))
		case gosh.NoArgumentsError:
			panic(executor.UnknownError.Wrap(err))
		case gosh.NoSuchCwdError:
			panic(executor.UnknownError.Wrap(err))
		case gosh.ProcMonitorError:
			panic(executor.TaskExecError.Wrap(err))
		default:
			panic(executor.UnknownError.Wrap(err))
		}
	}).Done()

	// Wait for the job to complete
	// REVIEW: consider exposing `gosh.Proc`'s interface as part of repeatr's job tracking api?
	result.ExitCode = proc.GetExitCode()

	// Horrifyingly ambiguous attempts to detect failure modes from inside nsinit.
	// This can only be made correct by pushing patches into nsinit to use another channel for control data reporting that is completely separated from user data flows.
	// (Or, arguably, putting another layer of control processes as the first parent inside nsinit, but that's ducktape within a ducktape mesh; let's not.)
	// Certain program outputs may be incorrectly attributed as launch failure, though this should be... "unlikely".
	// Also note that if we ever switch to non-blocking execution, this will become even more of a mess: we won't be able to tell if exec failed, esp. in the case of e.g. a long running process with no output, and so we won't know when it's safe to return.

	// TODO handle the following leading strings:
	// - "exec: \"%s\": executable file not found in $PATH\n"
	// - "no such file or directory\n"
	// this will probably require rejiggering a whole bunch of stuff so that the streamer is reachable down here.

	// Save outputs
	result.Outputs = util.PreserveOutputs(transmat, f.Outputs, rootfs, journal)
}
// Execute a formula in a specified directory. MAY PANIC.
func (e *Executor) Execute(f def.Formula, j def.Job, d string, result *def.JobResult, stdin io.Reader, outS, errS io.WriteCloser, journal log15.Logger) {
	// Prepare filesystem
	rootfs := filepath.Join(d, "rootfs")
	transmat := util.DefaultTransmat()
	assembly := util.ProvisionInputs(
		transmat,
		util.BestAssembler(),
		f.Inputs, rootfs, journal,
	)
	defer assembly.Teardown() // What ever happens: Disassemble filesystem
	util.ProvisionOutputs(f.Outputs, rootfs, journal)

	// chroot's are pretty easy.
	cmdName := f.Accents.Entrypoint[0]
	cmd := exec.Command(cmdName, f.Accents.Entrypoint[1:]...)
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Chroot:    rootfs,
		Pdeathsig: syscall.SIGKILL,
	}

	// except handling cwd is a little odd.
	// see comments in gosh tests with chroot for information about the odd behavior we're hacking around here;
	// we're comfortable making this special check here, but not upstreaming it to gosh, because in our context we "know" we're not racing anyone.
	if externalCwdStat, err := os.Stat(filepath.Join(rootfs, f.Accents.Cwd)); err != nil {
		panic(executor.TaskExecError.New("cannot set cwd to %q: %s", f.Accents.Cwd, err.(*os.PathError).Err))
	} else if !externalCwdStat.IsDir() {
		panic(executor.TaskExecError.New("cannot set cwd to %q: not a dir", f.Accents.Cwd))
	}
	cmd.Dir = f.Accents.Cwd

	// set env.
	// initialization already required by earlier 'validate' calls.
	cmd.Env = envToSlice(f.Accents.Env)

	cmd.Stdin = stdin
	cmd.Stdout = outS
	cmd.Stderr = errS

	// launch execution.
	// transform gosh's typed errors to repeatr's hierarchical errors.
	var proc gosh.Proc
	try.Do(func() {
		proc = gosh.ExecProcCmd(cmd)
	}).CatchAll(func(err error) {
		switch err.(type) {
		case gosh.NoSuchCommandError:
			panic(executor.NoSuchCommandError.Wrap(err))
		case gosh.NoArgumentsError:
			panic(executor.NoSuchCommandError.Wrap(err))
		case gosh.NoSuchCwdError: // included for clarity and completeness, but we'll never actually see this; see comments in gosh about the interaction of chroot and cwd error handling.
			panic(executor.TaskExecError.Wrap(err))
		case gosh.ProcMonitorError:
			panic(executor.TaskExecError.Wrap(err))
		default:
			panic(executor.UnknownError.Wrap(err))
		}
	}).Done()

	// Wait for the job to complete
	// REVIEW: consider exposing `gosh.Proc`'s interface as part of repeatr's job tracking api?
	result.ExitCode = proc.GetExitCode()

	// Save outputs
	result.Outputs = util.PreserveOutputs(transmat, f.Outputs, rootfs, journal)
}