Skip to content

Commit a33b3b3

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

File tree

5 files changed

+370
-89
lines changed

5 files changed

+370
-89
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: cnitool/cnitool.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func main() {
6666
if netdir == "" {
6767
netdir = DefaultNetDir
6868
}
69-
netconf, err := libcni.LoadConfList(netdir, os.Args[2])
69+
netconf, err := libcni.LoadNetworkConf(netdir, os.Args[2])
7070
if err != nil {
7171
exit(err)
7272
}

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

+69-46
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 {
@@ -176,15 +179,52 @@ func NetworkConfFromBytes(confBytes []byte) (*NetworkConfigList, error) {
176179
}
177180
}
178181

182+
loadPluginsFromFolder := false
183+
if rawLoadCheck, ok := rawList["loadPluginsFromFolder"]; ok {
184+
loadPluginsFromFolder, ok = rawLoadCheck.(bool)
185+
if !ok {
186+
return nil, fmt.Errorf("error parsing configuration list: invalid loadPluginsFromFolder type %T", rawLoadCheck)
187+
}
188+
}
189+
179190
list := &NetworkConfigList{
180-
Name: name,
181-
DisableCheck: disableCheck,
182-
CNIVersion: cniVersion,
183-
Bytes: confBytes,
191+
Name: name,
192+
DisableCheck: disableCheck,
193+
LoadPluginsFromFolder: loadPluginsFromFolder,
194+
CNIVersion: cniVersion,
195+
Bytes: confBytes,
196+
}
197+
198+
var plugins []interface{}
199+
plug, ok := rawList["plugins"]
200+
// We can have a `plugins` list key in the main conf,
201+
// We can also have `loadPluginsFromFolder == true`
202+
// They can both be present in the same config.
203+
// But if one of them is NOT present/false, the other *must* be there.
204+
if !ok && !loadPluginsFromFolder {
205+
return nil, fmt.Errorf("error parsing configuration list: `loadPluginsFromFolder` not true, and no 'plugins' key")
206+
} else if !ok && loadPluginsFromFolder {
207+
return list, nil
184208
}
185209

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())
210+
plugins, ok = plug.([]interface{})
211+
if !ok {
212+
return nil, fmt.Errorf("error parsing configuration list: invalid 'plugins' type %T", plug)
213+
}
214+
if len(plugins) == 0 {
215+
return nil, fmt.Errorf("error parsing configuration list: no plugins in list")
216+
}
217+
218+
for i, conf := range plugins {
219+
newBytes, err := json.Marshal(conf)
220+
if err != nil {
221+
return nil, fmt.Errorf("failed to marshal plugin config %d: %w", i, err)
222+
}
223+
netConf, err := ConfFromBytes(newBytes)
224+
if err != nil {
225+
return nil, fmt.Errorf("failed to parse plugin config %d: %w", i, err)
226+
}
227+
list.Plugins = append(list.Plugins, netConf)
188228
}
189229
return list, nil
190230
}
@@ -195,31 +235,27 @@ func NetworkConfFromFile(filename string) (*NetworkConfigList, error) {
195235
return nil, fmt.Errorf("error reading %s: %w", filename, err)
196236
}
197237

198-
// TODO
199238
conf, err := NetworkConfFromBytes(bytes)
200239
if err != nil {
201240
return nil, err
202241
}
203242

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

209-
conf.Plugins = plugins
210-
211251
return conf, nil
212252
}
213253

214254
// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions
215-
func ConfFromBytes(bytes []byte) (*PluginConfig, error) {
255+
func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
216256
return NetworkPluginConfFromBytes(bytes)
217257
}
218258

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-
//
223259
// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions
224260
func ConfFromFile(filename string) (*NetworkConfig, error) {
225261
bytes, err := os.ReadFile(filename)
@@ -292,14 +328,23 @@ func LoadConf(dir, name string) (*NetworkConfig, error) {
292328

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

301346
for _, confFile := range files {
302-
conf, err := ConfListFromFile(confFile)
347+
conf, err := NetworkConfFromFile(confFile)
303348
if err != nil {
304349
return nil, err
305350
}
@@ -308,7 +353,7 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
308353
}
309354
}
310355

311-
// Try and load a network configuration file (instead of list)
356+
// Deprecated: Try and load a network configuration file (instead of list)
312357
// from the same name, then upconvert.
313358
singleConf, err := LoadConf(dir, name)
314359
if err != nil {
@@ -324,28 +369,6 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
324369
return ConfListFromConf(singleConf)
325370
}
326371

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-
349372
// InjectConf takes a PluginConfig and inserts additional values into it, ensuring the result is serializable.
350373
func InjectConf(original *PluginConfig, newValues map[string]interface{}) (*PluginConfig, error) {
351374
config := make(map[string]interface{})

0 commit comments

Comments
 (0)