Merge pull request #373 from dcbw/conflist-runtime-config

spec,libcni: add support for injecting runtimeConfig into plugin stdin data
This commit is contained in:
Dan Williams 2017-03-02 11:17:00 -06:00 committed by GitHub
commit 75a5cf1bf0
7 changed files with 345 additions and 81 deletions

View File

@ -29,18 +29,18 @@ This method of passing information to a plugin is recommended when the following
* The configuration has specific meaning to the plugin (i.e. it's not just general meta data)
* the plugin is expected to act on the configuration or return an error if it can't
Dynamic information (i.e. data that a runtime fills out) should be placed in a `runtime_config` section.
Dynamic information (i.e. data that a runtime fills out) should be placed in a `runtimeConfig` section.
| Area | Purpose| Spec and Example | Runtime implementations | Plugin Implementations |
| ------ | ------ | ------ | ------ | ------ | ------ |
| port mappings | Pass mapping from ports on the host to ports in the container network namespace. | Operators can ask runtimes to pass port mapping information to plugins, by setting the following in the CNI config <pre>"capabilities": {port_mappings": true} </pre> Runtimes should fill in the actual port mappings when the config is passed to plugins. It should be placed in a new section of the config "runtime_config" e.g. <pre>"runtime_config": {<br /> "port_mappings" : [<br /> { "host_port": 8080, "container_port": 80, "protocol": "tcp" },<br /> { "host_port": 8000, "container_port": 8001, "protocol": "udp" }<br /> ]<br />}</pre> | none | none |
| port mappings | Pass mapping from ports on the host to ports in the container network namespace. | Operators can ask runtimes to pass port mapping information to plugins, by setting the following in the CNI config <pre>"capabilities": {"portMappings": true} </pre> Runtimes should fill in the actual port mappings when the config is passed to plugins. It should be placed in a new section of the config "runtimeConfig" e.g. <pre>"runtimeConfig": {<br /> "portMappings" : [<br /> { "hostPort": 8080, "containerPort": 80, "protocol": "tcp" },<br /> { "hostPort": 8000, "containerPort": 8001, "protocol": "udp" }<br /> ]<br />}</pre> | none | none |
For example, the configuration for a port mapping plugin might look like this to an operator (it should be included as part of a [network configuration list](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration-lists).
```json
{
"name" : "ExamplePlugin",
"type" : "port-mapper",
"capabilities": {"port_mappings": true}
"capabilities": {"portMappings": true}
}
```
@ -49,9 +49,9 @@ But the runtime would fill in the mappings so the plugin itself would receive so
{
"name" : "ExamplePlugin",
"type" : "port-mapper",
"runtime_config": {
"port_mappings": [
{"host_port": 8080, "container_port": 80, "protocol": "tcp"}
"runtimeConfig": {
"portMappings": [
{"hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
]
}
}

View File

@ -253,8 +253,8 @@ The list is described in JSON form, and can be stored on disk or generated from
- `name` (string): Network name. This should be unique across all containers on the host (or other administrative domain).
- `plugins` (list): A list of standard CNI network configuration dictionaries (see above).
When executing a plugin list, the runtime MUST replace the `name` and `cniVersion` fields in each individual network configuration in the list with the `name` and `cniVersion` field of the list itself.
This ensures that the name and CNI version is the same for all plugin executions in the list, preventing versioning conflicts between plugins.
When executing a plugin list, the runtime MUST replace the `name` and `cniVersion` fields in each individual network configuration in the list with the `name` and `cniVersion` field of the list itself. This ensures that the name and CNI version is the same for all plugin executions in the list, preventing versioning conflicts between plugins.
The runtime may also pass capability-based keys as a map in the top-level `runtimeConfig` key of the plugin's config JSON if a plugin advertises it supports a specific capability via the `capabilities` key of its network configuration. The key passed in `runtimeConfig` MUST match the name of the specific capability from the `capabilities` key of the plugins network configuration. See CONVENTIONS.md for more information on capabilities and how they are sent to plugins via the `runtimeConfig` key.
For the ADD action, the runtime MUST also add a `prevResult` field to the configuration JSON of any plugin after the first one, which MUST be the Result of the previous plugin (if any) in JSON format ([see below](#network-configuration-list-runtime-examples)).
For the ADD action, plugins SHOULD echo the contents of the `prevResult` field to their stdout to allow subsequent plugins (and the runtime) to receive the result, unless they wish to modify or suppress a previous result.

View File

@ -28,6 +28,12 @@ type RuntimeConf struct {
NetNS string
IfName string
Args [][2]string
// A dictionary of capability-specific data passed by the runtime
// to plugins as top-level keys in the 'runtimeConfig' dictionary
// of the plugin's stdin data. libcni will ensure that only keys
// in this map which match the capabilities of the plugin are passed
// to the plugin
CapabilityArgs map[string]interface{}
}
type NetworkConfig struct {
@ -57,22 +63,54 @@ type CNIConfig struct {
// CNIConfig implements the CNI interface
var _ CNI = &CNIConfig{}
func buildOneConfig(list *NetworkConfigList, orig *NetworkConfig, prevResult types.Result) (*NetworkConfig, error) {
func buildOneConfig(list *NetworkConfigList, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) {
var err error
// Ensure every config uses the same name and version
orig, err = InjectConf(orig, "name", list.Name)
if err != nil {
return nil, err
inject := map[string]interface{}{
"name": list.Name,
"cniVersion": list.CNIVersion,
}
orig, err = InjectConf(orig, "cniVersion", list.CNIVersion)
// Add previous plugin result
if prevResult != nil {
inject["prevResult"] = prevResult
}
// Ensure every config uses the same name and version
orig, err = InjectConf(orig, inject)
if err != nil {
return nil, err
}
// Add previous plugin result
if prevResult != nil {
orig, err = InjectConf(orig, "prevResult", prevResult)
return injectRuntimeConfig(orig, rt)
}
// This function takes a libcni RuntimeConf structure and injects values into
// a "runtimeConfig" dictionary in the CNI network configuration JSON that
// will be passed to the plugin on stdin.
//
// Only "capabilities arguments" passed by the runtime are currently injected.
// These capabilities arguments are filtered through the plugin's advertised
// capabilities from its config JSON, and any keys in the CapabilityArgs
// matching plugin capabilities are added to the "runtimeConfig" dictionary
// sent to the plugin via JSON on stdin. For exmaple, if the plugin's
// capabilities include "portMappings", and the CapabilityArgs map includes a
// "portMappings" key, that key and its value are added to the "runtimeConfig"
// dictionary to be passed to the plugin's stdin.
func injectRuntimeConfig(orig *NetworkConfig, rt *RuntimeConf) (*NetworkConfig, error) {
var err error
rc := make(map[string]interface{})
for capability, supported := range orig.Network.Capabilities {
if !supported {
continue
}
if data, ok := rt.CapabilityArgs[capability]; ok {
rc[capability] = data
}
}
if len(rc) > 0 {
orig, err = InjectConf(orig, map[string]interface{}{"runtimeConfig": rc})
if err != nil {
return nil, err
}
@ -90,7 +128,7 @@ func (c *CNIConfig) AddNetworkList(list *NetworkConfigList, rt *RuntimeConf) (ty
return nil, err
}
newConf, err := buildOneConfig(list, net, prevResult)
newConf, err := buildOneConfig(list, net, prevResult, rt)
if err != nil {
return nil, err
}
@ -114,7 +152,7 @@ func (c *CNIConfig) DelNetworkList(list *NetworkConfigList, rt *RuntimeConf) err
return err
}
newConf, err := buildOneConfig(list, net, nil)
newConf, err := buildOneConfig(list, net, nil, rt)
if err != nil {
return err
}
@ -134,6 +172,11 @@ func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Resul
return nil, err
}
net, err = injectRuntimeConfig(net, rt)
if err != nil {
return nil, err
}
return invoke.ExecPluginWithResult(pluginPath, net.Bytes, c.args("ADD", rt))
}
@ -144,6 +187,11 @@ func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error {
return err
}
net, err = injectRuntimeConfig(net, rt)
if err != nil {
return err
}
return invoke.ExecPluginWithoutResult(pluginPath, net.Bytes, c.args("DEL", rt))
}

View File

@ -19,6 +19,7 @@ import (
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"github.com/containernetworking/cni/libcni"
@ -35,19 +36,25 @@ type pluginInfo struct {
debugFilePath string
debug *noop_debug.Debug
config string
stdinData []byte
}
func addNameToConfig(name, config string) ([]byte, error) {
obj := make(map[string]interface{})
err := json.Unmarshal([]byte(config), &obj)
if err != nil {
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
type portMapping struct {
HostPort int `json:"hostPort"`
ContainerPort int `json:"containerPort"`
Protocol string `json:"protocol"`
}
func stringInList(s string, list []string) bool {
for _, item := range list {
if s == item {
return true
}
}
obj["name"] = name
return json.Marshal(obj)
return false
}
func newPluginInfo(configKey, configValue, prevResult string, injectDebugFilePath bool, result string) pluginInfo {
func newPluginInfo(configValue, prevResult string, injectDebugFilePath bool, result string, runtimeConfig map[string]interface{}, capabilities []string) pluginInfo {
debugFile, err := ioutil.TempFile("", "cni_debug")
Expect(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed())
@ -58,23 +65,155 @@ func newPluginInfo(configKey, configValue, prevResult string, injectDebugFilePat
}
Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
config := fmt.Sprintf(`{"type": "noop", "%s": "%s", "cniVersion": "0.3.0"`, configKey, configValue)
// config is what would be in the plugin's on-disk configuration
// without runtime injected keys
config := fmt.Sprintf(`{"type": "noop", "some-key": "%s"`, configValue)
if prevResult != "" {
config += fmt.Sprintf(`, "prevResult": %s`, prevResult)
}
if injectDebugFilePath {
config += fmt.Sprintf(`, "debugFile": "%s"`, debugFilePath)
}
if len(capabilities) > 0 {
config += `, "capabilities": {`
for i, c := range capabilities {
if i > 0 {
config += ", "
}
config += fmt.Sprintf(`"%s": true`, c)
}
config += "}"
}
config += "}"
// stdinData is what the runtime should pass to the plugin's stdin,
// including injected keys like 'name', 'cniVersion', and 'runtimeConfig'
newConfig := make(map[string]interface{})
err = json.Unmarshal([]byte(config), &newConfig)
Expect(err).NotTo(HaveOccurred())
newConfig["name"] = "some-list"
newConfig["cniVersion"] = "0.3.0"
// Only include standard runtime config and capability args that this plugin advertises
newRuntimeConfig := make(map[string]interface{})
for key, value := range runtimeConfig {
if stringInList(key, capabilities) {
newRuntimeConfig[key] = value
}
}
if len(newRuntimeConfig) > 0 {
newConfig["runtimeConfig"] = newRuntimeConfig
}
stdinData, err := json.Marshal(newConfig)
Expect(err).NotTo(HaveOccurred())
return pluginInfo{
debugFilePath: debugFilePath,
debug: debug,
config: config,
stdinData: stdinData,
}
}
var _ = Describe("Invoking plugins", func() {
Describe("Capabilities", func() {
var (
debugFilePath string
debug *noop_debug.Debug
pluginConfig []byte
cniConfig libcni.CNIConfig
runtimeConfig *libcni.RuntimeConf
netConfig *libcni.NetworkConfig
)
BeforeEach(func() {
debugFile, err := ioutil.TempFile("", "cni_debug")
Expect(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed())
debugFilePath = debugFile.Name()
debug = &noop_debug.Debug{}
Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
pluginConfig = []byte(`{ "type": "noop", "cniVersion": "0.3.0", "capabilities": { "portMappings": true, "somethingElse": true, "noCapability": false } }`)
netConfig, err = libcni.ConfFromBytes(pluginConfig)
Expect(err).NotTo(HaveOccurred())
cniConfig = libcni.CNIConfig{Path: []string{filepath.Dir(pluginPaths["noop"])}}
runtimeConfig = &libcni.RuntimeConf{
ContainerID: "some-container-id",
NetNS: "/some/netns/path",
IfName: "some-eth0",
Args: [][2]string{{"DEBUG", debugFilePath}},
CapabilityArgs: map[string]interface{}{
"portMappings": []portMapping{
{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"},
},
"somethingElse": []string{"foobar", "baz"},
"noCapability": true,
"notAdded": []bool{true, false},
},
}
})
AfterEach(func() {
Expect(os.RemoveAll(debugFilePath)).To(Succeed())
})
It("adds correct runtime config for capabilities to stdin", func() {
_, err := cniConfig.AddNetwork(netConfig, runtimeConfig)
Expect(err).NotTo(HaveOccurred())
debug, err = noop_debug.ReadDebug(debugFilePath)
Expect(err).NotTo(HaveOccurred())
Expect(debug.Command).To(Equal("ADD"))
conf := make(map[string]interface{})
err = json.Unmarshal(debug.CmdArgs.StdinData, &conf)
Expect(err).NotTo(HaveOccurred())
// We expect runtimeConfig keys only for portMappings and somethingElse
rawRc := conf["runtimeConfig"]
rc, ok := rawRc.(map[string]interface{})
Expect(ok).To(Equal(true))
expectedKeys := []string{"portMappings", "somethingElse"}
Expect(len(rc)).To(Equal(len(expectedKeys)))
for _, key := range expectedKeys {
_, ok := rc[key]
Expect(ok).To(Equal(true))
}
})
It("adds no runtimeConfig when the plugin advertises no used capabilities", func() {
// Replace CapabilityArgs with ones we know the plugin
// doesn't support
runtimeConfig.CapabilityArgs = map[string]interface{}{
"portMappings22": []portMapping{
{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"},
},
"somethingElse22": []string{"foobar", "baz"},
}
_, err := cniConfig.AddNetwork(netConfig, runtimeConfig)
Expect(err).NotTo(HaveOccurred())
debug, err = noop_debug.ReadDebug(debugFilePath)
Expect(err).NotTo(HaveOccurred())
Expect(debug.Command).To(Equal("ADD"))
conf := make(map[string]interface{})
err = json.Unmarshal(debug.CmdArgs.StdinData, &conf)
Expect(err).NotTo(HaveOccurred())
// No intersection of plugin capabilities and CapabilityArgs,
// so plugin should not receive a "runtimeConfig" key
_, ok := conf["runtimeConfig"]
Expect(ok).Should(BeFalse())
})
})
Describe("Invoking a single plugin", func() {
var (
debugFilePath string
@ -99,12 +238,19 @@ var _ = Describe("Invoking plugins", func() {
}
Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
portMappings := []portMapping{
{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"},
}
cniBinPath = filepath.Dir(pluginPaths["noop"])
pluginConfig = `{ "type": "noop", "some-key": "some-value", "cniVersion": "0.3.0" }`
pluginConfig = `{ "type": "noop", "some-key": "some-value", "cniVersion": "0.3.0", "capabilities": { "portMappings": true } }`
cniConfig = libcni.CNIConfig{Path: []string{cniBinPath}}
netConfig = &libcni.NetworkConfig{
Network: &types.NetConf{
Type: "noop",
Capabilities: map[string]bool{
"portMappings": true,
},
},
Bytes: []byte(pluginConfig),
}
@ -112,19 +258,36 @@ var _ = Describe("Invoking plugins", func() {
ContainerID: "some-container-id",
NetNS: "/some/netns/path",
IfName: "some-eth0",
Args: [][2]string{[2]string{"DEBUG", debugFilePath}},
Args: [][2]string{{"DEBUG", debugFilePath}},
CapabilityArgs: map[string]interface{}{
"portMappings": portMappings,
},
}
// inject runtime args into the expected plugin config
conf := make(map[string]interface{})
err = json.Unmarshal([]byte(pluginConfig), &conf)
Expect(err).NotTo(HaveOccurred())
conf["runtimeConfig"] = map[string]interface{}{
"portMappings": portMappings,
}
newBytes, err := json.Marshal(conf)
Expect(err).NotTo(HaveOccurred())
expectedCmdArgs = skel.CmdArgs{
ContainerID: "some-container-id",
Netns: "/some/netns/path",
IfName: "some-eth0",
Args: "DEBUG=" + debugFilePath,
Path: cniBinPath,
StdinData: []byte(pluginConfig),
StdinData: newBytes,
}
})
AfterEach(func() {
Expect(os.RemoveAll(debugFilePath)).To(Succeed())
})
Describe("AddNetwork", func() {
It("executes the plugin with command ADD", func() {
r, err := cniConfig.AddNetwork(netConfig, runtimeConfig)
@ -149,6 +312,7 @@ var _ = Describe("Invoking plugins", func() {
Expect(err).NotTo(HaveOccurred())
Expect(debug.Command).To(Equal("ADD"))
Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
Expect(string(debug.CmdArgs.StdinData)).To(ContainSubstring("\"portMappings\":"))
})
Context("when finding the plugin fails", func() {
@ -184,6 +348,7 @@ var _ = Describe("Invoking plugins", func() {
Expect(err).NotTo(HaveOccurred())
Expect(debug.Command).To(Equal("DEL"))
Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
Expect(string(debug.CmdArgs.StdinData)).To(ContainSubstring("\"portMappings\":"))
})
Context("when finding the plugin fails", func() {
@ -241,10 +406,49 @@ var _ = Describe("Invoking plugins", func() {
)
BeforeEach(func() {
var err error
capabilityArgs := map[string]interface{}{
"portMappings": []portMapping{
{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"},
},
"otherCapability": 33,
}
cniBinPath = filepath.Dir(pluginPaths["noop"])
cniConfig = libcni.CNIConfig{Path: []string{cniBinPath}}
runtimeConfig = &libcni.RuntimeConf{
ContainerID: "some-container-id",
NetNS: "/some/netns/path",
IfName: "some-eth0",
Args: [][2]string{{"FOO", "BAR"}},
CapabilityArgs: capabilityArgs,
}
expectedCmdArgs = skel.CmdArgs{
ContainerID: runtimeConfig.ContainerID,
Netns: runtimeConfig.NetNS,
IfName: runtimeConfig.IfName,
Args: "FOO=BAR",
Path: cniBinPath,
}
rc := map[string]interface{}{
"containerId": runtimeConfig.ContainerID,
"netNs": runtimeConfig.NetNS,
"ifName": runtimeConfig.IfName,
"args": map[string]string{
"FOO": "BAR",
},
"portMappings": capabilityArgs["portMappings"],
"otherCapability": capabilityArgs["otherCapability"],
}
ipResult := `{"dns":{},"ips":[{"version": "4", "address": "10.1.2.3/24"}]}`
plugins = make([]pluginInfo, 3, 3)
plugins[0] = newPluginInfo("some-key", "some-value", "", true, `{"dns":{},"ips":[{"version": "4", "address": "10.1.2.3/24"}]}`)
plugins[1] = newPluginInfo("some-key", "some-other-value", `{"dns":{},"ips":[{"version": "4", "address": "10.1.2.3/24"}]}`, true, "PASSTHROUGH")
plugins[2] = newPluginInfo("some-key", "yet-another-value", `{"dns":{},"ips":[{"version": "4", "address": "10.1.2.3/24"}]}`, true, "INJECT-DNS")
plugins[0] = newPluginInfo("some-value", "", true, ipResult, rc, []string{"portMappings", "otherCapability"})
plugins[1] = newPluginInfo("some-other-value", ipResult, true, "PASSTHROUGH", rc, []string{"otherCapability"})
plugins[2] = newPluginInfo("yet-another-value", ipResult, true, "INJECT-DNS", rc, []string{})
configList := []byte(fmt.Sprintf(`{
"name": "some-list",
@ -256,25 +460,13 @@ var _ = Describe("Invoking plugins", func() {
]
}`, plugins[0].config, plugins[1].config, plugins[2].config))
var err error
netConfigList, err = libcni.ConfListFromBytes(configList)
Expect(err).NotTo(HaveOccurred())
})
cniBinPath = filepath.Dir(pluginPaths["noop"])
cniConfig = libcni.CNIConfig{Path: []string{cniBinPath}}
runtimeConfig = &libcni.RuntimeConf{
ContainerID: "some-container-id",
NetNS: "/some/netns/path",
IfName: "some-eth0",
Args: [][2]string{{"FOO", "BAR"}},
}
expectedCmdArgs = skel.CmdArgs{
ContainerID: "some-container-id",
Netns: "/some/netns/path",
IfName: "some-eth0",
Args: "FOO=BAR",
Path: cniBinPath,
AfterEach(func() {
for _, p := range plugins {
Expect(os.RemoveAll(p.debugFilePath)).To(Succeed())
}
})
@ -307,13 +499,10 @@ var _ = Describe("Invoking plugins", func() {
debug, err := noop_debug.ReadDebug(plugins[i].debugFilePath)
Expect(err).NotTo(HaveOccurred())
Expect(debug.Command).To(Equal("ADD"))
newConfig, err := addNameToConfig("some-list", plugins[i].config)
Expect(err).NotTo(HaveOccurred())
// Must explicitly match JSON due to dict element ordering
debugJSON := debug.CmdArgs.StdinData
Expect(debug.CmdArgs.StdinData).To(MatchJSON(plugins[i].stdinData))
debug.CmdArgs.StdinData = nil
Expect(debugJSON).To(MatchJSON(newConfig))
Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
}
})
@ -351,13 +540,10 @@ var _ = Describe("Invoking plugins", func() {
debug, err := noop_debug.ReadDebug(plugins[i].debugFilePath)
Expect(err).NotTo(HaveOccurred())
Expect(debug.Command).To(Equal("DEL"))
newConfig, err := addNameToConfig("some-list", plugins[i].config)
Expect(err).NotTo(HaveOccurred())
// Must explicitly match JSON due to dict element ordering
debugJSON := debug.CmdArgs.StdinData
Expect(debug.CmdArgs.StdinData).To(MatchJSON(plugins[i].stdinData))
debug.CmdArgs.StdinData = nil
Expect(debugJSON).To(MatchJSON(newConfig))
Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
}
})

View File

@ -201,22 +201,24 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
return ConfListFromConf(singleConf)
}
func InjectConf(original *NetworkConfig, key string, newValue interface{}) (*NetworkConfig, error) {
func InjectConf(original *NetworkConfig, newValues map[string]interface{}) (*NetworkConfig, error) {
config := make(map[string]interface{})
err := json.Unmarshal(original.Bytes, &config)
if err != nil {
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
}
if key == "" {
return nil, fmt.Errorf("key value can not be empty")
}
for key, value := range newValues {
if key == "" {
return nil, fmt.Errorf("keys cannot be empty")
}
if newValue == nil {
return nil, fmt.Errorf("newValue must be specified")
}
if value == nil {
return nil, fmt.Errorf("key '%s' value must not be nil", key)
}
config[key] = newValue
config[key] = value
}
newBytes, err := json.Marshal(config)
if err != nil {

View File

@ -115,6 +115,33 @@ var _ = Describe("Loading configuration from disk", func() {
})
})
Describe("Capabilities", func() {
var configDir string
BeforeEach(func() {
var err error
configDir, err = ioutil.TempDir("", "plugin-conf")
Expect(err).NotTo(HaveOccurred())
pluginConfig := []byte(`{ "name": "some-plugin", "type": "noop", "cniVersion": "0.3.0", "capabilities": { "portMappings": true, "somethingElse": true, "noCapability": false } }`)
Expect(ioutil.WriteFile(filepath.Join(configDir, "50-whatever.conf"), pluginConfig, 0600)).To(Succeed())
})
AfterEach(func() {
Expect(os.RemoveAll(configDir)).To(Succeed())
})
It("reads plugin capabilities from network config", func() {
netConfig, err := libcni.LoadConf(configDir, "some-plugin")
Expect(err).NotTo(HaveOccurred())
Expect(netConfig.Network.Capabilities).To(Equal(map[string]bool{
"portMappings": true,
"somethingElse": true,
"noCapability": false,
}))
})
})
Describe("ConfFromFile", func() {
Context("when the file cannot be opened", func() {
It("returns a useful error", func() {
@ -286,18 +313,18 @@ var _ = Describe("Loading configuration from disk", func() {
conf := &libcni.NetworkConfig{Network: &types.NetConf{Name: "some-plugin"},
Bytes: []byte(`{ cc cc cc}`)}
_, err := libcni.InjectConf(conf, "", nil)
_, err := libcni.InjectConf(conf, map[string]interface{}{"": nil})
Expect(err).To(MatchError(HavePrefix(`unmarshal existing network bytes`)))
})
It("returns key error", func() {
_, err := libcni.InjectConf(testNetConfig, "", nil)
Expect(err).To(MatchError(HavePrefix(`key value can not be empty`)))
_, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"": nil})
Expect(err).To(MatchError(HavePrefix(`keys cannot be empty`)))
})
It("returns newValue error", func() {
_, err := libcni.InjectConf(testNetConfig, "test", nil)
Expect(err).To(MatchError(HavePrefix(`newValue must be specified`)))
_, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"test": nil})
Expect(err).To(MatchError(HavePrefix(`key 'test' value must not be nil`)))
})
})
@ -305,7 +332,7 @@ var _ = Describe("Loading configuration from disk", func() {
It("adds the new key & value to the config", func() {
newPluginConfig := []byte(`{"name":"some-plugin","test":"test"}`)
resultConfig, err := libcni.InjectConf(testNetConfig, "test", "test")
resultConfig, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"test": "test"})
Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{
Network: &types.NetConf{Name: "some-plugin"},
@ -316,10 +343,10 @@ var _ = Describe("Loading configuration from disk", func() {
It("adds the new value for exiting key", func() {
newPluginConfig := []byte(`{"name":"some-plugin","test":"changedValue"}`)
resultConfig, err := libcni.InjectConf(testNetConfig, "test", "test")
resultConfig, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"test": "test"})
Expect(err).NotTo(HaveOccurred())
resultConfig, err = libcni.InjectConf(resultConfig, "test", "changedValue")
resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"test": "changedValue"})
Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{
@ -331,10 +358,10 @@ var _ = Describe("Loading configuration from disk", func() {
It("adds existing key & value", func() {
newPluginConfig := []byte(`{"name":"some-plugin","test":"test"}`)
resultConfig, err := libcni.InjectConf(testNetConfig, "test", "test")
resultConfig, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"test": "test"})
Expect(err).NotTo(HaveOccurred())
resultConfig, err = libcni.InjectConf(resultConfig, "test", "test")
resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"test": "test"})
Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{
@ -350,11 +377,11 @@ var _ = Describe("Loading configuration from disk", func() {
newDNS := &types.DNS{Nameservers: servers, Domain: "local"}
// inject DNS
resultConfig, err := libcni.InjectConf(testNetConfig, "dns", newDNS)
resultConfig, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"dns": newDNS})
Expect(err).NotTo(HaveOccurred())
// inject type
resultConfig, err = libcni.InjectConf(resultConfig, "type", "bridge")
resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"type": "bridge"})
Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{

View File

@ -60,9 +60,10 @@ func (n *IPNet) UnmarshalJSON(data []byte) error {
type NetConf struct {
CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
IPAM struct {
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Capabilities map[string]bool `json:"capabilities,omitempty"`
IPAM struct {
Type string `json:"type,omitempty"`
} `json:"ipam,omitempty"`
DNS DNS `json:"dns"`