diff --git a/Documentation/tuning.md b/Documentation/tuning.md index bc786d88..7ca16d0e 100644 --- a/Documentation/tuning.md +++ b/Documentation/tuning.md @@ -22,9 +22,7 @@ Other sysctls can be modified as long as they belong to the network namespace (` A successful result would simply be: ``` -{ - "cniVersion": "0.1.0" -} +{ } ``` ## Network sysctls documentation diff --git a/SPEC.md b/SPEC.md index 74f06e46..cf752226 100644 --- a/SPEC.md +++ b/SPEC.md @@ -62,10 +62,14 @@ The operations that the CNI plugin needs to support are: - **Extra arguments**, as defined above. - **Name of the interface inside the container**, as defined above. +- Report version + - Parameters: NONE. + - Result: + - The version of the CNI spec implemented by the plugin: `{ "cniVersion": "0.2.0" }` + The executable command-line API uses the type of network (see [Network Configuration](#network-configuration) below) as the name of the executable to invoke. It will then look for this executable in a list of predefined directories. Once found, it will invoke the executable using the following environment variables for argument passing: -- `CNI_VERSION`: [Semantic Version 2.0](http://semver.org) of CNI specification. This effectively versions the CNI_XXX environment variables. -- `CNI_COMMAND`: indicates the desired operation; either `ADD` or `DEL` +- `CNI_COMMAND`: indicates the desired operation; `ADD`, `DEL` or `VERSION`. - `CNI_CONTAINERID`: Container ID - `CNI_NETNS`: Path to network namespace file - `CNI_IFNAME`: Interface name to set up @@ -81,7 +85,7 @@ Success is indicated by a return code of zero and the following JSON printed to ``` { - "cniVersion": "0.1.0", + "cniVersion": "0.2.0", "ip4": { "ip": , "gateway": , (optional) @@ -110,7 +114,7 @@ Examples include generating an `/etc/resolv.conf` file to be injected into the c Errors are indicated by a non-zero return code and the following JSON being printed to stdout: ``` { - "cniVersion": "0.1.0", + "cniVersion": "0.2.0", "code": , "msg": , "details": (optional) @@ -147,7 +151,7 @@ Plugins may define additional fields that they accept and may generate an error ```json { - "cniVersion": "0.1.0", + "cniVersion": "0.2.0", "name": "dbnet", "type": "bridge", // type (plugin) specific @@ -166,7 +170,7 @@ Plugins may define additional fields that they accept and may generate an error ```json { - "cniVersion": "0.1.0", + "cniVersion": "0.2.0", "name": "pci", "type": "ovs", // type (plugin) specific @@ -216,7 +220,7 @@ Success is indicated by a zero return code and the following JSON being printed ``` { - "cniVersion": "0.1.0", + "cniVersion": "0.2.0", "ip4": { "ip": , "gateway": , (optional) diff --git a/pkg/skel/skel.go b/pkg/skel/skel.go index d6f0f4cf..19ddf249 100644 --- a/pkg/skel/skel.go +++ b/pkg/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/pkg/skel/skel_test.go b/pkg/skel/skel_test.go index 39df2716..e6304f5e 100644 --- a/pkg/skel/skel_test.go +++ b/pkg/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/pkg/version/version.go b/pkg/version/version.go new file mode 100644 index 00000000..2cb075fb --- /dev/null +++ b/pkg/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()}