Merge pull request #287 from rosenhouse/multi-version
Plugins report a list of supported versions
This commit is contained in:
10
SPEC.md
10
SPEC.md
@ -64,8 +64,14 @@ The operations that the CNI plugin needs to support are:
|
|||||||
|
|
||||||
- Report version
|
- Report version
|
||||||
- Parameters: NONE.
|
- Parameters: NONE.
|
||||||
- Result:
|
- Result: information about the CNI spec versions supported by the plugin
|
||||||
- The version of the CNI spec implemented by the plugin: `{ "cniVersion": "0.2.0" }`
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"cniVersion": "0.2.0", // the version of the CNI spec in use for this output
|
||||||
|
"supportedVersions": [ "0.1.0", "0.2.0" ] // the list of CNI spec versions that this plugin supports
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
The executable command-line API uses the type of network (see [Network Configuration](#network-configuration) below) as the name of the executable to invoke.
|
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:
|
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:
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/invoke"
|
"github.com/containernetworking/cni/pkg/invoke"
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RuntimeConf struct {
|
type RuntimeConf struct {
|
||||||
@ -42,6 +43,7 @@ type CNIConfig struct {
|
|||||||
Path []string
|
Path []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddNetwork executes the plugin with the ADD command
|
||||||
func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (*types.Result, error) {
|
func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (*types.Result, error) {
|
||||||
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
|
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -51,6 +53,7 @@ func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (*types.Resu
|
|||||||
return invoke.ExecPluginWithResult(pluginPath, net.Bytes, c.args("ADD", rt))
|
return invoke.ExecPluginWithResult(pluginPath, net.Bytes, c.args("ADD", rt))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DelNetwork executes the plugin with the DEL command
|
||||||
func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error {
|
func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error {
|
||||||
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
|
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -60,6 +63,17 @@ func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error {
|
|||||||
return invoke.ExecPluginWithoutResult(pluginPath, net.Bytes, c.args("DEL", rt))
|
return invoke.ExecPluginWithoutResult(pluginPath, net.Bytes, c.args("DEL", rt))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetVersionInfo reports which versions of the CNI spec are supported by
|
||||||
|
// the given plugin.
|
||||||
|
func (c *CNIConfig) GetVersionInfo(pluginType string) (version.PluginInfo, error) {
|
||||||
|
pluginPath, err := invoke.FindInPath(pluginType, c.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoke.GetVersionInfo(pluginPath)
|
||||||
|
}
|
||||||
|
|
||||||
// =====
|
// =====
|
||||||
func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args {
|
func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args {
|
||||||
return &invoke.Args{
|
return &invoke.Args{
|
||||||
|
@ -155,4 +155,23 @@ var _ = Describe("Invoking the plugin", func() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Describe("GetVersionInfo", func() {
|
||||||
|
It("executes the plugin with the command VERSION", func() {
|
||||||
|
versionInfo, err := cniConfig.GetVersionInfo("noop")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(versionInfo).NotTo(BeNil())
|
||||||
|
Expect(versionInfo.SupportedVersions()).To(Equal([]string{
|
||||||
|
"0.-42.0", "0.1.0", "0.2.0",
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when finding the plugin fails", func() {
|
||||||
|
It("returns the error", func() {
|
||||||
|
_, err := cniConfig.GetVersionInfo("does-not-exist")
|
||||||
|
Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -15,34 +15,41 @@
|
|||||||
package invoke
|
package invoke
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
func pluginErr(err error, output []byte) error {
|
func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (*types.Result, error) {
|
||||||
if _, ok := err.(*exec.ExitError); ok {
|
return defaultPluginExec.WithResult(pluginPath, netconf, args)
|
||||||
emsg := types.Error{}
|
|
||||||
if perr := json.Unmarshal(output, &emsg); perr != nil {
|
|
||||||
return fmt.Errorf("netplugin failed but error parsing its diagnostic message %q: %v", string(output), perr)
|
|
||||||
}
|
|
||||||
details := ""
|
|
||||||
if emsg.Details != "" {
|
|
||||||
details = fmt.Sprintf("; %v", emsg.Details)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%v%v", emsg.Msg, details)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (*types.Result, error) {
|
func ExecPluginWithoutResult(pluginPath string, netconf []byte, args CNIArgs) error {
|
||||||
stdoutBytes, err := execPlugin(pluginPath, netconf, args)
|
return defaultPluginExec.WithoutResult(pluginPath, netconf, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetVersionInfo(pluginPath string) (version.PluginInfo, error) {
|
||||||
|
return defaultPluginExec.GetVersionInfo(pluginPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultPluginExec = &PluginExec{
|
||||||
|
RawExec: &RawExec{Stderr: os.Stderr},
|
||||||
|
VersionDecoder: &version.PluginDecoder{},
|
||||||
|
}
|
||||||
|
|
||||||
|
type PluginExec struct {
|
||||||
|
RawExec interface {
|
||||||
|
ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error)
|
||||||
|
}
|
||||||
|
VersionDecoder interface {
|
||||||
|
Decode(jsonBytes []byte) (version.PluginInfo, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PluginExec) WithResult(pluginPath string, netconf []byte, args CNIArgs) (*types.Result, error) {
|
||||||
|
stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -52,35 +59,31 @@ func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (*typ
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExecPluginWithoutResult(pluginPath string, netconf []byte, args CNIArgs) error {
|
func (e *PluginExec) WithoutResult(pluginPath string, netconf []byte, args CNIArgs) error {
|
||||||
_, err := execPlugin(pluginPath, netconf, args)
|
_, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func execPlugin(pluginPath string, netconf []byte, args CNIArgs) ([]byte, error) {
|
// GetVersionInfo returns the version information available about the plugin.
|
||||||
return defaultRawExec.ExecPlugin(pluginPath, netconf, args.AsEnv())
|
// For recent-enough plugins, it uses the information returned by the VERSION
|
||||||
}
|
// command. For older plugins which do not recognize that command, it reports
|
||||||
|
// version 0.1.0
|
||||||
|
func (e *PluginExec) GetVersionInfo(pluginPath string) (version.PluginInfo, error) {
|
||||||
|
args := &Args{
|
||||||
|
Command: "VERSION",
|
||||||
|
|
||||||
var defaultRawExec = &RawExec{Stderr: os.Stderr}
|
// set fake values required by plugins built against an older version of skel
|
||||||
|
NetNS: "dummy",
|
||||||
type RawExec struct {
|
IfName: "dummy",
|
||||||
Stderr io.Writer
|
Path: "dummy",
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RawExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
|
|
||||||
stdout := &bytes.Buffer{}
|
|
||||||
|
|
||||||
c := exec.Cmd{
|
|
||||||
Env: environ,
|
|
||||||
Path: pluginPath,
|
|
||||||
Args: []string{pluginPath},
|
|
||||||
Stdin: bytes.NewBuffer(stdinData),
|
|
||||||
Stdout: stdout,
|
|
||||||
Stderr: e.Stderr,
|
|
||||||
}
|
}
|
||||||
if err := c.Run(); err != nil {
|
stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, nil, args.AsEnv())
|
||||||
return nil, pluginErr(err, stdout.Bytes())
|
if err != nil {
|
||||||
|
if err.Error() == "unknown CNI_COMMAND: VERSION" {
|
||||||
|
return version.PluginSupports("0.1.0"), nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return stdout.Bytes(), nil
|
return e.VersionDecoder.Decode(stdoutBytes)
|
||||||
}
|
}
|
||||||
|
@ -15,109 +15,137 @@
|
|||||||
package invoke_test
|
package invoke_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"errors"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/invoke"
|
"github.com/containernetworking/cni/pkg/invoke"
|
||||||
|
"github.com/containernetworking/cni/pkg/invoke/fakes"
|
||||||
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("RawExec", func() {
|
var _ = Describe("Executing a plugin, unit tests", func() {
|
||||||
var (
|
var (
|
||||||
debugFileName string
|
pluginExec *invoke.PluginExec
|
||||||
debug *noop_debug.Debug
|
rawExec *fakes.RawExec
|
||||||
environ []string
|
versionDecoder *fakes.VersionDecoder
|
||||||
stdin []byte
|
|
||||||
execer *invoke.RawExec
|
pluginPath string
|
||||||
|
netconf []byte
|
||||||
|
cniargs *fakes.CNIArgs
|
||||||
)
|
)
|
||||||
|
|
||||||
const reportResult = `{ "some": "result" }`
|
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
debugFile, err := ioutil.TempFile("", "cni_debug")
|
rawExec = &fakes.RawExec{}
|
||||||
Expect(err).NotTo(HaveOccurred())
|
rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "ip4": { "ip": "1.2.3.4/24" } }`)
|
||||||
Expect(debugFile.Close()).To(Succeed())
|
|
||||||
debugFileName = debugFile.Name()
|
|
||||||
|
|
||||||
debug = &noop_debug.Debug{
|
versionDecoder = &fakes.VersionDecoder{}
|
||||||
ReportResult: reportResult,
|
versionDecoder.DecodeCall.Returns.PluginInfo = version.PluginSupports("0.42.0")
|
||||||
ReportStderr: "some stderr message",
|
|
||||||
|
pluginExec = &invoke.PluginExec{
|
||||||
|
RawExec: rawExec,
|
||||||
|
VersionDecoder: versionDecoder,
|
||||||
}
|
}
|
||||||
Expect(debug.WriteDebug(debugFileName)).To(Succeed())
|
pluginPath = "/some/plugin/path"
|
||||||
|
netconf = []byte(`{ "some": "stdin" }`)
|
||||||
environ = []string{
|
cniargs = &fakes.CNIArgs{}
|
||||||
"CNI_COMMAND=ADD",
|
cniargs.AsEnvCall.Returns.Env = []string{"SOME=ENV"}
|
||||||
"CNI_CONTAINERID=some-container-id",
|
|
||||||
"CNI_ARGS=DEBUG=" + debugFileName,
|
|
||||||
"CNI_NETNS=/some/netns/path",
|
|
||||||
"CNI_PATH=/some/bin/path",
|
|
||||||
"CNI_IFNAME=some-eth0",
|
|
||||||
}
|
|
||||||
stdin = []byte(`{"some":"stdin-json"}`)
|
|
||||||
execer = &invoke.RawExec{}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
AfterEach(func() {
|
Describe("returning a result", func() {
|
||||||
Expect(os.Remove(debugFileName)).To(Succeed())
|
It("unmarshals the result bytes into the Result type", func() {
|
||||||
})
|
result, err := pluginExec.WithResult(pluginPath, netconf, cniargs)
|
||||||
|
|
||||||
It("runs the plugin with the given stdin and environment", func() {
|
|
||||||
_, err := execer.ExecPlugin(pathToPlugin, stdin, environ)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
debug, err := noop_debug.ReadDebug(debugFileName)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(debug.Command).To(Equal("ADD"))
|
|
||||||
Expect(debug.CmdArgs.StdinData).To(Equal(stdin))
|
|
||||||
Expect(debug.CmdArgs.Netns).To(Equal("/some/netns/path"))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("returns the resulting stdout as bytes", func() {
|
|
||||||
resultBytes, err := execer.ExecPlugin(pathToPlugin, stdin, environ)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
Expect(resultBytes).To(BeEquivalentTo(reportResult))
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("when the Stderr writer is set", func() {
|
|
||||||
var stderrBuffer *bytes.Buffer
|
|
||||||
|
|
||||||
BeforeEach(func() {
|
|
||||||
stderrBuffer = &bytes.Buffer{}
|
|
||||||
execer.Stderr = stderrBuffer
|
|
||||||
})
|
|
||||||
|
|
||||||
It("forwards any stderr bytes to the Stderr writer", func() {
|
|
||||||
_, err := execer.ExecPlugin(pathToPlugin, stdin, environ)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(result.IP4.IP.IP.String()).To(Equal("1.2.3.4"))
|
||||||
|
})
|
||||||
|
|
||||||
Expect(stderrBuffer.String()).To(Equal("some stderr message"))
|
It("passes its arguments through to the rawExec", func() {
|
||||||
|
pluginExec.WithResult(pluginPath, netconf, cniargs)
|
||||||
|
Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath))
|
||||||
|
Expect(rawExec.ExecPluginCall.Received.StdinData).To(Equal(netconf))
|
||||||
|
Expect(rawExec.ExecPluginCall.Received.Environ).To(Equal([]string{"SOME=ENV"}))
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the rawExec fails", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
rawExec.ExecPluginCall.Returns.Error = errors.New("banana")
|
||||||
|
})
|
||||||
|
It("returns the error", func() {
|
||||||
|
_, err := pluginExec.WithResult(pluginPath, netconf, cniargs)
|
||||||
|
Expect(err).To(MatchError("banana"))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("when the plugin errors", func() {
|
Describe("without returning a result", func() {
|
||||||
|
It("passes its arguments through to the rawExec", func() {
|
||||||
|
pluginExec.WithoutResult(pluginPath, netconf, cniargs)
|
||||||
|
Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath))
|
||||||
|
Expect(rawExec.ExecPluginCall.Received.StdinData).To(Equal(netconf))
|
||||||
|
Expect(rawExec.ExecPluginCall.Received.Environ).To(Equal([]string{"SOME=ENV"}))
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the rawExec fails", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
rawExec.ExecPluginCall.Returns.Error = errors.New("banana")
|
||||||
|
})
|
||||||
|
It("returns the error", func() {
|
||||||
|
err := pluginExec.WithoutResult(pluginPath, netconf, cniargs)
|
||||||
|
Expect(err).To(MatchError("banana"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("discovering the plugin version", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
debug.ReportError = "banana"
|
rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "some": "version-info" }`)
|
||||||
Expect(debug.WriteDebug(debugFileName)).To(Succeed())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("wraps and returns the error", func() {
|
It("execs the plugin with the command VERSION", func() {
|
||||||
_, err := execer.ExecPlugin(pathToPlugin, stdin, environ)
|
pluginExec.GetVersionInfo(pluginPath)
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath))
|
||||||
Expect(err).To(MatchError("banana"))
|
Expect(rawExec.ExecPluginCall.Received.StdinData).To(BeNil())
|
||||||
|
Expect(rawExec.ExecPluginCall.Received.Environ).To(ContainElement("CNI_COMMAND=VERSION"))
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
Context("when the system is unable to execute the plugin", func() {
|
It("decodes and returns the version info", func() {
|
||||||
It("returns the error", func() {
|
versionInfo, err := pluginExec.GetVersionInfo(pluginPath)
|
||||||
_, err := execer.ExecPlugin("/tmp/some/invalid/plugin/path", stdin, environ)
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(versionInfo.SupportedVersions()).To(Equal([]string{"0.42.0"}))
|
||||||
Expect(err).To(MatchError(ContainSubstring("/tmp/some/invalid/plugin/path")))
|
Expect(versionDecoder.DecodeCall.Received.JSONBytes).To(MatchJSON(`{ "some": "version-info" }`))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Context("when the rawExec fails", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
rawExec.ExecPluginCall.Returns.Error = errors.New("banana")
|
||||||
|
})
|
||||||
|
It("returns the error", func() {
|
||||||
|
_, err := pluginExec.GetVersionInfo(pluginPath)
|
||||||
|
Expect(err).To(MatchError("banana"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the plugin is too old to recognize the VERSION command", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
rawExec.ExecPluginCall.Returns.Error = errors.New("unknown CNI_COMMAND: VERSION")
|
||||||
|
})
|
||||||
|
|
||||||
|
It("interprets the error as a 0.1.0 version", func() {
|
||||||
|
versionInfo, err := pluginExec.GetVersionInfo(pluginPath)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(versionInfo.SupportedVersions()).To(ConsistOf("0.1.0"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("sets dummy values for env vars required by very old plugins", func() {
|
||||||
|
pluginExec.GetVersionInfo(pluginPath)
|
||||||
|
|
||||||
|
env := rawExec.ExecPluginCall.Received.Environ
|
||||||
|
Expect(env).To(ContainElement("CNI_NETNS=dummy"))
|
||||||
|
Expect(env).To(ContainElement("CNI_IFNAME=dummy"))
|
||||||
|
Expect(env).To(ContainElement("CNI_PATH=dummy"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
27
pkg/invoke/fakes/cni_args.go
Normal file
27
pkg/invoke/fakes/cni_args.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// 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 fakes
|
||||||
|
|
||||||
|
type CNIArgs struct {
|
||||||
|
AsEnvCall struct {
|
||||||
|
Returns struct {
|
||||||
|
Env []string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *CNIArgs) AsEnv() []string {
|
||||||
|
return a.AsEnvCall.Returns.Env
|
||||||
|
}
|
36
pkg/invoke/fakes/raw_exec.go
Normal file
36
pkg/invoke/fakes/raw_exec.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// 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 fakes
|
||||||
|
|
||||||
|
type RawExec struct {
|
||||||
|
ExecPluginCall struct {
|
||||||
|
Received struct {
|
||||||
|
PluginPath string
|
||||||
|
StdinData []byte
|
||||||
|
Environ []string
|
||||||
|
}
|
||||||
|
Returns struct {
|
||||||
|
ResultBytes []byte
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RawExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
|
||||||
|
e.ExecPluginCall.Received.PluginPath = pluginPath
|
||||||
|
e.ExecPluginCall.Received.StdinData = stdinData
|
||||||
|
e.ExecPluginCall.Received.Environ = environ
|
||||||
|
return e.ExecPluginCall.Returns.ResultBytes, e.ExecPluginCall.Returns.Error
|
||||||
|
}
|
34
pkg/invoke/fakes/version_decoder.go
Normal file
34
pkg/invoke/fakes/version_decoder.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// 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 fakes
|
||||||
|
|
||||||
|
import "github.com/containernetworking/cni/pkg/version"
|
||||||
|
|
||||||
|
type VersionDecoder struct {
|
||||||
|
DecodeCall struct {
|
||||||
|
Received struct {
|
||||||
|
JSONBytes []byte
|
||||||
|
}
|
||||||
|
Returns struct {
|
||||||
|
PluginInfo version.PluginInfo
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *VersionDecoder) Decode(jsonData []byte) (version.PluginInfo, error) {
|
||||||
|
e.DecodeCall.Received.JSONBytes = jsonData
|
||||||
|
return e.DecodeCall.Returns.PluginInfo, e.DecodeCall.Returns.Error
|
||||||
|
}
|
107
pkg/invoke/get_version_integration_test.go
Normal file
107
pkg/invoke/get_version_integration_test.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
// 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 invoke_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/invoke"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
|
"github.com/containernetworking/cni/pkg/version/testhelpers"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/ginkgo/extensions/table"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("GetVersion, integration tests", func() {
|
||||||
|
var (
|
||||||
|
pluginDir string
|
||||||
|
pluginPath string
|
||||||
|
)
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
pluginDir, err := ioutil.TempDir("", "plugins")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
pluginPath = filepath.Join(pluginDir, "test-plugin")
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
Expect(os.RemoveAll(pluginDir)).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
DescribeTable("correctly reporting plugin versions",
|
||||||
|
func(gitRef string, pluginSource string, expectedVersions version.PluginInfo) {
|
||||||
|
Expect(testhelpers.BuildAt([]byte(pluginSource), gitRef, pluginPath)).To(Succeed())
|
||||||
|
versionInfo, err := invoke.GetVersionInfo(pluginPath)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(versionInfo.SupportedVersions()).To(ConsistOf(expectedVersions.SupportedVersions()))
|
||||||
|
},
|
||||||
|
|
||||||
|
Entry("historical: before VERSION was introduced",
|
||||||
|
git_ref_v010, plugin_source_no_custom_versions,
|
||||||
|
version.PluginSupports("0.1.0"),
|
||||||
|
),
|
||||||
|
|
||||||
|
Entry("historical: when VERSION was introduced but plugins couldn't customize it",
|
||||||
|
git_ref_v020_no_custom_versions, plugin_source_no_custom_versions,
|
||||||
|
version.PluginSupports("0.1.0", "0.2.0"),
|
||||||
|
),
|
||||||
|
|
||||||
|
Entry("historical: when plugins started reporting their own version list",
|
||||||
|
git_ref_v020_custom_versions, plugin_source_v020_custom_versions,
|
||||||
|
version.PluginSupports("0.2.0", "0.999.0"),
|
||||||
|
),
|
||||||
|
|
||||||
|
// this entry tracks the current behavior. Before you change it, ensure
|
||||||
|
// that its previous behavior is captured in the most recent "historical" entry
|
||||||
|
Entry("current",
|
||||||
|
"HEAD", plugin_source_v020_custom_versions,
|
||||||
|
version.PluginSupports("0.2.0", "0.999.0"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// a 0.2.0 plugin that can report its own versions
|
||||||
|
const plugin_source_v020_custom_versions = `package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func c(_ *skel.CmdArgs) error { fmt.Println("{}"); return nil }
|
||||||
|
|
||||||
|
func main() { skel.PluginMain(c, c, version.PluginSupports("0.2.0", "0.999.0")) }
|
||||||
|
`
|
||||||
|
const git_ref_v020_custom_versions = "bf31ed15"
|
||||||
|
|
||||||
|
// a minimal 0.1.0 / 0.2.0 plugin that cannot report it's own version support
|
||||||
|
const plugin_source_no_custom_versions = `package main
|
||||||
|
|
||||||
|
import "github.com/containernetworking/cni/pkg/skel"
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func c(_ *skel.CmdArgs) error { fmt.Println("{}"); return nil }
|
||||||
|
|
||||||
|
func main() { skel.PluginMain(c, c) }
|
||||||
|
`
|
||||||
|
|
||||||
|
const git_ref_v010 = "2c482f4"
|
||||||
|
const git_ref_v020_no_custom_versions = "349d66d"
|
63
pkg/invoke/raw_exec.go
Normal file
63
pkg/invoke/raw_exec.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// 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 invoke
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RawExec struct {
|
||||||
|
Stderr io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RawExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
|
||||||
|
stdout := &bytes.Buffer{}
|
||||||
|
|
||||||
|
c := exec.Cmd{
|
||||||
|
Env: environ,
|
||||||
|
Path: pluginPath,
|
||||||
|
Args: []string{pluginPath},
|
||||||
|
Stdin: bytes.NewBuffer(stdinData),
|
||||||
|
Stdout: stdout,
|
||||||
|
Stderr: e.Stderr,
|
||||||
|
}
|
||||||
|
if err := c.Run(); err != nil {
|
||||||
|
return nil, pluginErr(err, stdout.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdout.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func pluginErr(err error, output []byte) error {
|
||||||
|
if _, ok := err.(*exec.ExitError); ok {
|
||||||
|
emsg := types.Error{}
|
||||||
|
if perr := json.Unmarshal(output, &emsg); perr != nil {
|
||||||
|
return fmt.Errorf("netplugin failed but error parsing its diagnostic message %q: %v", string(output), perr)
|
||||||
|
}
|
||||||
|
details := ""
|
||||||
|
if emsg.Details != "" {
|
||||||
|
details = fmt.Sprintf("; %v", emsg.Details)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%v%v", emsg.Msg, details)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
123
pkg/invoke/raw_exec_test.go
Normal file
123
pkg/invoke/raw_exec_test.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
// 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 invoke_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/invoke"
|
||||||
|
|
||||||
|
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("RawExec", func() {
|
||||||
|
var (
|
||||||
|
debugFileName string
|
||||||
|
debug *noop_debug.Debug
|
||||||
|
environ []string
|
||||||
|
stdin []byte
|
||||||
|
execer *invoke.RawExec
|
||||||
|
)
|
||||||
|
|
||||||
|
const reportResult = `{ "some": "result" }`
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
debugFile, err := ioutil.TempFile("", "cni_debug")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(debugFile.Close()).To(Succeed())
|
||||||
|
debugFileName = debugFile.Name()
|
||||||
|
|
||||||
|
debug = &noop_debug.Debug{
|
||||||
|
ReportResult: reportResult,
|
||||||
|
ReportStderr: "some stderr message",
|
||||||
|
}
|
||||||
|
Expect(debug.WriteDebug(debugFileName)).To(Succeed())
|
||||||
|
|
||||||
|
environ = []string{
|
||||||
|
"CNI_COMMAND=ADD",
|
||||||
|
"CNI_CONTAINERID=some-container-id",
|
||||||
|
"CNI_ARGS=DEBUG=" + debugFileName,
|
||||||
|
"CNI_NETNS=/some/netns/path",
|
||||||
|
"CNI_PATH=/some/bin/path",
|
||||||
|
"CNI_IFNAME=some-eth0",
|
||||||
|
}
|
||||||
|
stdin = []byte(`{"some":"stdin-json"}`)
|
||||||
|
execer = &invoke.RawExec{}
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
Expect(os.Remove(debugFileName)).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("runs the plugin with the given stdin and environment", func() {
|
||||||
|
_, err := execer.ExecPlugin(pathToPlugin, stdin, environ)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
debug, err := noop_debug.ReadDebug(debugFileName)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(debug.Command).To(Equal("ADD"))
|
||||||
|
Expect(debug.CmdArgs.StdinData).To(Equal(stdin))
|
||||||
|
Expect(debug.CmdArgs.Netns).To(Equal("/some/netns/path"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns the resulting stdout as bytes", func() {
|
||||||
|
resultBytes, err := execer.ExecPlugin(pathToPlugin, stdin, environ)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(resultBytes).To(BeEquivalentTo(reportResult))
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the Stderr writer is set", func() {
|
||||||
|
var stderrBuffer *bytes.Buffer
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
stderrBuffer = &bytes.Buffer{}
|
||||||
|
execer.Stderr = stderrBuffer
|
||||||
|
})
|
||||||
|
|
||||||
|
It("forwards any stderr bytes to the Stderr writer", func() {
|
||||||
|
_, err := execer.ExecPlugin(pathToPlugin, stdin, environ)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(stderrBuffer.String()).To(Equal("some stderr message"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the plugin errors", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
debug.ReportError = "banana"
|
||||||
|
Expect(debug.WriteDebug(debugFileName)).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("wraps and returns the error", func() {
|
||||||
|
_, err := execer.ExecPlugin(pathToPlugin, stdin, environ)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err).To(MatchError("banana"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the system is unable to execute the plugin", func() {
|
||||||
|
It("returns the error", func() {
|
||||||
|
_, err := execer.ExecPlugin("/tmp/some/invalid/plugin/path", stdin, environ)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err).To(MatchError(ContainSubstring("/tmp/some/invalid/plugin/path")))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -39,11 +39,10 @@ type CmdArgs struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type dispatcher struct {
|
type dispatcher struct {
|
||||||
Getenv func(string) string
|
Getenv func(string) string
|
||||||
Stdin io.Reader
|
Stdin io.Reader
|
||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
Stderr io.Writer
|
Stderr io.Writer
|
||||||
Versioner version.PluginVersioner
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type reqForCmdEntry map[string]bool
|
type reqForCmdEntry map[string]bool
|
||||||
@ -144,7 +143,7 @@ func createTypedError(f string, args ...interface{}) *types.Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *dispatcher) pluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error) *types.Error {
|
func (t *dispatcher) pluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) *types.Error {
|
||||||
cmd, cmdArgs, err := t.getCmdArgsFromEnv()
|
cmd, cmdArgs, err := t.getCmdArgsFromEnv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return createTypedError(err.Error())
|
return createTypedError(err.Error())
|
||||||
@ -158,7 +157,7 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error) *types.Er
|
|||||||
err = cmdDel(cmdArgs)
|
err = cmdDel(cmdArgs)
|
||||||
|
|
||||||
case "VERSION":
|
case "VERSION":
|
||||||
err = t.Versioner.Encode(t.Stdout)
|
err = versionInfo.Encode(t.Stdout)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return createTypedError("unknown CNI_COMMAND: %v", cmd)
|
return createTypedError("unknown CNI_COMMAND: %v", cmd)
|
||||||
@ -176,16 +175,15 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error) *types.Er
|
|||||||
|
|
||||||
// PluginMain is the "main" for a plugin. It accepts
|
// PluginMain is the "main" for a plugin. It accepts
|
||||||
// two callback functions for add and del commands.
|
// two callback functions for add and del commands.
|
||||||
func PluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error) {
|
func PluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) {
|
||||||
caller := dispatcher{
|
caller := dispatcher{
|
||||||
Getenv: os.Getenv,
|
Getenv: os.Getenv,
|
||||||
Stdin: os.Stdin,
|
Stdin: os.Stdin,
|
||||||
Stdout: os.Stdout,
|
Stdout: os.Stdout,
|
||||||
Stderr: os.Stderr,
|
Stderr: os.Stderr,
|
||||||
Versioner: version.DefaultPluginVersioner,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := caller.pluginMain(cmdAdd, cmdDel)
|
err := caller.pluginMain(cmdAdd, cmdDel, versionInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dieErr(err)
|
dieErr(err)
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||||||
cmdAdd, cmdDel *fakeCmd
|
cmdAdd, cmdDel *fakeCmd
|
||||||
dispatch *dispatcher
|
dispatch *dispatcher
|
||||||
expectedCmdArgs *CmdArgs
|
expectedCmdArgs *CmdArgs
|
||||||
|
versionInfo version.PluginInfo
|
||||||
)
|
)
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
@ -67,13 +68,12 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||||||
stdin = strings.NewReader(`{ "some": "config" }`)
|
stdin = strings.NewReader(`{ "some": "config" }`)
|
||||||
stdout = &bytes.Buffer{}
|
stdout = &bytes.Buffer{}
|
||||||
stderr = &bytes.Buffer{}
|
stderr = &bytes.Buffer{}
|
||||||
versioner := &version.BasicVersioner{CNIVersion: "9.8.7"}
|
versionInfo = version.PluginSupports("9.8.7")
|
||||||
dispatch = &dispatcher{
|
dispatch = &dispatcher{
|
||||||
Getenv: func(key string) string { return environment[key] },
|
Getenv: func(key string) string { return environment[key] },
|
||||||
Stdin: stdin,
|
Stdin: stdin,
|
||||||
Stdout: stdout,
|
Stdout: stdout,
|
||||||
Stderr: stderr,
|
Stderr: stderr,
|
||||||
Versioner: versioner,
|
|
||||||
}
|
}
|
||||||
cmdAdd = &fakeCmd{}
|
cmdAdd = &fakeCmd{}
|
||||||
cmdDel = &fakeCmd{}
|
cmdDel = &fakeCmd{}
|
||||||
@ -90,7 +90,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||||||
var envVarChecker = func(envVar string, isRequired bool) {
|
var envVarChecker = func(envVar string, isRequired bool) {
|
||||||
delete(environment, envVar)
|
delete(environment, envVar)
|
||||||
|
|
||||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func)
|
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||||
if isRequired {
|
if isRequired {
|
||||||
Expect(err).To(Equal(&types.Error{
|
Expect(err).To(Equal(&types.Error{
|
||||||
Code: 100,
|
Code: 100,
|
||||||
@ -104,7 +104,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||||||
|
|
||||||
Context("when the CNI_COMMAND is ADD", func() {
|
Context("when the CNI_COMMAND is ADD", func() {
|
||||||
It("extracts env vars and stdin data and calls cmdAdd", func() {
|
It("extracts env vars and stdin data and calls cmdAdd", func() {
|
||||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func)
|
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||||
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(cmdAdd.CallCount).To(Equal(1))
|
Expect(cmdAdd.CallCount).To(Equal(1))
|
||||||
@ -113,7 +113,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("does not call cmdDel", func() {
|
It("does not call cmdDel", func() {
|
||||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func)
|
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||||
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(cmdDel.CallCount).To(Equal(0))
|
Expect(cmdDel.CallCount).To(Equal(0))
|
||||||
@ -136,7 +136,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("reports that all of them are missing, not just the first", func() {
|
It("reports that all of them are missing, not just the first", func() {
|
||||||
Expect(dispatch.pluginMain(cmdAdd.Func, cmdDel.Func)).NotTo(Succeed())
|
Expect(dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)).NotTo(Succeed())
|
||||||
log := stderr.String()
|
log := stderr.String()
|
||||||
Expect(log).To(ContainSubstring("CNI_NETNS env variable missing\n"))
|
Expect(log).To(ContainSubstring("CNI_NETNS env variable missing\n"))
|
||||||
Expect(log).To(ContainSubstring("CNI_IFNAME env variable missing\n"))
|
Expect(log).To(ContainSubstring("CNI_IFNAME env variable missing\n"))
|
||||||
@ -152,7 +152,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("calls cmdDel with the env vars and stdin data", func() {
|
It("calls cmdDel with the env vars and stdin data", func() {
|
||||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func)
|
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||||
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(cmdDel.CallCount).To(Equal(1))
|
Expect(cmdDel.CallCount).To(Equal(1))
|
||||||
@ -160,7 +160,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("does not call cmdAdd", func() {
|
It("does not call cmdAdd", func() {
|
||||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func)
|
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||||
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||||
@ -182,14 +182,17 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("prints the version to stdout", func() {
|
It("prints the version to stdout", func() {
|
||||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func)
|
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||||
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(stdout).To(MatchJSON(`{ "cniVersion": "9.8.7" }`))
|
Expect(stdout).To(MatchJSON(`{
|
||||||
|
"cniVersion": "0.2.0",
|
||||||
|
"supportedVersions": ["9.8.7"]
|
||||||
|
}`))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("does not call cmdAdd or cmdDel", func() {
|
It("does not call cmdAdd or cmdDel", func() {
|
||||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func)
|
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||||
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||||
@ -212,14 +215,14 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("does not call any cmd callback", func() {
|
It("does not call any cmd callback", func() {
|
||||||
dispatch.pluginMain(cmdAdd.Func, cmdDel.Func)
|
dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||||
|
|
||||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||||
Expect(cmdDel.CallCount).To(Equal(0))
|
Expect(cmdDel.CallCount).To(Equal(0))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("returns an error", func() {
|
It("returns an error", func() {
|
||||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func)
|
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||||
|
|
||||||
Expect(err).To(Equal(&types.Error{
|
Expect(err).To(Equal(&types.Error{
|
||||||
Code: 100,
|
Code: 100,
|
||||||
@ -234,14 +237,14 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("does not call any cmd callback", func() {
|
It("does not call any cmd callback", func() {
|
||||||
dispatch.pluginMain(cmdAdd.Func, cmdDel.Func)
|
dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||||
|
|
||||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||||
Expect(cmdDel.CallCount).To(Equal(0))
|
Expect(cmdDel.CallCount).To(Equal(0))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("wraps and returns the error", func() {
|
It("wraps and returns the error", func() {
|
||||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func)
|
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||||
|
|
||||||
Expect(err).To(Equal(&types.Error{
|
Expect(err).To(Equal(&types.Error{
|
||||||
Code: 100,
|
Code: 100,
|
||||||
@ -260,7 +263,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("returns the error as-is", func() {
|
It("returns the error as-is", func() {
|
||||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func)
|
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||||
|
|
||||||
Expect(err).To(Equal(&types.Error{
|
Expect(err).To(Equal(&types.Error{
|
||||||
Code: 1234,
|
Code: 1234,
|
||||||
@ -275,7 +278,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("wraps and returns the error", func() {
|
It("wraps and returns the error", func() {
|
||||||
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func)
|
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
||||||
|
|
||||||
Expect(err).To(Equal(&types.Error{
|
Expect(err).To(Equal(&types.Error{
|
||||||
Code: 100,
|
Code: 100,
|
||||||
|
@ -57,6 +57,8 @@ func (n *IPNet) UnmarshalJSON(data []byte) error {
|
|||||||
|
|
||||||
// NetConf describes a network.
|
// NetConf describes a network.
|
||||||
type NetConf struct {
|
type NetConf struct {
|
||||||
|
CNIVersion string `json:"cniVersion,omitempty"`
|
||||||
|
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
IPAM struct {
|
IPAM struct {
|
||||||
|
77
pkg/version/plugin.go
Normal file
77
pkg/version/plugin.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// 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"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PluginInfo reports information about CNI versioning
|
||||||
|
type PluginInfo interface {
|
||||||
|
// SupportedVersions returns one or more CNI spec versions that the plugin
|
||||||
|
// supports. If input is provided in one of these versions, then the plugin
|
||||||
|
// promises to use the same CNI version in its response
|
||||||
|
SupportedVersions() []string
|
||||||
|
|
||||||
|
// Encode writes this CNI version information as JSON to the given Writer
|
||||||
|
Encode(io.Writer) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type pluginInfo struct {
|
||||||
|
CNIVersion_ string `json:"cniVersion"`
|
||||||
|
SupportedVersions_ []string `json:"supportedVersions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pluginInfo) Encode(w io.Writer) error {
|
||||||
|
return json.NewEncoder(w).Encode(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pluginInfo) SupportedVersions() []string {
|
||||||
|
return p.SupportedVersions_
|
||||||
|
}
|
||||||
|
|
||||||
|
// PluginSupports returns a new PluginInfo that will report the given versions
|
||||||
|
// as supported
|
||||||
|
func PluginSupports(supportedVersions ...string) PluginInfo {
|
||||||
|
if len(supportedVersions) < 1 {
|
||||||
|
panic("programmer error: you must support at least one version")
|
||||||
|
}
|
||||||
|
return &pluginInfo{
|
||||||
|
CNIVersion_: Current(),
|
||||||
|
SupportedVersions_: supportedVersions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PluginDecoder struct{}
|
||||||
|
|
||||||
|
func (_ *PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) {
|
||||||
|
var info pluginInfo
|
||||||
|
err := json.Unmarshal(jsonBytes, &info)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decoding version info: %s", err)
|
||||||
|
}
|
||||||
|
if info.CNIVersion_ == "" {
|
||||||
|
return nil, fmt.Errorf("decoding version info: missing field cniVersion")
|
||||||
|
}
|
||||||
|
if len(info.SupportedVersions_) == 0 {
|
||||||
|
if info.CNIVersion_ == "0.2.0" {
|
||||||
|
return PluginSupports("0.1.0", "0.2.0"), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("decoding version info: missing field supportedVersions")
|
||||||
|
}
|
||||||
|
return &info, nil
|
||||||
|
}
|
85
pkg/version/plugin_test.go
Normal file
85
pkg/version/plugin_test.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// 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_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Decoding versions reported by a plugin", func() {
|
||||||
|
var (
|
||||||
|
decoder *version.PluginDecoder
|
||||||
|
versionStdout []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
decoder = &version.PluginDecoder{}
|
||||||
|
versionStdout = []byte(`{
|
||||||
|
"cniVersion": "some-library-version",
|
||||||
|
"supportedVersions": [ "some-version", "some-other-version" ]
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns a PluginInfo that represents the given json bytes", func() {
|
||||||
|
pluginInfo, err := decoder.Decode(versionStdout)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(pluginInfo).NotTo(BeNil())
|
||||||
|
Expect(pluginInfo.SupportedVersions()).To(Equal([]string{
|
||||||
|
"some-version",
|
||||||
|
"some-other-version",
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the bytes cannot be decoded as json", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
versionStdout = []byte(`{{{`)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns a meaningful error", func() {
|
||||||
|
_, err := decoder.Decode(versionStdout)
|
||||||
|
Expect(err).To(MatchError("decoding version info: invalid character '{' looking for beginning of object key string"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the json bytes are missing the required CNIVersion field", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
versionStdout = []byte(`{ "supportedVersions": [ "foo" ] }`)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns a meaningful error", func() {
|
||||||
|
_, err := decoder.Decode(versionStdout)
|
||||||
|
Expect(err).To(MatchError("decoding version info: missing field cniVersion"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when there are no supported versions", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
versionStdout = []byte(`{ "cniVersion": "0.2.0" }`)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("assumes that the supported versions are 0.1.0 and 0.2.0", func() {
|
||||||
|
pluginInfo, err := decoder.Decode(versionStdout)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(pluginInfo).NotTo(BeNil())
|
||||||
|
Expect(pluginInfo.SupportedVersions()).To(Equal([]string{
|
||||||
|
"0.1.0",
|
||||||
|
"0.2.0",
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
156
pkg/version/testhelpers/testhelpers.go
Normal file
156
pkg/version/testhelpers/testhelpers.go
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
// 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 testhelpers supports testing of CNI components of different versions
|
||||||
|
//
|
||||||
|
// For example, to build a plugin against an old version of the CNI library,
|
||||||
|
// we can pass the plugin's source and the old git commit reference to BuildAt.
|
||||||
|
// We could then test how the built binary responds when called by the latest
|
||||||
|
// version of this library.
|
||||||
|
package testhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const packageBaseName = "github.com/containernetworking/cni"
|
||||||
|
|
||||||
|
func run(cmd *exec.Cmd) error {
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
command := strings.Join(cmd.Args, " ")
|
||||||
|
return fmt.Errorf("running %q: %s", command, out)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func goBuildEnviron(gopath string) []string {
|
||||||
|
environ := os.Environ()
|
||||||
|
for i, kvp := range environ {
|
||||||
|
if strings.HasPrefix(kvp, "GOPATH=") {
|
||||||
|
environ[i] = "GOPATH=" + gopath
|
||||||
|
return environ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
environ = append(environ, "GOPATH="+gopath)
|
||||||
|
return environ
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildGoProgram(gopath, packageName, outputFilePath string) error {
|
||||||
|
cmd := exec.Command("go", "build", "-o", outputFilePath, packageName)
|
||||||
|
cmd.Env = goBuildEnviron(gopath)
|
||||||
|
return run(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSingleFilePackage(gopath, packageName string, fileContents []byte) error {
|
||||||
|
dirName := filepath.Join(gopath, "src", packageName)
|
||||||
|
err := os.MkdirAll(dirName, 0700)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ioutil.WriteFile(filepath.Join(dirName, "main.go"), fileContents, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removePackage(gopath, packageName string) error {
|
||||||
|
dirName := filepath.Join(gopath, "src", packageName)
|
||||||
|
return os.RemoveAll(dirName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRepoRoot(path string) bool {
|
||||||
|
_, err := ioutil.ReadDir(filepath.Join(path, ".git"))
|
||||||
|
return (err == nil) && (filepath.Base(path) == "cni")
|
||||||
|
}
|
||||||
|
|
||||||
|
func LocateCurrentGitRepo() (string, error) {
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
if isRepoRoot(dir) {
|
||||||
|
return dir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dir, err = filepath.Abs(filepath.Dir(dir))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("abs(dir(%q)): %s", dir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("unable to find cni repo root, landed at %q", dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitCloneThisRepo(cloneDestination string) error {
|
||||||
|
err := os.MkdirAll(cloneDestination, 0700)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
currentGitRepo, err := LocateCurrentGitRepo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return run(exec.Command("git", "clone", currentGitRepo, cloneDestination))
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitCheckout(localRepo string, gitRef string) error {
|
||||||
|
return run(exec.Command("git", "-C", localRepo, "checkout", gitRef))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildAt builds the go programSource using the version of the CNI library
|
||||||
|
// at gitRef, and saves the resulting binary file at outputFilePath
|
||||||
|
func BuildAt(programSource []byte, gitRef string, outputFilePath string) error {
|
||||||
|
tempGoPath, err := ioutil.TempDir("", "cni-git-")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tempGoPath)
|
||||||
|
|
||||||
|
cloneDestination := filepath.Join(tempGoPath, "src", packageBaseName)
|
||||||
|
err = gitCloneThisRepo(cloneDestination)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = gitCheckout(cloneDestination, gitRef)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
testPackageName := fmt.Sprintf("test-package-%x", rand.Int31())
|
||||||
|
|
||||||
|
err = createSingleFilePackage(tempGoPath, testPackageName, programSource)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer removePackage(tempGoPath, testPackageName)
|
||||||
|
|
||||||
|
err = buildGoProgram(tempGoPath, testPackageName, outputFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
27
pkg/version/testhelpers/testhelpers_suite_test.go
Normal file
27
pkg/version/testhelpers/testhelpers_suite_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// 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 testhelpers_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTesthelpers(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Testhelpers Suite")
|
||||||
|
}
|
106
pkg/version/testhelpers/testhelpers_test.go
Normal file
106
pkg/version/testhelpers/testhelpers_test.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// 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 testhelpers_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/version/testhelpers"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("BuildAt", func() {
|
||||||
|
var (
|
||||||
|
gitRef string
|
||||||
|
outputFilePath string
|
||||||
|
outputDir string
|
||||||
|
programSource []byte
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
programSource = []byte(`package main
|
||||||
|
|
||||||
|
import "github.com/containernetworking/cni/pkg/skel"
|
||||||
|
|
||||||
|
func c(_ *skel.CmdArgs) error { return nil }
|
||||||
|
|
||||||
|
func main() { skel.PluginMain(c, c) }
|
||||||
|
`)
|
||||||
|
gitRef = "f4364185253"
|
||||||
|
|
||||||
|
var err error
|
||||||
|
outputDir, err = ioutil.TempDir("", "bin")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
outputFilePath = filepath.Join(outputDir, "some-binary")
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
Expect(os.RemoveAll(outputDir)).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("builds the provided source code using the CNI library at the given git ref", func() {
|
||||||
|
Expect(outputFilePath).NotTo(BeAnExistingFile())
|
||||||
|
|
||||||
|
err := testhelpers.BuildAt(programSource, gitRef, outputFilePath)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(outputFilePath).To(BeAnExistingFile())
|
||||||
|
|
||||||
|
cmd := exec.Command(outputFilePath)
|
||||||
|
cmd.Env = []string{"CNI_COMMAND=VERSION"}
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
Expect(err).To(BeAssignableToTypeOf(&exec.ExitError{}))
|
||||||
|
Expect(output).To(ContainSubstring("unknown CNI_COMMAND: VERSION"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
var _ = Describe("LocateCurrentGitRepo", func() {
|
||||||
|
It("returns the path to the root of the CNI git repo", func() {
|
||||||
|
path, err := testhelpers.LocateCurrentGitRepo()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
AssertItIsTheCNIRepoRoot(path)
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when run from a different directory", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
os.Chdir("..")
|
||||||
|
})
|
||||||
|
|
||||||
|
It("still finds the CNI repo root", func() {
|
||||||
|
path, err := testhelpers.LocateCurrentGitRepo()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
AssertItIsTheCNIRepoRoot(path)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
func AssertItIsTheCNIRepoRoot(path string) {
|
||||||
|
Expect(path).To(BeADirectory())
|
||||||
|
files, err := ioutil.ReadDir(path)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
names := []string{}
|
||||||
|
for _, file := range files {
|
||||||
|
names = append(names, file.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
Expect(names).To(ContainElement("SPEC.md"))
|
||||||
|
Expect(names).To(ContainElement("libcni"))
|
||||||
|
Expect(names).To(ContainElement("cnitool"))
|
||||||
|
}
|
@ -14,29 +14,16 @@
|
|||||||
|
|
||||||
package version
|
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
|
// Current reports the version of the CNI spec implemented by this library
|
||||||
func Current() string {
|
func Current() string {
|
||||||
return "0.2.0"
|
return "0.2.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultPluginVersioner reports the Current library spec version as the cniVersion
|
// Legacy PluginInfo describes a plugin that is backwards compatible with the
|
||||||
var DefaultPluginVersioner = &BasicVersioner{CNIVersion: Current()}
|
// CNI spec version 0.1.0. In particular, a runtime compiled against the 0.1.0
|
||||||
|
// library ought to work correctly with a plugin that reports support for
|
||||||
|
// Legacy versions.
|
||||||
|
//
|
||||||
|
// Any future CNI spec versions which meet this definition should be added to
|
||||||
|
// this list.
|
||||||
|
var Legacy = PluginSupports("0.1.0", "0.2.0")
|
||||||
|
27
pkg/version/version_suite_test.go
Normal file
27
pkg/version/version_suite_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// 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_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVersion(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Version Suite")
|
||||||
|
}
|
@ -22,6 +22,7 @@ import (
|
|||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/skel"
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
const socketPath = "/run/cni/dhcp.sock"
|
const socketPath = "/run/cni/dhcp.sock"
|
||||||
@ -30,7 +31,7 @@ func main() {
|
|||||||
if len(os.Args) > 1 && os.Args[1] == "daemon" {
|
if len(os.Args) > 1 && os.Args[1] == "daemon" {
|
||||||
runDaemon()
|
runDaemon()
|
||||||
} else {
|
} else {
|
||||||
skel.PluginMain(cmdAdd, cmdDel)
|
skel.PluginMain(cmdAdd, cmdDel, version.Legacy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,10 +19,11 @@ import (
|
|||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/skel"
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
skel.PluginMain(cmdAdd, cmdDel)
|
skel.PluginMain(cmdAdd, cmdDel, version.Legacy)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdAdd(args *skel.CmdArgs) error {
|
func cmdAdd(args *skel.CmdArgs) error {
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/containernetworking/cni/pkg/skel"
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
"github.com/containernetworking/cni/pkg/utils"
|
"github.com/containernetworking/cni/pkg/utils"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -354,5 +355,5 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
skel.PluginMain(cmdAdd, cmdDel)
|
skel.PluginMain(cmdAdd, cmdDel, version.Legacy)
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/containernetworking/cni/pkg/ns"
|
"github.com/containernetworking/cni/pkg/ns"
|
||||||
"github.com/containernetworking/cni/pkg/skel"
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -171,5 +172,5 @@ func renameLink(curName, newName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
skel.PluginMain(cmdAdd, cmdDel)
|
skel.PluginMain(cmdAdd, cmdDel, version.Legacy)
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/containernetworking/cni/pkg/ns"
|
"github.com/containernetworking/cni/pkg/ns"
|
||||||
"github.com/containernetworking/cni/pkg/skel"
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -67,5 +68,5 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
skel.PluginMain(cmdAdd, cmdDel)
|
skel.PluginMain(cmdAdd, cmdDel, version.Legacy)
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
"github.com/containernetworking/cni/pkg/skel"
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
"github.com/containernetworking/cni/pkg/utils/sysctl"
|
"github.com/containernetworking/cni/pkg/utils/sysctl"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -193,5 +194,5 @@ func renameLink(curName, newName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
skel.PluginMain(cmdAdd, cmdDel)
|
skel.PluginMain(cmdAdd, cmdDel, version.Legacy)
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/containernetworking/cni/pkg/skel"
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
"github.com/containernetworking/cni/pkg/utils"
|
"github.com/containernetworking/cni/pkg/utils"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -236,5 +237,5 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
skel.PluginMain(cmdAdd, cmdDel)
|
skel.PluginMain(cmdAdd, cmdDel, version.Legacy)
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ import (
|
|||||||
"github.com/containernetworking/cni/pkg/invoke"
|
"github.com/containernetworking/cni/pkg/invoke"
|
||||||
"github.com/containernetworking/cni/pkg/skel"
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -249,5 +250,5 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
skel.PluginMain(cmdAdd, cmdDel)
|
skel.PluginMain(cmdAdd, cmdDel, version.Legacy)
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/containernetworking/cni/pkg/ns"
|
"github.com/containernetworking/cni/pkg/ns"
|
||||||
"github.com/containernetworking/cni/pkg/skel"
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TuningConf represents the network tuning configuration.
|
// TuningConf represents the network tuning configuration.
|
||||||
@ -78,5 +79,5 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
skel.PluginMain(cmdAdd, cmdDel)
|
skel.PluginMain(cmdAdd, cmdDel, version.Legacy)
|
||||||
}
|
}
|
||||||
|
@ -24,11 +24,17 @@ import (
|
|||||||
|
|
||||||
// Debug is used to control and record the behavior of the noop plugin
|
// Debug is used to control and record the behavior of the noop plugin
|
||||||
type Debug struct {
|
type Debug struct {
|
||||||
ReportResult string
|
// Report* fields allow the test to control the behavior of the no-op plugin
|
||||||
ReportError string
|
ReportResult string
|
||||||
ReportStderr string
|
ReportError string
|
||||||
Command string
|
ReportStderr string
|
||||||
CmdArgs skel.CmdArgs
|
ReportVersionSupport []string
|
||||||
|
|
||||||
|
// Command stores the CNI command that the plugin received
|
||||||
|
Command string
|
||||||
|
|
||||||
|
// CmdArgs stores the CNI Args and Env Vars that the plugin recieved
|
||||||
|
CmdArgs skel.CmdArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadDebug will return a debug file recorded by the noop plugin
|
// ReadDebug will return a debug file recorded by the noop plugin
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/skel"
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
"github.com/containernetworking/cni/plugins/test/noop/debug"
|
"github.com/containernetworking/cni/plugins/test/noop/debug"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -62,6 +63,23 @@ func debugBehavior(args *skel.CmdArgs, command string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func debugGetSupportedVersions() []string {
|
||||||
|
vers := []string{"0.-42.0", "0.1.0", "0.2.0"}
|
||||||
|
cniArgs := os.Getenv("CNI_ARGS")
|
||||||
|
if cniArgs == "" {
|
||||||
|
return vers
|
||||||
|
}
|
||||||
|
debugFilePath := strings.TrimPrefix(cniArgs, "DEBUG=")
|
||||||
|
debug, err := debug.ReadDebug(debugFilePath)
|
||||||
|
if err != nil {
|
||||||
|
panic("test setup error: unable to read debug file: " + err.Error())
|
||||||
|
}
|
||||||
|
if debug.ReportVersionSupport == nil {
|
||||||
|
return vers
|
||||||
|
}
|
||||||
|
return debug.ReportVersionSupport
|
||||||
|
}
|
||||||
|
|
||||||
func cmdAdd(args *skel.CmdArgs) error {
|
func cmdAdd(args *skel.CmdArgs) error {
|
||||||
return debugBehavior(args, "ADD")
|
return debugBehavior(args, "ADD")
|
||||||
}
|
}
|
||||||
@ -71,5 +89,6 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
skel.PluginMain(cmdAdd, cmdDel)
|
supportedVersions := debugGetSupportedVersions()
|
||||||
|
skel.PluginMain(cmdAdd, cmdDel, version.PluginSupports(supportedVersions...))
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/skel"
|
"github.com/containernetworking/cni/pkg/skel"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
|
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
@ -38,7 +39,10 @@ var _ = Describe("No-op plugin", func() {
|
|||||||
const reportResult = `{ "ip4": { "ip": "10.1.2.3/24" }, "dns": {} }`
|
const reportResult = `{ "ip4": { "ip": "10.1.2.3/24" }, "dns": {} }`
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
debug = &noop_debug.Debug{ReportResult: reportResult}
|
debug = &noop_debug.Debug{
|
||||||
|
ReportResult: reportResult,
|
||||||
|
ReportVersionSupport: []string{"0.1.0", "0.2.0", "0.3.0"},
|
||||||
|
}
|
||||||
|
|
||||||
debugFile, err := ioutil.TempFile("", "cni_debug")
|
debugFile, err := ioutil.TempFile("", "cni_debug")
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
@ -122,6 +126,25 @@ var _ = Describe("No-op plugin", func() {
|
|||||||
Expect(debug.Command).To(Equal("DEL"))
|
Expect(debug.Command).To(Equal("DEL"))
|
||||||
Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
|
Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the CNI_COMMAND is VERSION", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
cmd.Env[0] = "CNI_COMMAND=VERSION"
|
||||||
|
debug.ReportVersionSupport = []string{"0.123.0", "0.2.0"}
|
||||||
|
|
||||||
|
Expect(debug.WriteDebug(debugFileName)).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("claims to support the specified versions", func() {
|
||||||
|
session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Eventually(session).Should(gexec.Exit(0))
|
||||||
|
decoder := &version.PluginDecoder{}
|
||||||
|
pluginInfo, err := decoder.Decode(session.Out.Contents())
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(pluginInfo.SupportedVersions()).To(ConsistOf(
|
||||||
|
"0.123.0", "0.2.0"))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
2
test
2
test
@ -11,7 +11,7 @@ set -e
|
|||||||
|
|
||||||
source ./build
|
source ./build
|
||||||
|
|
||||||
TESTABLE="libcni plugins/ipam/dhcp plugins/ipam/host-local plugins/main/loopback pkg/invoke pkg/ns pkg/skel pkg/types pkg/utils plugins/main/ipvlan plugins/main/macvlan plugins/main/bridge plugins/main/ptp plugins/test/noop pkg/utils/hwaddr pkg/ip"
|
TESTABLE="libcni plugins/ipam/dhcp plugins/ipam/host-local plugins/main/loopback pkg/invoke pkg/ns pkg/skel pkg/types pkg/utils plugins/main/ipvlan plugins/main/macvlan plugins/main/bridge plugins/main/ptp plugins/test/noop pkg/utils/hwaddr pkg/ip pkg/version pkg/version/testhelpers"
|
||||||
FORMATTABLE="$TESTABLE pkg/testutils plugins/meta/flannel plugins/meta/tuning"
|
FORMATTABLE="$TESTABLE pkg/testutils plugins/meta/flannel plugins/meta/tuning"
|
||||||
|
|
||||||
# user has not provided PKG override
|
# user has not provided PKG override
|
||||||
|
Reference in New Issue
Block a user