skel: Plugins require a cniVersion in the NetConf
authorGabe Rosenhouse <rosenhouse@gmail.com>
Wed, 7 Sep 2016 00:19:26 +0000 (20:19 -0400)
committerGabe Rosenhouse <grosenhouse@pivotal.io>
Mon, 19 Sep 2016 04:30:57 +0000 (21:30 -0700)
invoke/exec.go
invoke/exec_test.go
invoke/raw_exec_test.go
skel/skel.go
skel/skel_test.go
version/plugin.go

index 7eb0615..167d38f 100644 (file)
@@ -16,6 +16,7 @@ package invoke
 
 import (
        "encoding/json"
+       "fmt"
        "os"
 
        "github.com/containernetworking/cni/pkg/types"
@@ -77,7 +78,8 @@ func (e *PluginExec) GetVersionInfo(pluginPath string) (version.PluginInfo, erro
                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
index 94007d7..2b9c9bf 100644 (file)
@@ -15,6 +15,7 @@
 package invoke_test
 
 import (
+       "encoding/json"
        "errors"
 
        "github.com/containernetworking/cni/pkg/invoke"
@@ -48,7 +49,7 @@ var _ = Describe("Executing a plugin, unit tests", func() {
                        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"}
        })
@@ -105,8 +106,9 @@ var _ = Describe("Executing a plugin, unit tests", func() {
                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() {
@@ -146,6 +148,5 @@ var _ = Describe("Executing a plugin, unit tests", func() {
                                Expect(env).To(ContainElement("CNI_PATH=dummy"))
                        })
                })
-
        })
 })
index 7df60a1..b0ca960 100644 (file)
@@ -58,7 +58,7 @@ var _ = Describe("RawExec", func() {
                        "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{}
        })
 
index de64d7d..180ce24 100644 (file)
@@ -17,6 +17,7 @@
 package skel
 
 import (
+       "encoding/json"
        "fmt"
        "io"
        "io/ioutil"
@@ -143,12 +144,28 @@ func createTypedError(f string, args ...interface{}) *types.Error {
        }
 }
 
+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)
index 1cc533b..7ae25d3 100644 (file)
@@ -17,7 +17,6 @@ package skel
 import (
        "bytes"
        "errors"
-       "io"
        "strings"
 
        "github.com/containernetworking/cni/pkg/types"
@@ -48,7 +47,7 @@ func (c *fakeCmd) Func(args *CmdArgs) error {
 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
@@ -65,13 +64,14 @@ var _ = Describe("dispatching to the correct callback", func() {
                        "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,
                }
@@ -83,7 +83,7 @@ var _ = Describe("dispatching to the correct callback", func() {
                        IfName:      "eth0",
                        Args:        "some;extra;args",
                        Path:        "/some/cni/path",
-                       StdinData:   []byte(`{ "some": "config" }`),
+                       StdinData:   []byte(stdinData),
                }
        })
 
@@ -144,6 +144,22 @@ var _ = Describe("dispatching to the correct callback", func() {
 
                        })
                })
+
+               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() {
index 9bd7dc8..3a42fe2 100644 (file)
@@ -56,6 +56,7 @@ func PluginSupports(supportedVersions ...string) PluginInfo {
        }
 }
 
+// PluginDecoder can decode the response returned by a plugin's VERSION command
 type PluginDecoder struct{}
 
 func (_ *PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) {