diff --git a/skel/skel.go b/skel/skel.go index 4325ec69..b5d6ecf9 100644 --- a/skel/skel.go +++ b/skel/skel.go @@ -24,6 +24,7 @@ import ( "os" "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/version" ) // CmdArgs captures all the arguments passed in to the plugin @@ -38,9 +39,11 @@ type CmdArgs struct { } type dispatcher struct { - Getenv func(string) string - Stdin io.Reader - Stderr io.Writer + Getenv func(string) string + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer + Versioner version.PluginVersioner } type reqForCmdEntry map[string]bool @@ -154,6 +157,9 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error) *types.Er case "DEL": err = cmdDel(cmdArgs) + case "VERSION": + err = t.Versioner.Encode(t.Stdout) + default: return createTypedError("unknown CNI_COMMAND: %v", cmd) } @@ -172,9 +178,11 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error) *types.Er // two callback functions for add and del commands. func PluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error) { caller := dispatcher{ - Getenv: os.Getenv, - Stdin: os.Stdin, - Stderr: os.Stderr, + Getenv: os.Getenv, + Stdin: os.Stdin, + Stdout: os.Stdout, + Stderr: os.Stderr, + Versioner: version.DefaultPluginVersioner, } err := caller.pluginMain(cmdAdd, cmdDel) diff --git a/skel/skel_test.go b/skel/skel_test.go index 39df2716..e6304f5e 100644 --- a/skel/skel_test.go +++ b/skel/skel_test.go @@ -21,6 +21,7 @@ import ( "strings" "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/testutils" . "github.com/onsi/ginkgo" @@ -48,7 +49,7 @@ var _ = Describe("dispatching to the correct callback", func() { var ( environment map[string]string stdin io.Reader - stderr *bytes.Buffer + stdout, stderr *bytes.Buffer cmdAdd, cmdDel *fakeCmd dispatch *dispatcher expectedCmdArgs *CmdArgs @@ -64,11 +65,15 @@ var _ = Describe("dispatching to the correct callback", func() { "CNI_PATH": "/some/cni/path", } stdin = strings.NewReader(`{ "some": "config" }`) + stdout = &bytes.Buffer{} stderr = &bytes.Buffer{} + versioner := &version.BasicVersioner{CNIVersion: "9.8.7"} dispatch = &dispatcher{ - Getenv: func(key string) string { return environment[key] }, - Stdin: stdin, - Stderr: stderr, + Getenv: func(key string) string { return environment[key] }, + Stdin: stdin, + Stdout: stdout, + Stderr: stderr, + Versioner: versioner, } cmdAdd = &fakeCmd{} cmdDel = &fakeCmd{} @@ -171,6 +176,36 @@ var _ = Describe("dispatching to the correct callback", func() { ) }) + Context("when the CNI_COMMAND is VERSION", func() { + BeforeEach(func() { + environment["CNI_COMMAND"] = "VERSION" + }) + + It("prints the version to stdout", func() { + err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func) + + Expect(err).NotTo(HaveOccurred()) + Expect(stdout).To(MatchJSON(`{ "cniVersion": "9.8.7" }`)) + }) + + It("does not call cmdAdd or cmdDel", func() { + err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func) + + Expect(err).NotTo(HaveOccurred()) + Expect(cmdAdd.CallCount).To(Equal(0)) + Expect(cmdDel.CallCount).To(Equal(0)) + }) + + DescribeTable("VERSION does not need the usual env vars", envVarChecker, + Entry("command", "CNI_COMMAND", true), + Entry("container id", "CNI_CONTAINER_ID", false), + Entry("net ns", "CNI_NETNS", false), + Entry("if name", "CNI_IFNAME", false), + Entry("args", "CNI_ARGS", false), + Entry("path", "CNI_PATH", false), + ) + }) + Context("when the CNI_COMMAND is unrecognized", func() { BeforeEach(func() { environment["CNI_COMMAND"] = "NOPE" diff --git a/version/version.go b/version/version.go new file mode 100644 index 00000000..2cb075fb --- /dev/null +++ b/version/version.go @@ -0,0 +1,42 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package version + +import ( + "encoding/json" + "io" +) + +// A PluginVersioner can encode information about its version +type PluginVersioner interface { + Encode(io.Writer) error +} + +// BasicVersioner is a PluginVersioner which reports a single cniVersion string +type BasicVersioner struct { + CNIVersion string `json:"cniVersion"` +} + +func (p *BasicVersioner) Encode(w io.Writer) error { + return json.NewEncoder(w).Encode(p) +} + +// Current reports the version of the CNI spec implemented by this library +func Current() string { + return "0.2.0" +} + +// DefaultPluginVersioner reports the Current library spec version as the cniVersion +var DefaultPluginVersioner = &BasicVersioner{CNIVersion: Current()}