1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
package netavark
import (
"encoding/json"
"errors"
"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,
}
}
// 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 execNetavark(binary string, args []string, stdin, result interface{}) error {
stdinR, stdinW, err := os.Pipe()
if err != nil {
return newNetavarkError("failed to create stdin pipe", err)
}
defer stdinR.Close()
stdoutR, stdoutW, err := os.Pipe()
if err != nil {
return newNetavarkError("failed to create stdout pipe", err)
}
defer stdoutR.Close()
defer stdoutW.Close()
cmd := exec.Command(binary, args...)
// connect the pipes to stdin and stdout
cmd.Stdin = stdinR
cmd.Stdout = stdoutW
// connect stderr to the podman stderr for logging
cmd.Stderr = os.Stderr
// 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)
stdinW.Close()
if err != nil {
return newNetavarkError("failed to encode stdin data", err)
}
dec := json.NewDecoder(stdoutR)
err = cmd.Wait()
stdoutW.Close()
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
}
|