162 lines
4.2 KiB
Go
162 lines
4.2 KiB
Go
// +build linux
|
|
|
|
package netavark
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"strconv"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type netavarkError struct {
|
|
exitCode int
|
|
// Set the json key to "error" so we can directly unmarshal into this struct
|
|
Msg string `json:"error"`
|
|
err error
|
|
}
|
|
|
|
func (e *netavarkError) Error() string {
|
|
ec := ""
|
|
// only add the exit code the the error message if we have at least info log level
|
|
// the normal user does not need to care about the number
|
|
if e.exitCode > 0 && logrus.IsLevelEnabled(logrus.InfoLevel) {
|
|
ec = " (exit code " + strconv.Itoa(e.exitCode) + ")"
|
|
}
|
|
msg := "netavark" + ec
|
|
if len(msg) > 0 {
|
|
msg += ": " + e.Msg
|
|
}
|
|
if e.err != nil {
|
|
msg += ": " + e.err.Error()
|
|
}
|
|
return msg
|
|
}
|
|
|
|
func (e *netavarkError) Unwrap() error {
|
|
return e.err
|
|
}
|
|
|
|
func newNetavarkError(msg string, err error) error {
|
|
return &netavarkError{
|
|
Msg: msg,
|
|
err: err,
|
|
}
|
|
}
|
|
|
|
// Type to implement io.Writer interface
|
|
// This will write the logrus at info level
|
|
type logrusNetavarkWriter struct{}
|
|
|
|
func (l *logrusNetavarkWriter) Write(b []byte) (int, error) {
|
|
logrus.Info("netavark: ", string(b))
|
|
return len(b), nil
|
|
}
|
|
|
|
// getRustLogEnv returns the RUST_LOG env var based on the current logrus level
|
|
func getRustLogEnv() string {
|
|
level := logrus.GetLevel().String()
|
|
// rust env_log uses warn instead of warning
|
|
if level == "warning" {
|
|
level = "warn"
|
|
}
|
|
// the rust netlink library is very verbose
|
|
// make sure to only log netavark logs
|
|
return "RUST_LOG=netavark=" + level
|
|
}
|
|
|
|
// execNetavark will execute netavark with the following arguments
|
|
// It takes the path to the binary, the list of args and an interface which is
|
|
// marshaled to json and send via stdin to netavark. The result interface is
|
|
// used to marshal the netavark output into it. This can be nil.
|
|
// All errors return by this function should be of the type netavarkError
|
|
// to provide a helpful error message.
|
|
func (n *netavarkNetwork) execNetavark(args []string, stdin, result interface{}) error {
|
|
stdinR, stdinW, err := os.Pipe()
|
|
if err != nil {
|
|
return newNetavarkError("failed to create stdin pipe", err)
|
|
}
|
|
stdinWClosed := false
|
|
defer func() {
|
|
stdinR.Close()
|
|
if !stdinWClosed {
|
|
stdinW.Close()
|
|
}
|
|
}()
|
|
|
|
stdoutR, stdoutW, err := os.Pipe()
|
|
if err != nil {
|
|
return newNetavarkError("failed to create stdout pipe", err)
|
|
}
|
|
stdoutWClosed := false
|
|
defer func() {
|
|
stdoutR.Close()
|
|
if !stdoutWClosed {
|
|
stdoutW.Close()
|
|
}
|
|
}()
|
|
|
|
// connect stderr to the podman stderr for logging
|
|
var logWriter io.Writer = os.Stderr
|
|
if n.syslog {
|
|
// connect logrus to stderr as well so that the logs will be written to the syslog as well
|
|
logWriter = io.MultiWriter(logWriter, &logrusNetavarkWriter{})
|
|
}
|
|
|
|
cmd := exec.Command(n.netavarkBinary, args...)
|
|
// connect the pipes to stdin and stdout
|
|
cmd.Stdin = stdinR
|
|
cmd.Stdout = stdoutW
|
|
cmd.Stderr = logWriter
|
|
// set the netavark log level to the same as the podman
|
|
cmd.Env = append(os.Environ(), getRustLogEnv())
|
|
// if we run with debug log level lets also set RUST_BACKTRACE=1 so we can get the full stack trace in case of panics
|
|
if logrus.IsLevelEnabled(logrus.DebugLevel) {
|
|
cmd.Env = append(cmd.Env, "RUST_BACKTRACE=1")
|
|
}
|
|
|
|
err = cmd.Start()
|
|
if err != nil {
|
|
return newNetavarkError("failed to start process", err)
|
|
}
|
|
err = json.NewEncoder(stdinW).Encode(stdin)
|
|
// we have to close stdinW so netavark gets the EOF and does not hang forever
|
|
stdinW.Close()
|
|
stdinWClosed = true
|
|
if err != nil {
|
|
return newNetavarkError("failed to encode stdin data", err)
|
|
}
|
|
|
|
dec := json.NewDecoder(stdoutR)
|
|
|
|
err = cmd.Wait()
|
|
// we have to close stdoutW so we can decode the json without hanging forever
|
|
stdoutW.Close()
|
|
stdoutWClosed = true
|
|
if err != nil {
|
|
exitError := &exec.ExitError{}
|
|
if errors.As(err, &exitError) {
|
|
ne := &netavarkError{}
|
|
// lets disallow unknown fields to make sure we do not get some unexpected stuff
|
|
dec.DisallowUnknownFields()
|
|
// this will unmarshal the error message into the error struct
|
|
ne.err = dec.Decode(ne)
|
|
ne.exitCode = exitError.ExitCode()
|
|
return ne
|
|
}
|
|
return newNetavarkError("unexpected failure during execution", err)
|
|
}
|
|
|
|
if result != nil {
|
|
err = dec.Decode(result)
|
|
if err != nil {
|
|
return newNetavarkError("failed to decode result", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|