diff --git a/invoke/exec.go b/invoke/exec.go index 7eb06156..167d38f5 100644 --- a/invoke/exec.go +++ b/invoke/exec.go @@ -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 diff --git a/invoke/exec_test.go b/invoke/exec_test.go index 94007d7c..2b9c9bf9 100644 --- a/invoke/exec_test.go +++ b/invoke/exec_test.go @@ -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")) }) }) - }) }) diff --git a/invoke/raw_exec_test.go b/invoke/raw_exec_test.go index 7df60a11..b0ca9607 100644 --- a/invoke/raw_exec_test.go +++ b/invoke/raw_exec_test.go @@ -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{} }) diff --git a/skel/skel.go b/skel/skel.go index de64d7dd..180ce24e 100644 --- a/skel/skel.go +++ b/skel/skel.go @@ -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) diff --git a/skel/skel_test.go b/skel/skel_test.go index 1cc533bd..7ae25d35 100644 --- a/skel/skel_test.go +++ b/skel/skel_test.go @@ -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() { diff --git a/version/plugin.go b/version/plugin.go index 9bd7dc83..3a42fe25 100644 --- a/version/plugin.go +++ b/version/plugin.go @@ -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) {