mirror of
https://github.com/cirruslabs/orchard.git
synced 2026-10-03 21:43:06 +02:00
* Support dynamically updating Softnet policy * Use cmpopts.EquateEmpty() when comparing two VMSpecs
104 lines
2.3 KiB
Go
104 lines
2.3 KiB
Go
package base
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"github.com/cirruslabs/orchard/internal/worker/vmmanager"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func Cmd(
|
|
ctx context.Context,
|
|
logger *zap.SugaredLogger,
|
|
commandName string,
|
|
args ...string,
|
|
) (string, string, error) {
|
|
return CmdWithExtraFiles(ctx, logger, commandName, nil, args...)
|
|
}
|
|
|
|
func CmdWithExtraFiles(
|
|
ctx context.Context,
|
|
logger *zap.SugaredLogger,
|
|
commandName string,
|
|
extraFiles []*os.File,
|
|
args ...string,
|
|
) (string, string, error) {
|
|
cmd := exec.CommandContext(ctx, commandName, args...)
|
|
|
|
cmd.ExtraFiles = extraFiles
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
|
|
cmd.Stdout = &stdout
|
|
cmd.Stderr = &stderr
|
|
|
|
logger.Debugf("running '%s %s'", commandName, strings.Join(args, " "))
|
|
err := cmd.Start()
|
|
for _, file := range extraFiles {
|
|
_ = file.Close()
|
|
}
|
|
if err == nil {
|
|
err = cmd.Wait()
|
|
}
|
|
|
|
if err != nil {
|
|
if errors.Is(err, exec.ErrNotFound) {
|
|
return "", "", fmt.Errorf("%s command not found in PATH, make sure %s is installed: %w",
|
|
commandName, strings.ToTitle(commandName), err)
|
|
}
|
|
|
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
select {
|
|
case <-ctx.Done():
|
|
// Do not log an error because it's the user's intent to cancel this VM operation
|
|
default:
|
|
logger.Warnf(
|
|
"'%s %s' failed with exit code %d: %s",
|
|
commandName, strings.Join(args, " "),
|
|
exitErr.ExitCode(), firstNonEmptyLine(stderr.String(), stdout.String()),
|
|
)
|
|
}
|
|
|
|
// Preserve the exit status while adding the command-specific output.
|
|
err = fmt.Errorf("%s command failed: %q: %w", commandName,
|
|
firstNonEmptyLine(stderr.String(), stdout.String()), exitErr)
|
|
}
|
|
}
|
|
|
|
return stdout.String(), stderr.String(), err
|
|
}
|
|
|
|
func List(ctx context.Context, logger *zap.SugaredLogger, commandName string) ([]vmmanager.VMInfo, error) {
|
|
output, _, err := Cmd(ctx, logger, commandName, "list", "--format", "json")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var entries []vmmanager.VMInfo
|
|
|
|
if err := json.Unmarshal([]byte(output), &entries); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return entries, nil
|
|
}
|
|
|
|
func firstNonEmptyLine(outputs ...string) string {
|
|
for _, output := range outputs {
|
|
for _, line := range strings.Split(output, "\n") {
|
|
if line != "" {
|
|
return line
|
|
}
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|