$ mkdir -p /etc/cni/net.d
$ cat >/etc/cni/net.d/10-mynet.conf <<EOF
{
+ "cniVersion": "0.2.0",
"name": "mynet",
"type": "bridge",
"bridge": "cni0",
EOF
$ cat >/etc/cni/net.d/99-loopback.conf <<EOF
{
+ "cniVersion": "0.2.0",
"type": "loopback"
}
EOF
Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
cniBinPath = filepath.Dir(pathToPlugin)
- pluginConfig = `{ "type": "noop", "some-key": "some-value" }`
+ pluginConfig = `{ "type": "noop", "some-key": "some-value", "cniVersion": "0.2.0" }`
cniConfig = libcni.CNIConfig{Path: []string{cniBinPath}}
netConfig = &libcni.NetworkConfig{
Network: &types.NetConf{
import (
"encoding/json"
+ "fmt"
"os"
"github.com/containernetworking/cni/pkg/types"
IfName: "dummy",
Path: "dummy",
}
- stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, nil, args.AsEnv())
+ stdin := []byte(fmt.Sprintf(`{"cniVersion":%q}`, version.Current()))
+ stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, stdin, args.AsEnv())
if err != nil {
if err.Error() == "unknown CNI_COMMAND: VERSION" {
return version.PluginSupports("0.1.0"), nil
package invoke_test
import (
+ "encoding/json"
"errors"
"github.com/containernetworking/cni/pkg/invoke"
VersionDecoder: versionDecoder,
}
pluginPath = "/some/plugin/path"
- netconf = []byte(`{ "some": "stdin" }`)
+ netconf = []byte(`{ "some": "stdin", "cniVersion": "0.2.0" }`)
cniargs = &fakes.CNIArgs{}
cniargs.AsEnvCall.Returns.Env = []string{"SOME=ENV"}
})
It("execs the plugin with the command VERSION", func() {
pluginExec.GetVersionInfo(pluginPath)
Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath))
- Expect(rawExec.ExecPluginCall.Received.StdinData).To(BeNil())
Expect(rawExec.ExecPluginCall.Received.Environ).To(ContainElement("CNI_COMMAND=VERSION"))
+ expectedStdin, _ := json.Marshal(map[string]string{"cniVersion": version.Current()})
+ Expect(rawExec.ExecPluginCall.Received.StdinData).To(MatchJSON(expectedStdin))
})
It("decodes and returns the version info", func() {
Expect(env).To(ContainElement("CNI_PATH=dummy"))
})
})
-
})
})
"CNI_PATH=/some/bin/path",
"CNI_IFNAME=some-eth0",
}
- stdin = []byte(`{"some":"stdin-json"}`)
+ stdin = []byte(`{"some":"stdin-json", "cniVersion": "0.2.0"}`)
execer = &invoke.RawExec{}
})
package skel
import (
+ "encoding/json"
"fmt"
"io"
"io/ioutil"
}
}
+func (t *dispatcher) validateVersion(stdinData []byte) error {
+ var netconf types.NetConf
+ if err := json.Unmarshal(stdinData, &netconf); err != nil {
+ return err
+ }
+ if netconf.CNIVersion == "" {
+ return fmt.Errorf("missing required config cniVersion")
+ }
+
+ return nil
+}
+
func (t *dispatcher) pluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) *types.Error {
cmd, cmdArgs, err := t.getCmdArgsFromEnv()
if err != nil {
return createTypedError(err.Error())
}
+ if err = t.validateVersion(cmdArgs.StdinData); err != nil {
+ return createTypedError(err.Error())
+ }
+
switch cmd {
case "ADD":
err = cmdAdd(cmdArgs)
import (
"bytes"
"errors"
- "io"
"strings"
"github.com/containernetworking/cni/pkg/types"
var _ = Describe("dispatching to the correct callback", func() {
var (
environment map[string]string
- stdin io.Reader
+ stdinData string
stdout, stderr *bytes.Buffer
cmdAdd, cmdDel *fakeCmd
dispatch *dispatcher
"CNI_ARGS": "some;extra;args",
"CNI_PATH": "/some/cni/path",
}
- stdin = strings.NewReader(`{ "some": "config" }`)
+
+ stdinData = `{ "some": "config", "cniVersion": "9.8.7" }`
stdout = &bytes.Buffer{}
stderr = &bytes.Buffer{}
versionInfo = version.PluginSupports("9.8.7")
dispatch = &dispatcher{
Getenv: func(key string) string { return environment[key] },
- Stdin: stdin,
+ Stdin: strings.NewReader(stdinData),
Stdout: stdout,
Stderr: stderr,
}
IfName: "eth0",
Args: "some;extra;args",
Path: "/some/cni/path",
- StdinData: []byte(`{ "some": "config" }`),
+ StdinData: []byte(stdinData),
}
})
})
})
+
+ Context("when the stdin data is missing the required cniVersion config", func() {
+ BeforeEach(func() {
+ dispatch.Stdin = strings.NewReader(`{ "some": "config" }`)
+ })
+
+ It("immediately returns a useful error", func() {
+ err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
+ Expect(err).To(MatchError("missing required config cniVersion"))
+ })
+
+ It("does not call either callback", func() {
+ Expect(cmdAdd.CallCount).To(Equal(0))
+ Expect(cmdDel.CallCount).To(Equal(0))
+ })
+ })
})
Context("when the CNI_COMMAND is DEL", func() {
}
}
+// PluginDecoder can decode the response returned by a plugin's VERSION command
type PluginDecoder struct{}
func (_ *PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) {
conf := &NetConf{
NetConf: types.NetConf{
- Name: "testConfig",
- Type: "bridge",
+ CNIVersion: "0.2.0",
+ Name: "testConfig",
+ Type: "bridge",
},
BrName: IFNAME,
IsGW: false,
conf := &NetConf{
NetConf: types.NetConf{
- Name: "testConfig",
- Type: "bridge",
+ CNIVersion: "0.2.0",
+ Name: "testConfig",
+ Type: "bridge",
},
BrName: IFNAME,
IsGW: false,
Expect(err).NotTo(HaveOccurred())
conf := fmt.Sprintf(`{
+ "cniVersion": "0.2.0",
"name": "mynet",
"type": "bridge",
"bridge": "%s",
"isDefaultGateway": true,
"ipMasq": false,
"ipam": {
+ "cniVersion": "0.2.0",
"type": "host-local",
"subnet": "%s"
}
})
It("ensure bridge address", func() {
-
const IFNAME = "bridge0"
const EXPECTED_IP = "10.0.0.0/8"
const CHANGED_EXPECTED_IP = "10.1.2.3/16"
conf := &NetConf{
NetConf: types.NetConf{
- Name: "testConfig",
- Type: "bridge",
+ CNIVersion: "0.2.0",
+ Name: "testConfig",
+ Type: "bridge",
},
BrName: IFNAME,
IsGW: true,
})
Expect(err).NotTo(HaveOccurred())
})
-
})
It("creates an ipvlan link in a non-default namespace", func() {
conf := &NetConf{
NetConf: types.NetConf{
- Name: "testConfig",
- Type: "ipvlan",
+ CNIVersion: "0.2.0",
+ Name: "testConfig",
+ Type: "ipvlan",
},
Master: MASTER_NAME,
Mode: "l2",
const IFNAME = "ipvl0"
conf := fmt.Sprintf(`{
+ "cniVersion": "0.2.0",
"name": "mynet",
"type": "ipvlan",
"master": "%s",
"ipam": {
+ "cniVersion": "0.2.0",
"type": "host-local",
"subnet": "10.1.2.0/24"
}
fmt.Sprintf("CNI_ARGS=%s", "none"),
fmt.Sprintf("CNI_PATH=%s", "/some/test/path"),
}
- command.Stdin = strings.NewReader("this doesn't matter")
+ command.Stdin = strings.NewReader(`{ "cniVersion": "0.1.0" }`)
})
AfterEach(func() {
It("creates an macvlan link in a non-default namespace", func() {
conf := &NetConf{
NetConf: types.NetConf{
- Name: "testConfig",
- Type: "macvlan",
+ CNIVersion: "0.2.0",
+ Name: "testConfig",
+ Type: "macvlan",
},
Master: MASTER_NAME,
Mode: "bridge",
const IFNAME = "macvl0"
conf := fmt.Sprintf(`{
+ "cniVersion": "0.2.0",
"name": "mynet",
"type": "macvlan",
"master": "%s",
"ipam": {
+ "cniVersion": "0.2.0",
"type": "host-local",
"subnet": "10.1.2.0/24"
}
const IFNAME = "ptp0"
conf := `{
+ "cniVersion": "0.2.0",
"name": "mynet",
"type": "ptp",
"ipMasq": true,
"mtu": 5000,
"ipam": {
+ "cniVersion": "0.2.0",
"type": "host-local",
"subnet": "10.1.2.0/24"
}
"CNI_IFNAME=some-eth0",
"CNI_PATH=/some/bin/path",
}
- cmd.Stdin = strings.NewReader(`{"some":"stdin-json"}`)
+ cmd.Stdin = strings.NewReader(`{"some":"stdin-json", "cniVersion": "0.2.0"}`)
expectedCmdArgs = skel.CmdArgs{
ContainerID: "some-container-id",
Netns: "/some/netns/path",
IfName: "some-eth0",
Args: "DEBUG=" + debugFileName,
Path: "/some/bin/path",
- StdinData: []byte(`{"some":"stdin-json"}`),
+ StdinData: []byte(`{"some":"stdin-json", "cniVersion": "0.2.0"}`),
}
})