Skip to content

Commit 4f4a7c0

Browse files
committed
Fixup as per comments
Signed-off-by: Benjamin Leggett <[email protected]>
1 parent 455c465 commit 4f4a7c0

File tree

4 files changed

+370
-84
lines changed

4 files changed

+370
-84
lines changed

Diff for: SPEC.md

+76-7
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ A network configuration consists of a JSON object with the following keys:
111111
- `cniVersions` (string list): List of all CNI versions which this configuration supports. See [version selection](#version-selection) below.
112112
- `name` (string): Network name. This should be unique across all network configurations on a host (or other administrative domain). Must start with an alphanumeric character, optionally followed by any combination of one or more alphanumeric characters, underscore, dot (.) or hyphen (-). Must not contain characters disallowed in file paths. A path segment (such as a filesystem directory) with the same name as the network name, containing one or more plugin configuration JSON objects for that network, should exist at the same level as the network configuration object itself.
113113
- `disableCheck` (boolean): Either `true` or `false`. If `disableCheck` is `true`, runtimes must not call `CHECK` for this network configuration list. This allows an administrator to prevent `CHECK`ing where a combination of plugins is known to return spurious errors.
114-
<!-- - `plugins` (list): A list of CNI plugins and their configuration, which is a list of plugin configuration objects. -->
114+
- `loadPluginsFromFolder` (boolean): Either `true` or `false`. If `true`, indicates [plugin configuration objects](#plugin-configuration-objects) should be loaded from a sibling folder with the same name as the network `name` field. These sibling folders should exist at the same path as the network configuration itself. Any valid plugin configuration objects within the sibling folder will be appended to the final list of plugins for that network. If `plugins` is not present in the network configuration, `loadPluginsFromFolder` must be present, and set to true.
115+
- `plugins` (list): A list of inlined [plugin configuration objects](#plugin-configuration-objects). If this key is populated with inlined plugin objects, and `loadPluginsFromFolder` is true, the final set of plugins for a network must consist of all the plugin objects in this list and the all the plugins loaded from the sibling folder with the same name as the network.
115116

116117
#### Plugin configuration objects:
117118
All plugin configuration objects present in a directory with the same name as a valid network configuration must be parsed, and each plugin with a parsable configuration object must be invoked.
@@ -147,13 +148,14 @@ Plugins that consume any of these configuration keys should respect their intend
147148
Plugins may define additional fields that they accept and may generate an error if called with unknown fields. Runtimes must preserve unknown fields in plugin configuration objects when transforming for execution.
148149

149150
#### Example configuration
150-
151+
Network configuration with no inlined plugin confs, and two loaded plugin confs:
151152
`/etc/cni/net.d/10-dbnet.conf`:
152153
```jsonc
153154
{
154155
"cniVersion": "1.1.0",
155156
"cniVersions": ["0.3.1", "0.4.0", "1.0.0", "1.1.0"],
156157
"name": "dbnet",
158+
"loadPluginsFromFolder": true,
157159
}
158160
```
159161

@@ -177,7 +179,7 @@ Plugins may define additional fields that they accept and may generate an error
177179
"dns": {
178180
"nameservers": [ "10.1.0.1" ]
179181
}
180-
},
182+
}
181183
```
182184

183185
`/etc/cni/net.d/dbnet/10-tuning.conf`:
@@ -190,16 +192,83 @@ Plugins may define additional fields that they accept and may generate an error
190192
"sysctl": {
191193
"net.core.somaxconn": "500"
192194
}
193-
},
195+
}
194196
```
195197

196-
`/etc/cni/net.d/dbnet/15-portmap.conf`:
198+
Network configuration with one inlined plugin conf, and one loaded plugin conf:
199+
`/etc/cni/net.d/10-dbnet.conf`:
197200
```jsonc
198201
{
199-
"type": "portmap",
200-
"capabilities": {"portMappings": true}
202+
"cniVersion": "1.1.0",
203+
"cniVersions": ["0.3.1", "0.4.0", "1.0.0", "1.1.0"],
204+
"name": "dbnet",
205+
"loadPluginsFromFolder": true,
206+
plugins: [
207+
{
208+
"type": "bridge",
209+
// plugin specific parameters
210+
"bridge": "cni0",
211+
"keyA": ["some more", "plugin specific", "configuration"],
212+
213+
"ipam": {
214+
"type": "host-local",
215+
// ipam specific
216+
"subnet": "10.1.0.0/16",
217+
"gateway": "10.1.0.1",
218+
"routes": [
219+
{"dst": "0.0.0.0/0"}
220+
]
221+
},
222+
"dns": {
223+
"nameservers": [ "10.1.0.1" ]
224+
}
225+
}
226+
]
227+
}
228+
```
229+
230+
`/etc/cni/net.d/dbnet/10-tuning.conf`:
231+
```jsonc
232+
{
233+
"type": "tuning",
234+
"capabilities": {
235+
"mac": true
236+
},
237+
"sysctl": {
238+
"net.core.somaxconn": "500"
239+
}
201240
}
241+
```
202242

243+
Network configuration with one inlined plugin conf, and no loaded plugin conf:
244+
`/etc/cni/net.d/10-dbnet.conf`:
245+
```jsonc
246+
{
247+
"cniVersion": "1.1.0",
248+
"cniVersions": ["0.3.1", "0.4.0", "1.0.0", "1.1.0"],
249+
"name": "dbnet",
250+
"plugins": [
251+
{
252+
"type": "bridge",
253+
// plugin specific parameters
254+
"bridge": "cni0",
255+
"keyA": ["some more", "plugin specific", "configuration"],
256+
257+
"ipam": {
258+
"type": "host-local",
259+
// ipam specific
260+
"subnet": "10.1.0.0/16",
261+
"gateway": "10.1.0.1",
262+
"routes": [
263+
{"dst": "0.0.0.0/0"}
264+
]
265+
},
266+
"dns": {
267+
"nameservers": [ "10.1.0.1" ]
268+
}
269+
}
270+
]
271+
}
203272
```
204273

205274
### Version considerations

Diff for: libcni/api.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,12 @@ type PluginConfig struct {
7676
}
7777

7878
type NetworkConfigList struct {
79-
Name string
80-
CNIVersion string
81-
DisableCheck bool
82-
Plugins []*PluginConfig
83-
Bytes []byte
79+
Name string
80+
CNIVersion string
81+
DisableCheck bool
82+
LoadPluginsFromFolder bool
83+
Plugins []*PluginConfig
84+
Bytes []byte
8485
}
8586

8687
type NetworkAttachment struct {

Diff for: libcni/conf.go

+66-42
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,20 @@ func NetworkPluginConfFromBytes(pluginConfBytes []byte) (*PluginConfig, error) {
6767
// Given a path to a directory containing a network configuration, and the name of a network,
6868
// loads all plugin definitions found at path `networkConfPath/networkName/*.conf`
6969
func NetworkPluginConfsFromFiles(networkConfPath, networkName string) ([]*PluginConfig, error) {
70-
pluginConfFiles, err := ConfFiles(filepath.Join(networkConfPath, networkName), []string{".conf"})
70+
var pConfs []*PluginConfig
71+
72+
pluginConfPath := filepath.Join(networkConfPath, networkName)
73+
74+
pluginConfFiles, err := ConfFiles(pluginConfPath, []string{".conf"})
7175
switch {
7276
case err != nil:
73-
return nil, fmt.Errorf("failed to read plugin config file: %w", err)
77+
return nil, fmt.Errorf("failed to read plugin config files in %s: %w", pluginConfPath, err)
7478
case len(pluginConfFiles) == 0:
7579
// Having 0 plugins for a given network is not necessarily a problem,
76-
// but return as error for caller to decide.
77-
return nil, fmt.Errorf("no plugin config found in %s: %w", networkConfPath, err)
80+
// but return as error for caller to decide, since they tried to load
81+
return nil, fmt.Errorf("no plugin config found in %s", pluginConfPath)
7882
}
7983

80-
var pConfs []*PluginConfig
8184
for _, pluginConfFile := range pluginConfFiles {
8285
pluginConfBytes, err := os.ReadFile(pluginConfFile)
8386
if err != nil {
@@ -92,6 +95,7 @@ func NetworkPluginConfsFromFiles(networkConfPath, networkName string) ([]*Plugin
9295
return pConfs, nil
9396
}
9497

98+
9599
func NetworkConfFromBytes(confBytes []byte) (*NetworkConfigList, error) {
96100
rawList := make(map[string]interface{})
97101
if err := json.Unmarshal(confBytes, &rawList); err != nil {
@@ -176,15 +180,52 @@ func NetworkConfFromBytes(confBytes []byte) (*NetworkConfigList, error) {
176180
}
177181
}
178182

183+
loadPluginsFromFolder := false
184+
if rawLoadCheck, ok := rawList["loadPluginsFromFolder"]; ok {
185+
loadPluginsFromFolder, ok = rawLoadCheck.(bool)
186+
if !ok {
187+
return nil, fmt.Errorf("error parsing configuration list: invalid loadPluginsFromFolder type %T", rawLoadCheck)
188+
}
189+
}
190+
179191
list := &NetworkConfigList{
180192
Name: name,
181193
DisableCheck: disableCheck,
194+
LoadPluginsFromFolder: loadPluginsFromFolder,
182195
CNIVersion: cniVersion,
183196
Bytes: confBytes,
184197
}
185198

186-
if _, ok := rawList["plugins"]; ok {
187-
return nil, fmt.Errorf("error parsing configuration list: no 'plugins' key allowed, cniVersion %s must load plugins from subdirectories", version.Current())
199+
var plugins []interface{}
200+
plug, ok := rawList["plugins"]
201+
// We can have a `plugins` list key in the main conf,
202+
// We can also have `loadPluginsFromFolder == true`
203+
// They can both be present in the same config.
204+
// But if one of them is NOT present/false, the other *must* be there.
205+
if !ok && !loadPluginsFromFolder {
206+
return nil, fmt.Errorf("error parsing configuration list: `loadPluginsFromFolder` not true, and no 'plugins' key")
207+
} else if !ok && loadPluginsFromFolder {
208+
return list, nil
209+
}
210+
211+
plugins, ok = plug.([]interface{})
212+
if !ok {
213+
return nil, fmt.Errorf("error parsing configuration list: invalid 'plugins' type %T", plug)
214+
}
215+
if len(plugins) == 0 {
216+
return nil, fmt.Errorf("error parsing configuration list: no plugins in list")
217+
}
218+
219+
for i, conf := range plugins {
220+
newBytes, err := json.Marshal(conf)
221+
if err != nil {
222+
return nil, fmt.Errorf("failed to marshal plugin config %d: %w", i, err)
223+
}
224+
netConf, err := ConfFromBytes(newBytes)
225+
if err != nil {
226+
return nil, fmt.Errorf("failed to parse plugin config %d: %w", i, err)
227+
}
228+
list.Plugins = append(list.Plugins, netConf)
188229
}
189230
return list, nil
190231
}
@@ -195,31 +236,27 @@ func NetworkConfFromFile(filename string) (*NetworkConfigList, error) {
195236
return nil, fmt.Errorf("error reading %s: %w", filename, err)
196237
}
197238

198-
// TODO
199239
conf, err := NetworkConfFromBytes(bytes)
200240
if err != nil {
201241
return nil, err
202242
}
203243

204-
plugins, err := NetworkPluginConfsFromFiles(filepath.Dir(filename), conf.Name)
205-
if err != nil {
206-
return nil, err
244+
if conf.LoadPluginsFromFolder {
245+
plugins, err := NetworkPluginConfsFromFiles(filepath.Dir(filename), conf.Name)
246+
if err != nil {
247+
return nil, err
248+
}
249+
conf.Plugins = append(conf.Plugins, plugins...)
207250
}
208251

209-
conf.Plugins = plugins
210-
211252
return conf, nil
212253
}
213254

214255
// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions
215-
func ConfFromBytes(bytes []byte) (*PluginConfig, error) {
256+
func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
216257
return NetworkPluginConfFromBytes(bytes)
217258
}
218259

219-
// TODO Are we ok, at this point, cutting a new major version of libCNI and dropping the non-list CNI config?
220-
//
221-
// The non-list format was dropped in 1.0 and so I feel we should.
222-
//
223260
// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions
224261
func ConfFromFile(filename string) (*NetworkConfig, error) {
225262
bytes, err := os.ReadFile(filename)
@@ -292,14 +329,23 @@ func LoadConf(dir, name string) (*NetworkConfig, error) {
292329

293330
// Deprecated: Use NetworkConfXXX and NetworkPluginXXX functions
294331
func LoadConfList(dir, name string) (*NetworkConfigList, error) {
332+
return LoadNetworkConf(dir, name)
333+
}
334+
335+
// LoadNetworkConf looks at all the network configs in a given dir,
336+
// loads and parses them all, and returns the first one with an extension of `.conf`
337+
// that matches the provided network name predicate.
338+
func LoadNetworkConf(dir, name string) (*NetworkConfigList, error) {
339+
// TODO this .conflist/.conf extension thing is confusing and inexact
340+
// for implementors. We should pick one extension for everything and stick with it.
295341
files, err := ConfFiles(dir, []string{".conflist"})
296342
if err != nil {
297343
return nil, err
298344
}
299345
sort.Strings(files)
300346

301347
for _, confFile := range files {
302-
conf, err := ConfListFromFile(confFile)
348+
conf, err := NetworkConfFromFile(confFile)
303349
if err != nil {
304350
return nil, err
305351
}
@@ -308,7 +354,7 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
308354
}
309355
}
310356

311-
// Try and load a network configuration file (instead of list)
357+
// Deprecated: Try and load a network configuration file (instead of list)
312358
// from the same name, then upconvert.
313359
singleConf, err := LoadConf(dir, name)
314360
if err != nil {
@@ -324,28 +370,6 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
324370
return ConfListFromConf(singleConf)
325371
}
326372

327-
// LoadNetworkConf looks at all the network configs in a given dir,
328-
// loads and parses them all, and returns the first one that matches the provided
329-
// network name predicate.
330-
func LoadNetworkConf(dir, name string) (*NetworkConfigList, error) {
331-
files, err := ConfFiles(dir, []string{".conf"})
332-
if err != nil {
333-
return nil, err
334-
}
335-
sort.Strings(files)
336-
337-
for _, confFile := range files {
338-
conf, err := NetworkConfFromFile(confFile)
339-
if err != nil {
340-
return nil, err
341-
}
342-
if conf.Name == name {
343-
return conf, nil
344-
}
345-
}
346-
return nil, fmt.Errorf("no netconfig found in %q with name %s", dir, name)
347-
}
348-
349373
// InjectConf takes a PluginConfig and inserts additional values into it, ensuring the result is serializable.
350374
func InjectConf(original *PluginConfig, newValues map[string]interface{}) (*PluginConfig, error) {
351375
config := make(map[string]interface{})

0 commit comments

Comments
 (0)