Skip to content

Commit 08c3022

Browse files
authored
Merge pull request moby#51634 from robmry/nri-config
NRI: add daemon.json/command line options
2 parents 51f2a64 + a230544 commit 08c3022

File tree

4 files changed

+217
-0
lines changed

4 files changed

+217
-0
lines changed

daemon/command/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ func installCommonConfigFlags(conf *config.Config, flags *pflag.FlagSet) {
7979

8080
flags.Var(opts.NewNamedListOptsRef("cdi-spec-dirs", &conf.CDISpecDirs, nil), "cdi-spec-dir", "CDI specification directories to use")
8181

82+
flags.Var(opts.NewNamedNRIOptsRef(&conf.NRIOpts), "nri-opts", "Node Resource Interface configuration")
83+
8284
// Deprecated flags / options
8385
flags.BoolVarP(&conf.AutoRestart, "restart", "r", true, "--restart on the daemon has been deprecated in favor of --restart policies on docker run")
8486
_ = flags.MarkDeprecated("restart", "Please use a restart policy on docker run")

daemon/config/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ var flatOptions = map[string]bool{
8888
"default-ulimits": true,
8989
"features": true,
9090
"builder": true,
91+
"nri-opts": true,
9192
}
9293

9394
// skipValidateOptions contains configuration keys
@@ -284,6 +285,9 @@ type CommonConfig struct {
284285
// CDISpecDirs is a list of directories in which CDI specifications can be found.
285286
CDISpecDirs []string `json:"cdi-spec-dirs,omitempty"`
286287

288+
// NRIOpts defines configuration for NRI (Node Resource Interface).
289+
NRIOpts opts.NRIOpts `json:"nri-opts,omitempty"`
290+
287291
// The minimum API version provided by the daemon. Defaults to [defaultMinAPIVersion].
288292
//
289293
// The DOCKER_MIN_API_VERSION allows overriding the minimum API version within

daemon/pkg/opts/nri_opts.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package opts
2+
3+
import (
4+
"bytes"
5+
"encoding/csv"
6+
"encoding/json"
7+
"fmt"
8+
"strconv"
9+
"strings"
10+
)
11+
12+
type NRIOpts struct {
13+
Enable bool `json:"enable,omitempty"`
14+
PluginPath string `json:"plugin-path,omitempty"`
15+
PluginConfigPath string `json:"plugin-config-path,omitempty"`
16+
SocketPath string `json:"socket-path,omitempty"`
17+
}
18+
19+
func (c *NRIOpts) UnmarshalJSON(raw []byte) error {
20+
dec := json.NewDecoder(bytes.NewReader(raw))
21+
dec.DisallowUnknownFields()
22+
type nc *NRIOpts // prevent recursion
23+
if err := dec.Decode(nc(c)); err != nil {
24+
return err
25+
}
26+
return nil
27+
}
28+
29+
// NamedNRIOpts is a NamedOption and flags.Value for NRI configuration parsing.
30+
type NamedNRIOpts struct {
31+
Val *NRIOpts
32+
}
33+
34+
func NewNamedNRIOptsRef(val *NRIOpts) *NamedNRIOpts {
35+
return &NamedNRIOpts{Val: val}
36+
}
37+
38+
func (p *NamedNRIOpts) Set(value string) error {
39+
csvReader := csv.NewReader(strings.NewReader(value))
40+
fields, err := csvReader.Read()
41+
if err != nil {
42+
return err
43+
}
44+
45+
for _, field := range fields {
46+
key, val, _ := strings.Cut(field, "=")
47+
switch key {
48+
case "enable":
49+
// Assume "enable=true" if no value given, so that "--nri enable" works.
50+
if val == "" {
51+
val = "true"
52+
}
53+
en, err := strconv.ParseBool(val)
54+
if err != nil {
55+
return fmt.Errorf("invalid value for NRI enable %q: %w", val, err)
56+
}
57+
p.Val.Enable = en
58+
case "plugin-path":
59+
p.Val.PluginPath = val
60+
case "plugin-config-path":
61+
p.Val.PluginConfigPath = val
62+
case "socket-path":
63+
p.Val.SocketPath = val
64+
default:
65+
return fmt.Errorf("unexpected key '%s' in '%s'", key, field)
66+
}
67+
}
68+
return nil
69+
}
70+
71+
// Type returns the type of this option
72+
func (p *NamedNRIOpts) Type() string {
73+
return "nri-opts"
74+
}
75+
76+
// String returns a string repr of this option
77+
func (p *NamedNRIOpts) String() string {
78+
vals := []string{fmt.Sprintf("enable=%v", p.Val.Enable)}
79+
if p.Val.PluginPath != "" {
80+
vals = append(vals, "plugin-path="+p.Val.PluginPath)
81+
}
82+
if p.Val.PluginConfigPath != "" {
83+
vals = append(vals, "plugin-config-path="+p.Val.PluginConfigPath)
84+
}
85+
if p.Val.SocketPath != "" {
86+
vals = append(vals, "socket-path="+p.Val.SocketPath)
87+
}
88+
return strings.Join(vals, ",")
89+
}
90+
91+
// Name returns the flag name of this option
92+
func (p *NamedNRIOpts) Name() string {
93+
return "nri-opts"
94+
}

daemon/pkg/opts/nri_opts_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package opts
2+
3+
import (
4+
"testing"
5+
6+
"gotest.tools/v3/assert"
7+
is "gotest.tools/v3/assert/cmp"
8+
)
9+
10+
func TestNRIOptsJSON(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
json string
14+
expErr string
15+
expNRI NRIOpts
16+
}{
17+
{
18+
name: "no config",
19+
expNRI: NRIOpts{},
20+
},
21+
{
22+
name: "json enable",
23+
json: `{"enable": true}`,
24+
expNRI: NRIOpts{
25+
Enable: true,
26+
},
27+
},
28+
{
29+
name: "json enable with paths",
30+
json: `{"enable": true, "plugin-path": "/foo", "plugin-config-path": "/bar", "socket-path": "/baz"}`,
31+
expNRI: NRIOpts{
32+
Enable: true,
33+
PluginPath: "/foo",
34+
PluginConfigPath: "/bar",
35+
SocketPath: "/baz",
36+
},
37+
},
38+
{
39+
name: "json unknown field",
40+
json: `{"foo": "bar"}`,
41+
expErr: "unknown field \"foo\"",
42+
},
43+
}
44+
45+
for _, tc := range tests {
46+
t.Run(tc.name, func(t *testing.T) {
47+
var nc NRIOpts
48+
if tc.json != "" {
49+
err := nc.UnmarshalJSON([]byte(tc.json))
50+
if tc.expErr != "" {
51+
assert.Check(t, is.ErrorContains(err, tc.expErr))
52+
return
53+
}
54+
assert.Check(t, err)
55+
}
56+
assert.Check(t, is.Equal(nc, tc.expNRI))
57+
})
58+
}
59+
}
60+
61+
func TestNRIOptsCmd(t *testing.T) {
62+
tests := []struct {
63+
name string
64+
cmd string
65+
expErr string
66+
expNRI NRIOpts
67+
expString string
68+
}{
69+
{
70+
name: "cmd enable",
71+
cmd: "enable=true",
72+
expNRI: NRIOpts{
73+
Enable: true,
74+
},
75+
expString: "enable=true",
76+
},
77+
{
78+
name: "cmd enable with paths",
79+
cmd: "enable=true,plugin-path=/foo,plugin-config-path=/bar,socket-path=/baz",
80+
expNRI: NRIOpts{
81+
Enable: true,
82+
PluginPath: "/foo",
83+
PluginConfigPath: "/bar",
84+
SocketPath: "/baz",
85+
},
86+
expString: "enable=true,plugin-path=/foo,plugin-config-path=/bar,socket-path=/baz",
87+
},
88+
{
89+
// "--nri-opts enable"
90+
name: "cmd enable no value",
91+
cmd: `enable`,
92+
expNRI: NRIOpts{
93+
Enable: true,
94+
},
95+
expString: "enable=true",
96+
},
97+
{
98+
name: "cmd unknown field",
99+
cmd: `foo=bar`,
100+
expErr: "unexpected key 'foo' in 'foo=bar'",
101+
},
102+
}
103+
104+
for _, tc := range tests {
105+
t.Run(tc.name, func(t *testing.T) {
106+
var nc NRIOpts
107+
nriOpt := NewNamedNRIOptsRef(&nc)
108+
err := nriOpt.Set(tc.cmd)
109+
if tc.expErr != "" {
110+
assert.Check(t, is.ErrorContains(err, tc.expErr))
111+
return
112+
}
113+
assert.Check(t, err)
114+
assert.Check(t, is.Equal(nriOpt.String(), tc.expString))
115+
})
116+
}
117+
}

0 commit comments

Comments
 (0)