Update CNI to v0.6.0

Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
Lantao Liu 2017-08-23 01:23:24 +00:00
parent 01137e4591
commit af83d3e1f7
17 changed files with 935 additions and 141 deletions

View File

@ -1,5 +1,5 @@
RUNC_VERSION=e775f0fba3ea329b8b766451c892c41a3d49594d RUNC_VERSION=e775f0fba3ea329b8b766451c892c41a3d49594d
CNI_VERSION=v0.4.0 CNI_VERSION=v0.6.0
CONTAINERD_VERSION=938810e706bbcdbcb937ce63ba3e7c9ca329af64 CONTAINERD_VERSION=938810e706bbcdbcb937ce63ba3e7c9ca329af64
CRITEST_VERSION=74bbd4e142f752f13c648d9dde23defed3e472a2 CRITEST_VERSION=74bbd4e142f752f13c648d9dde23defed3e472a2
KUBERNETES_VERSION=493ee8b28560c118cebd2165ba9ef0959cfa2bc3 KUBERNETES_VERSION=493ee8b28560c118cebd2165ba9ef0959cfa2bc3

View File

@ -2,15 +2,25 @@
[![Coverage Status](https://coveralls.io/repos/github/containernetworking/cni/badge.svg?branch=master)](https://coveralls.io/github/containernetworking/cni?branch=master) [![Coverage Status](https://coveralls.io/repos/github/containernetworking/cni/badge.svg?branch=master)](https://coveralls.io/github/containernetworking/cni?branch=master)
[![Slack Status](https://cryptic-tundra-43194.herokuapp.com/badge.svg)](https://cryptic-tundra-43194.herokuapp.com/) [![Slack Status](https://cryptic-tundra-43194.herokuapp.com/badge.svg)](https://cryptic-tundra-43194.herokuapp.com/)
![CNI Logo](logo.png)
---
# Community Sync Meeting
There is a community sync meeting for users and developers every 1-2 months. The next meeting will help on a Google Hangout and the link is in the [agenda](https://docs.google.com/document/d/10ECyT2mBGewsJUcmYmS8QNo1AcNgy2ZIe2xS7lShYhE/edit?usp=sharing) (Notes from previous meeting are also in this doc). The next meeting will be held on *Wednesday, June 21th* at *3:00pm UTC* [Add to Calendar]https://www.worldtimebuddy.com/?qm=1&lid=100,5,2643743,5391959&h=100&date=2017-6-21&sln=15-16).
---
# CNI - the Container Network Interface # CNI - the Container Network Interface
## What is CNI? ## What is CNI?
The CNI (_Container Network Interface_) project consists of a specification and libraries for writing plugins to configure network interfaces in Linux containers, along with a number of supported plugins. CNI (_Container Network Interface_), a [Cloud Native Computing Foundation](https://cncf.io) project, consists of a specification and libraries for writing plugins to configure network interfaces in Linux containers, along with a number of supported plugins.
CNI concerns itself only with network connectivity of containers and removing allocated resources when the container is deleted. CNI concerns itself only with network connectivity of containers and removing allocated resources when the container is deleted.
Because of this focus, CNI has a wide range of support and the specification is simple to implement. Because of this focus, CNI has a wide range of support and the specification is simple to implement.
As well as the [specification](SPEC.md), this repository contains the Go source code of a library for integrating CNI into applications, an example command-line tool, a template for making new plugins, and the supported plugins. As well as the [specification](SPEC.md), this repository contains the Go source code of a [library for integrating CNI into applications](libcni) and an [example command-line tool](cnitool) for executing CNI plugins. A [separate repository contains reference plugins](https://github.com/containernetworking/plugins) and a template for making new plugins.
The template code makes it straight-forward to create a CNI plugin for an existing container networking project. The template code makes it straight-forward to create a CNI plugin for an existing container networking project.
CNI also makes a good framework for creating a new container networking project from scratch. CNI also makes a good framework for creating a new container networking project from scratch.
@ -27,7 +37,8 @@ To avoid duplication, we think it is prudent to define a common interface betwee
- [rkt - container engine](https://coreos.com/blog/rkt-cni-networking.html) - [rkt - container engine](https://coreos.com/blog/rkt-cni-networking.html)
- [Kurma - container runtime](http://kurma.io/) - [Kurma - container runtime](http://kurma.io/)
- [Kubernetes - a system to simplify container operations](http://kubernetes.io/docs/admin/network-plugins/) - [Kubernetes - a system to simplify container operations](http://kubernetes.io/docs/admin/network-plugins/)
- [Cloud Foundry - a platform for cloud applications](https://github.com/cloudfoundry-incubator/netman-release) - [OpenShift - Kubernetes with additional enterprise features](https://github.com/openshift/origin/blob/master/docs/openshift_networking_requirements.md)
- [Cloud Foundry - a platform for cloud applications](https://github.com/cloudfoundry-incubator/cf-networking-release)
- [Mesos - a distributed systems kernel](https://github.com/apache/mesos/blob/master/docs/cni.md) - [Mesos - a distributed systems kernel](https://github.com/apache/mesos/blob/master/docs/cni.md)
### 3rd party plugins ### 3rd party plugins
@ -37,8 +48,14 @@ To avoid duplication, we think it is prudent to define a common interface betwee
- [SR-IOV](https://github.com/hustcat/sriov-cni) - [SR-IOV](https://github.com/hustcat/sriov-cni)
- [Cilium - BPF & XDP for containers](https://github.com/cilium/cilium) - [Cilium - BPF & XDP for containers](https://github.com/cilium/cilium)
- [Infoblox - enterprise IP address management for containers](https://github.com/infobloxopen/cni-infoblox) - [Infoblox - enterprise IP address management for containers](https://github.com/infobloxopen/cni-infoblox)
- [Multus - a Multi plugin](https://github.com/Intel-Corp/multus-cni)
- [Romana - Layer 3 CNI plugin supporting network policy for Kubernetes](https://github.com/romana/kube)
- [CNI-Genie - generic CNI network plugin](https://github.com/Huawei-PaaS/CNI-Genie)
- [Nuage CNI - Nuage Networks SDN plugin for network policy kubernetes support ](https://github.com/nuagenetworks/nuage-cni)
- [Silk - a CNI plugin designed for Cloud Foundry](https://github.com/cloudfoundry-incubator/silk)
- [Linen - a CNI plugin designed for overlay networks with Open vSwitch and fit in SDN/OpenFlow network environment](https://github.com/John-Lin/linen-cni)
The CNI team also maintains some [core plugins](plugins). The CNI team also maintains some [core plugins in a separate repository](https://github.com/containernetworking/plugins).
## Contributing to CNI ## Contributing to CNI
@ -50,19 +67,16 @@ If you intend to contribute to code or documentation, please read [CONTRIBUTING.
### Requirements ### Requirements
CNI requires Go 1.5+ to build. The CNI spec is language agnostic. To use the Go language libraries in this repository, you'll need a recent version of Go. Our [automated tests](https://travis-ci.org/containernetworking/cni/builds) cover Go versions 1.7 and 1.8.
Go 1.5 users will need to set GO15VENDOREXPERIMENT=1 to get vendored ### Reference Plugins
dependencies. This flag is set by default in 1.6.
### Included Plugins The CNI project maintains a set of [reference plugins](https://github.com/containernetworking/plugins) that implement the CNI specification.
NOTE: the reference plugins used to live in this repository but have been split out into a [separate repository](https://github.com/containernetworking/plugins) as of May 2017.
This repository includes a number of common plugins in the `plugins/` directory.
Please see the [Documentation/](Documentation/) directory for documentation about particular plugins.
### Running the plugins ### Running the plugins
The scripts/ directory contains two scripts, `priv-net-run.sh` and `docker-run.sh`, that can be used to exercise the plugins. After building and installing the [reference plugins](https://github.com/containernetworking/plugins), you can use the `priv-net-run.sh` and `docker-run.sh` scripts in the `scripts/` directory to exercise the plugins.
**note - priv-net-run.sh depends on `jq`** **note - priv-net-run.sh depends on `jq`**
@ -100,14 +114,15 @@ The directory `/etc/cni/net.d` is the default location in which the scripts will
Next, build the plugins: Next, build the plugins:
```bash ```bash
$ ./build $ cd $GOPATH/src/github.com/containernetworking/plugins
$ ./build.sh
``` ```
Finally, execute a command (`ifconfig` in this example) in a private network namespace that has joined the `mynet` network: Finally, execute a command (`ifconfig` in this example) in a private network namespace that has joined the `mynet` network:
```bash ```bash
$ CNI_PATH=`pwd`/bin $ CNI_PATH=$GOPATH/src/github.com/containernetworking/plugins/bin
$ cd scripts $ cd $GOPATH/src/github.com/containernetworking/cni/scripts
$ sudo CNI_PATH=$CNI_PATH ./priv-net-run.sh ifconfig $ sudo CNI_PATH=$CNI_PATH ./priv-net-run.sh ifconfig
eth0 Link encap:Ethernet HWaddr f2:c2:6f:54:b8:2b eth0 Link encap:Ethernet HWaddr f2:c2:6f:54:b8:2b
inet addr:10.22.0.2 Bcast:0.0.0.0 Mask:255.255.0.0 inet addr:10.22.0.2 Bcast:0.0.0.0 Mask:255.255.0.0
@ -136,8 +151,8 @@ Use the instructions in the previous section to define a netconf and build the p
Next, docker-run.sh script wraps `docker run`, to execute the plugins prior to entering the container: Next, docker-run.sh script wraps `docker run`, to execute the plugins prior to entering the container:
```bash ```bash
$ CNI_PATH=`pwd`/bin $ CNI_PATH=$GOPATH/src/github.com/containernetworking/plugins/bin
$ cd scripts $ cd $GOPATH/src/github.com/containernetworking/cni/scripts
$ sudo CNI_PATH=$CNI_PATH ./docker-run.sh --rm busybox:latest ifconfig $ sudo CNI_PATH=$CNI_PATH ./docker-run.sh --rm busybox:latest ifconfig
eth0 Link encap:Ethernet HWaddr fa:60:70:aa:07:d1 eth0 Link encap:Ethernet HWaddr fa:60:70:aa:07:d1
inet addr:10.22.0.2 Bcast:0.0.0.0 Mask:255.255.0.0 inet addr:10.22.0.2 Bcast:0.0.0.0 Mask:255.255.0.0

View File

@ -15,6 +15,7 @@
package libcni package libcni
import ( import (
"os"
"strings" "strings"
"github.com/containernetworking/cni/pkg/invoke" "github.com/containernetworking/cni/pkg/invoke"
@ -27,6 +28,12 @@ type RuntimeConf struct {
NetNS string NetNS string
IfName string IfName string
Args [][2]string Args [][2]string
// A dictionary of capability-specific data passed by the runtime
// to plugins as top-level keys in the 'runtimeConfig' dictionary
// of the plugin's stdin data. libcni will ensure that only keys
// in this map which match the capabilities of the plugin are passed
// to the plugin
CapabilityArgs map[string]interface{}
} }
type NetworkConfig struct { type NetworkConfig struct {
@ -34,8 +41,18 @@ type NetworkConfig struct {
Bytes []byte Bytes []byte
} }
type NetworkConfigList struct {
Name string
CNIVersion string
Plugins []*NetworkConfig
Bytes []byte
}
type CNI interface { type CNI interface {
AddNetwork(net *NetworkConfig, rt *RuntimeConf) (*types.Result, error) AddNetworkList(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
DelNetworkList(net *NetworkConfigList, rt *RuntimeConf) error
AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
DelNetwork(net *NetworkConfig, rt *RuntimeConf) error DelNetwork(net *NetworkConfig, rt *RuntimeConf) error
} }
@ -46,13 +63,120 @@ type CNIConfig struct {
// CNIConfig implements the CNI interface // CNIConfig implements the CNI interface
var _ CNI = &CNIConfig{} var _ CNI = &CNIConfig{}
// AddNetwork executes the plugin with the ADD command func buildOneConfig(list *NetworkConfigList, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) {
func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (*types.Result, error) { var err error
inject := map[string]interface{}{
"name": list.Name,
"cniVersion": list.CNIVersion,
}
// Add previous plugin result
if prevResult != nil {
inject["prevResult"] = prevResult
}
// Ensure every config uses the same name and version
orig, err = InjectConf(orig, inject)
if err != nil {
return nil, err
}
return injectRuntimeConfig(orig, rt)
}
// This function takes a libcni RuntimeConf structure and injects values into
// a "runtimeConfig" dictionary in the CNI network configuration JSON that
// will be passed to the plugin on stdin.
//
// Only "capabilities arguments" passed by the runtime are currently injected.
// These capabilities arguments are filtered through the plugin's advertised
// capabilities from its config JSON, and any keys in the CapabilityArgs
// matching plugin capabilities are added to the "runtimeConfig" dictionary
// sent to the plugin via JSON on stdin. For exmaple, if the plugin's
// capabilities include "portMappings", and the CapabilityArgs map includes a
// "portMappings" key, that key and its value are added to the "runtimeConfig"
// dictionary to be passed to the plugin's stdin.
func injectRuntimeConfig(orig *NetworkConfig, rt *RuntimeConf) (*NetworkConfig, error) {
var err error
rc := make(map[string]interface{})
for capability, supported := range orig.Network.Capabilities {
if !supported {
continue
}
if data, ok := rt.CapabilityArgs[capability]; ok {
rc[capability] = data
}
}
if len(rc) > 0 {
orig, err = InjectConf(orig, map[string]interface{}{"runtimeConfig": rc})
if err != nil {
return nil, err
}
}
return orig, nil
}
// AddNetworkList executes a sequence of plugins with the ADD command
func (c *CNIConfig) AddNetworkList(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
var prevResult types.Result
for _, net := range list.Plugins {
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path) pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
newConf, err := buildOneConfig(list, net, prevResult, rt)
if err != nil {
return nil, err
}
prevResult, err = invoke.ExecPluginWithResult(pluginPath, newConf.Bytes, c.args("ADD", rt))
if err != nil {
return nil, err
}
}
return prevResult, nil
}
// DelNetworkList executes a sequence of plugins with the DEL command
func (c *CNIConfig) DelNetworkList(list *NetworkConfigList, rt *RuntimeConf) error {
for i := len(list.Plugins) - 1; i >= 0; i-- {
net := list.Plugins[i]
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
if err != nil {
return err
}
newConf, err := buildOneConfig(list, net, nil, rt)
if err != nil {
return err
}
if err := invoke.ExecPluginWithoutResult(pluginPath, newConf.Bytes, c.args("DEL", rt)); err != nil {
return err
}
}
return nil
}
// AddNetwork executes the plugin with the ADD command
func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
if err != nil {
return nil, err
}
net, err = injectRuntimeConfig(net, rt)
if err != nil {
return nil, err
}
return invoke.ExecPluginWithResult(pluginPath, net.Bytes, c.args("ADD", rt)) return invoke.ExecPluginWithResult(pluginPath, net.Bytes, c.args("ADD", rt))
} }
@ -63,6 +187,11 @@ func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error {
return err return err
} }
net, err = injectRuntimeConfig(net, rt)
if err != nil {
return err
}
return invoke.ExecPluginWithoutResult(pluginPath, net.Bytes, c.args("DEL", rt)) return invoke.ExecPluginWithoutResult(pluginPath, net.Bytes, c.args("DEL", rt))
} }
@ -85,6 +214,6 @@ func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args {
NetNS: rt.NetNS, NetNS: rt.NetNS,
PluginArgs: rt.Args, PluginArgs: rt.Args,
IfName: rt.IfName, IfName: rt.IfName,
Path: strings.Join(c.Path, ":"), Path: strings.Join(c.Path, string(os.PathListSeparator)),
} }
} }

View File

@ -23,6 +23,23 @@ import (
"sort" "sort"
) )
type NotFoundError struct {
Dir string
Name string
}
func (e NotFoundError) Error() string {
return fmt.Sprintf(`no net configuration with name "%s" in %s`, e.Name, e.Dir)
}
type NoConfigsFoundError struct {
Dir string
}
func (e NoConfigsFoundError) Error() string {
return fmt.Sprintf(`no net configurations found in %s`, e.Dir)
}
func ConfFromBytes(bytes []byte) (*NetworkConfig, error) { func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
conf := &NetworkConfig{Bytes: bytes} conf := &NetworkConfig{Bytes: bytes}
if err := json.Unmarshal(bytes, &conf.Network); err != nil { if err := json.Unmarshal(bytes, &conf.Network); err != nil {
@ -39,7 +56,73 @@ func ConfFromFile(filename string) (*NetworkConfig, error) {
return ConfFromBytes(bytes) return ConfFromBytes(bytes)
} }
func ConfFiles(dir string) ([]string, error) { func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
rawList := make(map[string]interface{})
if err := json.Unmarshal(bytes, &rawList); err != nil {
return nil, fmt.Errorf("error parsing configuration list: %s", err)
}
rawName, ok := rawList["name"]
if !ok {
return nil, fmt.Errorf("error parsing configuration list: no name")
}
name, ok := rawName.(string)
if !ok {
return nil, fmt.Errorf("error parsing configuration list: invalid name type %T", rawName)
}
var cniVersion string
rawVersion, ok := rawList["cniVersion"]
if ok {
cniVersion, ok = rawVersion.(string)
if !ok {
return nil, fmt.Errorf("error parsing configuration list: invalid cniVersion type %T", rawVersion)
}
}
list := &NetworkConfigList{
Name: name,
CNIVersion: cniVersion,
Bytes: bytes,
}
var plugins []interface{}
plug, ok := rawList["plugins"]
if !ok {
return nil, fmt.Errorf("error parsing configuration list: no 'plugins' key")
}
plugins, ok = plug.([]interface{})
if !ok {
return nil, fmt.Errorf("error parsing configuration list: invalid 'plugins' type %T", plug)
}
if len(plugins) == 0 {
return nil, fmt.Errorf("error parsing configuration list: no plugins in list")
}
for i, conf := range plugins {
newBytes, err := json.Marshal(conf)
if err != nil {
return nil, fmt.Errorf("Failed to marshal plugin config %d: %v", i, err)
}
netConf, err := ConfFromBytes(newBytes)
if err != nil {
return nil, fmt.Errorf("Failed to parse plugin config %d: %v", i, err)
}
list.Plugins = append(list.Plugins, netConf)
}
return list, nil
}
func ConfListFromFile(filename string) (*NetworkConfigList, error) {
bytes, err := ioutil.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("error reading %s: %s", filename, err)
}
return ConfListFromBytes(bytes)
}
func ConfFiles(dir string, extensions []string) ([]string, error) {
// In part, adapted from rkt/networking/podenv.go#listFiles // In part, adapted from rkt/networking/podenv.go#listFiles
files, err := ioutil.ReadDir(dir) files, err := ioutil.ReadDir(dir)
switch { switch {
@ -56,20 +139,22 @@ func ConfFiles(dir string) ([]string, error) {
continue continue
} }
fileExt := filepath.Ext(f.Name()) fileExt := filepath.Ext(f.Name())
if fileExt == ".conf" || fileExt == ".json" { for _, ext := range extensions {
if fileExt == ext {
confFiles = append(confFiles, filepath.Join(dir, f.Name())) confFiles = append(confFiles, filepath.Join(dir, f.Name()))
} }
} }
}
return confFiles, nil return confFiles, nil
} }
func LoadConf(dir, name string) (*NetworkConfig, error) { func LoadConf(dir, name string) (*NetworkConfig, error) {
files, err := ConfFiles(dir) files, err := ConfFiles(dir, []string{".conf", ".json"})
switch { switch {
case err != nil: case err != nil:
return nil, err return nil, err
case len(files) == 0: case len(files) == 0:
return nil, fmt.Errorf("no net configurations found") return nil, NoConfigsFoundError{Dir: dir}
} }
sort.Strings(files) sort.Strings(files)
@ -82,25 +167,59 @@ func LoadConf(dir, name string) (*NetworkConfig, error) {
return conf, nil return conf, nil
} }
} }
return nil, fmt.Errorf(`no net configuration with name "%s" in %s`, name, dir) return nil, NotFoundError{dir, name}
} }
func InjectConf(original *NetworkConfig, key string, newValue interface{}) (*NetworkConfig, error) { func LoadConfList(dir, name string) (*NetworkConfigList, error) {
files, err := ConfFiles(dir, []string{".conflist"})
if err != nil {
return nil, err
}
sort.Strings(files)
for _, confFile := range files {
conf, err := ConfListFromFile(confFile)
if err != nil {
return nil, err
}
if conf.Name == name {
return conf, nil
}
}
// Try and load a network configuration file (instead of list)
// from the same name, then upconvert.
singleConf, err := LoadConf(dir, name)
if err != nil {
// A little extra logic so the error makes sense
if _, ok := err.(NoConfigsFoundError); len(files) != 0 && ok {
// Config lists found but no config files found
return nil, NotFoundError{dir, name}
}
return nil, err
}
return ConfListFromConf(singleConf)
}
func InjectConf(original *NetworkConfig, newValues map[string]interface{}) (*NetworkConfig, error) {
config := make(map[string]interface{}) config := make(map[string]interface{})
err := json.Unmarshal(original.Bytes, &config) err := json.Unmarshal(original.Bytes, &config)
if err != nil { if err != nil {
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err) return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
} }
for key, value := range newValues {
if key == "" { if key == "" {
return nil, fmt.Errorf("key value can not be empty") return nil, fmt.Errorf("keys cannot be empty")
} }
if newValue == nil { if value == nil {
return nil, fmt.Errorf("newValue must be specified") return nil, fmt.Errorf("key '%s' value must not be nil", key)
} }
config[key] = newValue config[key] = value
}
newBytes, err := json.Marshal(config) newBytes, err := json.Marshal(config)
if err != nil { if err != nil {
@ -109,3 +228,29 @@ func InjectConf(original *NetworkConfig, key string, newValue interface{}) (*Net
return ConfFromBytes(newBytes) return ConfFromBytes(newBytes)
} }
// ConfListFromConf "upconverts" a network config in to a NetworkConfigList,
// with the single network as the only entry in the list.
func ConfListFromConf(original *NetworkConfig) (*NetworkConfigList, error) {
// Re-deserialize the config's json, then make a raw map configlist.
// This may seem a bit strange, but it's to make the Bytes fields
// actually make sense. Otherwise, the generated json is littered with
// golang default values.
rawConfig := make(map[string]interface{})
if err := json.Unmarshal(original.Bytes, &rawConfig); err != nil {
return nil, err
}
rawConfigList := map[string]interface{}{
"name": original.Network.Name,
"cniVersion": original.Network.CNIVersion,
"plugins": []interface{}{rawConfig},
}
b, err := json.Marshal(rawConfigList)
if err != nil {
return nil, err
}
return ConfListFromBytes(b)
}

View File

@ -57,13 +57,16 @@ func (args *Args) AsEnv() []string {
pluginArgsStr = stringify(args.PluginArgs) pluginArgsStr = stringify(args.PluginArgs)
} }
env = append(env, // Ensure that the custom values are first, so any value present in
// the process environment won't override them.
env = append([]string{
"CNI_COMMAND=" + args.Command, "CNI_COMMAND=" + args.Command,
"CNI_CONTAINERID=" + args.ContainerID, "CNI_CONTAINERID=" + args.ContainerID,
"CNI_NETNS=" + args.NetNS, "CNI_NETNS=" + args.NetNS,
"CNI_ARGS=" + pluginArgsStr, "CNI_ARGS=" + pluginArgsStr,
"CNI_IFNAME=" + args.IfName, "CNI_IFNAME=" + args.IfName,
"CNI_PATH="+args.Path) "CNI_PATH=" + args.Path,
}, env...)
return env return env
} }

View File

@ -17,17 +17,17 @@ package invoke
import ( import (
"fmt" "fmt"
"os" "os"
"strings" "path/filepath"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
) )
func DelegateAdd(delegatePlugin string, netconf []byte) (*types.Result, error) { func DelegateAdd(delegatePlugin string, netconf []byte) (types.Result, error) {
if os.Getenv("CNI_COMMAND") != "ADD" { if os.Getenv("CNI_COMMAND") != "ADD" {
return nil, fmt.Errorf("CNI_COMMAND is not ADD") return nil, fmt.Errorf("CNI_COMMAND is not ADD")
} }
paths := strings.Split(os.Getenv("CNI_PATH"), ":") paths := filepath.SplitList(os.Getenv("CNI_PATH"))
pluginPath, err := FindInPath(delegatePlugin, paths) pluginPath, err := FindInPath(delegatePlugin, paths)
if err != nil { if err != nil {
@ -42,7 +42,7 @@ func DelegateDel(delegatePlugin string, netconf []byte) error {
return fmt.Errorf("CNI_COMMAND is not DEL") return fmt.Errorf("CNI_COMMAND is not DEL")
} }
paths := strings.Split(os.Getenv("CNI_PATH"), ":") paths := filepath.SplitList(os.Getenv("CNI_PATH"))
pluginPath, err := FindInPath(delegatePlugin, paths) pluginPath, err := FindInPath(delegatePlugin, paths)
if err != nil { if err != nil {

View File

@ -15,7 +15,6 @@
package invoke package invoke
import ( import (
"encoding/json"
"fmt" "fmt"
"os" "os"
@ -23,7 +22,7 @@ import (
"github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/version"
) )
func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (*types.Result, error) { func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (types.Result, error) {
return defaultPluginExec.WithResult(pluginPath, netconf, args) return defaultPluginExec.WithResult(pluginPath, netconf, args)
} }
@ -49,15 +48,20 @@ type PluginExec struct {
} }
} }
func (e *PluginExec) WithResult(pluginPath string, netconf []byte, args CNIArgs) (*types.Result, error) { func (e *PluginExec) WithResult(pluginPath string, netconf []byte, args CNIArgs) (types.Result, error) {
stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv()) stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv())
if err != nil { if err != nil {
return nil, err return nil, err
} }
res := &types.Result{} // Plugin must return result in same version as specified in netconf
err = json.Unmarshal(stdoutBytes, res) versionDecoder := &version.ConfigDecoder{}
return res, err confVersion, err := versionDecoder.Decode(netconf)
if err != nil {
return nil, err
}
return version.NewResult(confVersion, stdoutBytes)
} }
func (e *PluginExec) WithoutResult(pluginPath string, netconf []byte, args CNIArgs) error { func (e *PluginExec) WithoutResult(pluginPath string, netconf []byte, args CNIArgs) error {

View File

@ -30,18 +30,14 @@ func FindInPath(plugin string, paths []string) (string, error) {
return "", fmt.Errorf("no paths provided") return "", fmt.Errorf("no paths provided")
} }
var fullpath string
for _, path := range paths { for _, path := range paths {
full := filepath.Join(path, plugin) for _, fe := range ExecutableFileExtensions {
if fi, err := os.Stat(full); err == nil && fi.Mode().IsRegular() { fullpath := filepath.Join(path, plugin) + fe
fullpath = full if fi, err := os.Stat(fullpath); err == nil && fi.Mode().IsRegular() {
break
}
}
if fullpath == "" {
return "", fmt.Errorf("failed to find plugin %q in path %s", plugin, paths)
}
return fullpath, nil return fullpath, nil
} }
}
}
return "", fmt.Errorf("failed to find plugin %q in path %s", plugin, paths)
}

View File

@ -0,0 +1,20 @@
// 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.
// +build darwin dragonfly freebsd linux netbsd opensbd solaris
package invoke
// Valid file extensions for plugin executables.
var ExecutableFileExtensions = []string{""}

View File

@ -0,0 +1,18 @@
// 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
// Valid file extensions for plugin executables.
var ExecutableFileExtensions = []string{".exe", ""}

View File

@ -50,13 +50,9 @@ func pluginErr(err error, output []byte) error {
if _, ok := err.(*exec.ExitError); ok { if _, ok := err.(*exec.ExitError); ok {
emsg := types.Error{} emsg := types.Error{}
if perr := json.Unmarshal(output, &emsg); perr != nil { if perr := json.Unmarshal(output, &emsg); perr != nil {
return fmt.Errorf("netplugin failed but error parsing its diagnostic message %q: %v", string(output), perr) emsg.Msg = fmt.Sprintf("netplugin failed but error parsing its diagnostic message %q: %v", string(output), perr)
} }
details := "" return &emsg
if emsg.Details != "" {
details = fmt.Sprintf("; %v", emsg.Details)
}
return fmt.Errorf("%v%v", emsg.Msg, details)
} }
return err return err

View File

@ -0,0 +1,135 @@
// 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 types020
import (
"encoding/json"
"fmt"
"net"
"os"
"github.com/containernetworking/cni/pkg/types"
)
const ImplementedSpecVersion string = "0.2.0"
var SupportedVersions = []string{"", "0.1.0", ImplementedSpecVersion}
// Compatibility types for CNI version 0.1.0 and 0.2.0
func NewResult(data []byte) (types.Result, error) {
result := &Result{}
if err := json.Unmarshal(data, result); err != nil {
return nil, err
}
return result, nil
}
func GetResult(r types.Result) (*Result, error) {
// We expect version 0.1.0/0.2.0 results
result020, err := r.GetAsVersion(ImplementedSpecVersion)
if err != nil {
return nil, err
}
result, ok := result020.(*Result)
if !ok {
return nil, fmt.Errorf("failed to convert result")
}
return result, nil
}
// Result is what gets returned from the plugin (via stdout) to the caller
type Result struct {
CNIVersion string `json:"cniVersion,omitempty"`
IP4 *IPConfig `json:"ip4,omitempty"`
IP6 *IPConfig `json:"ip6,omitempty"`
DNS types.DNS `json:"dns,omitempty"`
}
func (r *Result) Version() string {
return ImplementedSpecVersion
}
func (r *Result) GetAsVersion(version string) (types.Result, error) {
for _, supportedVersion := range SupportedVersions {
if version == supportedVersion {
r.CNIVersion = version
return r, nil
}
}
return nil, fmt.Errorf("cannot convert version %q to %s", SupportedVersions, version)
}
func (r *Result) Print() error {
data, err := json.MarshalIndent(r, "", " ")
if err != nil {
return err
}
_, err = os.Stdout.Write(data)
return err
}
// String returns a formatted string in the form of "[IP4: $1,][ IP6: $2,] DNS: $3" where
// $1 represents the receiver's IPv4, $2 represents the receiver's IPv6 and $3 the
// receiver's DNS. If $1 or $2 are nil, they won't be present in the returned string.
func (r *Result) String() string {
var str string
if r.IP4 != nil {
str = fmt.Sprintf("IP4:%+v, ", *r.IP4)
}
if r.IP6 != nil {
str += fmt.Sprintf("IP6:%+v, ", *r.IP6)
}
return fmt.Sprintf("%sDNS:%+v", str, r.DNS)
}
// IPConfig contains values necessary to configure an interface
type IPConfig struct {
IP net.IPNet
Gateway net.IP
Routes []types.Route
}
// net.IPNet is not JSON (un)marshallable so this duality is needed
// for our custom IPNet type
// JSON (un)marshallable types
type ipConfig struct {
IP types.IPNet `json:"ip"`
Gateway net.IP `json:"gateway,omitempty"`
Routes []types.Route `json:"routes,omitempty"`
}
func (c *IPConfig) MarshalJSON() ([]byte, error) {
ipc := ipConfig{
IP: types.IPNet(c.IP),
Gateway: c.Gateway,
Routes: c.Routes,
}
return json.Marshal(ipc)
}
func (c *IPConfig) UnmarshalJSON(data []byte) error {
ipc := ipConfig{}
if err := json.Unmarshal(data, &ipc); err != nil {
return err
}
c.IP = net.IPNet(ipc.IP)
c.Gateway = ipc.Gateway
c.Routes = ipc.Routes
return nil
}

View File

@ -63,6 +63,12 @@ func GetKeyField(keyString string, v reflect.Value) reflect.Value {
return v.Elem().FieldByName(keyString) return v.Elem().FieldByName(keyString)
} }
// UnmarshalableArgsError is used to indicate error unmarshalling args
// from the args-string in the form "K=V;K2=V2;..."
type UnmarshalableArgsError struct {
error
}
// LoadArgs parses args from a string in the form "K=V;K2=V2;..." // LoadArgs parses args from a string in the form "K=V;K2=V2;..."
func LoadArgs(args string, container interface{}) error { func LoadArgs(args string, container interface{}) error {
if args == "" { if args == "" {
@ -85,8 +91,13 @@ func LoadArgs(args string, container interface{}) error {
unknownArgs = append(unknownArgs, pair) unknownArgs = append(unknownArgs, pair)
continue continue
} }
keyFieldIface := keyField.Addr().Interface()
u := keyField.Addr().Interface().(encoding.TextUnmarshaler) u, ok := keyFieldIface.(encoding.TextUnmarshaler)
if !ok {
return UnmarshalableArgsError{fmt.Errorf(
"ARGS: cannot unmarshal into field '%s' - type '%s' does not implement encoding.TextUnmarshaler",
keyString, reflect.TypeOf(keyFieldIface))}
}
err := u.UnmarshalText([]byte(valueString)) err := u.UnmarshalText([]byte(valueString))
if err != nil { if err != nil {
return fmt.Errorf("ARGS: error parsing value of pair %q: %v)", pair, err) return fmt.Errorf("ARGS: error parsing value of pair %q: %v)", pair, err)

View File

@ -0,0 +1,300 @@
// 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 current
import (
"encoding/json"
"fmt"
"net"
"os"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/020"
)
const ImplementedSpecVersion string = "0.3.1"
var SupportedVersions = []string{"0.3.0", ImplementedSpecVersion}
func NewResult(data []byte) (types.Result, error) {
result := &Result{}
if err := json.Unmarshal(data, result); err != nil {
return nil, err
}
return result, nil
}
func GetResult(r types.Result) (*Result, error) {
resultCurrent, err := r.GetAsVersion(ImplementedSpecVersion)
if err != nil {
return nil, err
}
result, ok := resultCurrent.(*Result)
if !ok {
return nil, fmt.Errorf("failed to convert result")
}
return result, nil
}
var resultConverters = []struct {
versions []string
convert func(types.Result) (*Result, error)
}{
{types020.SupportedVersions, convertFrom020},
{SupportedVersions, convertFrom030},
}
func convertFrom020(result types.Result) (*Result, error) {
oldResult, err := types020.GetResult(result)
if err != nil {
return nil, err
}
newResult := &Result{
CNIVersion: ImplementedSpecVersion,
DNS: oldResult.DNS,
Routes: []*types.Route{},
}
if oldResult.IP4 != nil {
newResult.IPs = append(newResult.IPs, &IPConfig{
Version: "4",
Address: oldResult.IP4.IP,
Gateway: oldResult.IP4.Gateway,
})
for _, route := range oldResult.IP4.Routes {
gw := route.GW
if gw == nil {
gw = oldResult.IP4.Gateway
}
newResult.Routes = append(newResult.Routes, &types.Route{
Dst: route.Dst,
GW: gw,
})
}
}
if oldResult.IP6 != nil {
newResult.IPs = append(newResult.IPs, &IPConfig{
Version: "6",
Address: oldResult.IP6.IP,
Gateway: oldResult.IP6.Gateway,
})
for _, route := range oldResult.IP6.Routes {
gw := route.GW
if gw == nil {
gw = oldResult.IP6.Gateway
}
newResult.Routes = append(newResult.Routes, &types.Route{
Dst: route.Dst,
GW: gw,
})
}
}
if len(newResult.IPs) == 0 {
return nil, fmt.Errorf("cannot convert: no valid IP addresses")
}
return newResult, nil
}
func convertFrom030(result types.Result) (*Result, error) {
newResult, ok := result.(*Result)
if !ok {
return nil, fmt.Errorf("failed to convert result")
}
newResult.CNIVersion = ImplementedSpecVersion
return newResult, nil
}
func NewResultFromResult(result types.Result) (*Result, error) {
version := result.Version()
for _, converter := range resultConverters {
for _, supportedVersion := range converter.versions {
if version == supportedVersion {
return converter.convert(result)
}
}
}
return nil, fmt.Errorf("unsupported CNI result22 version %q", version)
}
// Result is what gets returned from the plugin (via stdout) to the caller
type Result struct {
CNIVersion string `json:"cniVersion,omitempty"`
Interfaces []*Interface `json:"interfaces,omitempty"`
IPs []*IPConfig `json:"ips,omitempty"`
Routes []*types.Route `json:"routes,omitempty"`
DNS types.DNS `json:"dns,omitempty"`
}
// Convert to the older 0.2.0 CNI spec Result type
func (r *Result) convertTo020() (*types020.Result, error) {
oldResult := &types020.Result{
CNIVersion: types020.ImplementedSpecVersion,
DNS: r.DNS,
}
for _, ip := range r.IPs {
// Only convert the first IP address of each version as 0.2.0
// and earlier cannot handle multiple IP addresses
if ip.Version == "4" && oldResult.IP4 == nil {
oldResult.IP4 = &types020.IPConfig{
IP: ip.Address,
Gateway: ip.Gateway,
}
} else if ip.Version == "6" && oldResult.IP6 == nil {
oldResult.IP6 = &types020.IPConfig{
IP: ip.Address,
Gateway: ip.Gateway,
}
}
if oldResult.IP4 != nil && oldResult.IP6 != nil {
break
}
}
for _, route := range r.Routes {
is4 := route.Dst.IP.To4() != nil
if is4 && oldResult.IP4 != nil {
oldResult.IP4.Routes = append(oldResult.IP4.Routes, types.Route{
Dst: route.Dst,
GW: route.GW,
})
} else if !is4 && oldResult.IP6 != nil {
oldResult.IP6.Routes = append(oldResult.IP6.Routes, types.Route{
Dst: route.Dst,
GW: route.GW,
})
}
}
if oldResult.IP4 == nil && oldResult.IP6 == nil {
return nil, fmt.Errorf("cannot convert: no valid IP addresses")
}
return oldResult, nil
}
func (r *Result) Version() string {
return ImplementedSpecVersion
}
func (r *Result) GetAsVersion(version string) (types.Result, error) {
switch version {
case "0.3.0", ImplementedSpecVersion:
r.CNIVersion = version
return r, nil
case types020.SupportedVersions[0], types020.SupportedVersions[1], types020.SupportedVersions[2]:
return r.convertTo020()
}
return nil, fmt.Errorf("cannot convert version 0.3.x to %q", version)
}
func (r *Result) Print() error {
data, err := json.MarshalIndent(r, "", " ")
if err != nil {
return err
}
_, err = os.Stdout.Write(data)
return err
}
// String returns a formatted string in the form of "[Interfaces: $1,][ IP: $2,] DNS: $3" where
// $1 represents the receiver's Interfaces, $2 represents the receiver's IP addresses and $3 the
// receiver's DNS. If $1 or $2 are nil, they won't be present in the returned string.
func (r *Result) String() string {
var str string
if len(r.Interfaces) > 0 {
str += fmt.Sprintf("Interfaces:%+v, ", r.Interfaces)
}
if len(r.IPs) > 0 {
str += fmt.Sprintf("IP:%+v, ", r.IPs)
}
if len(r.Routes) > 0 {
str += fmt.Sprintf("Routes:%+v, ", r.Routes)
}
return fmt.Sprintf("%sDNS:%+v", str, r.DNS)
}
// Convert this old version result to the current CNI version result
func (r *Result) Convert() (*Result, error) {
return r, nil
}
// Interface contains values about the created interfaces
type Interface struct {
Name string `json:"name"`
Mac string `json:"mac,omitempty"`
Sandbox string `json:"sandbox,omitempty"`
}
func (i *Interface) String() string {
return fmt.Sprintf("%+v", *i)
}
// Int returns a pointer to the int value passed in. Used to
// set the IPConfig.Interface field.
func Int(v int) *int {
return &v
}
// IPConfig contains values necessary to configure an IP address on an interface
type IPConfig struct {
// IP version, either "4" or "6"
Version string
// Index into Result structs Interfaces list
Interface *int
Address net.IPNet
Gateway net.IP
}
func (i *IPConfig) String() string {
return fmt.Sprintf("%+v", *i)
}
// JSON (un)marshallable types
type ipConfig struct {
Version string `json:"version"`
Interface *int `json:"interface,omitempty"`
Address types.IPNet `json:"address"`
Gateway net.IP `json:"gateway,omitempty"`
}
func (c *IPConfig) MarshalJSON() ([]byte, error) {
ipc := ipConfig{
Version: c.Version,
Interface: c.Interface,
Address: types.IPNet(c.Address),
Gateway: c.Gateway,
}
return json.Marshal(ipc)
}
func (c *IPConfig) UnmarshalJSON(data []byte) error {
ipc := ipConfig{}
if err := json.Unmarshal(data, &ipc); err != nil {
return err
}
c.Version = ipc.Version
c.Interface = ipc.Interface
c.Address = net.IPNet(ipc.Address)
c.Gateway = ipc.Gateway
return nil
}

View File

@ -16,6 +16,7 @@ package types
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net" "net"
"os" "os"
@ -61,42 +62,46 @@ type NetConf struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
Capabilities map[string]bool `json:"capabilities,omitempty"`
IPAM struct { IPAM struct {
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
} `json:"ipam,omitempty"` } `json:"ipam,omitempty"`
DNS DNS `json:"dns"` DNS DNS `json:"dns"`
} }
// Result is what gets returned from the plugin (via stdout) to the caller // NetConfList describes an ordered list of networks.
type Result struct { type NetConfList struct {
IP4 *IPConfig `json:"ip4,omitempty"` CNIVersion string `json:"cniVersion,omitempty"`
IP6 *IPConfig `json:"ip6,omitempty"`
DNS DNS `json:"dns,omitempty"` Name string `json:"name,omitempty"`
Plugins []*NetConf `json:"plugins,omitempty"`
} }
func (r *Result) Print() error { type ResultFactoryFunc func([]byte) (Result, error)
return prettyPrint(r)
// Result is an interface that provides the result of plugin execution
type Result interface {
// The highest CNI specification result verison the result supports
// without having to convert
Version() string
// Returns the result converted into the requested CNI specification
// result version, or an error if conversion failed
GetAsVersion(version string) (Result, error)
// Prints the result in JSON format to stdout
Print() error
// Returns a JSON string representation of the result
String() string
} }
// String returns a formatted string in the form of "[IP4: $1,][ IP6: $2,] DNS: $3" where func PrintResult(result Result, version string) error {
// $1 represents the receiver's IPv4, $2 represents the receiver's IPv6 and $3 the newResult, err := result.GetAsVersion(version)
// receiver's DNS. If $1 or $2 are nil, they won't be present in the returned string. if err != nil {
func (r *Result) String() string { return err
var str string
if r.IP4 != nil {
str = fmt.Sprintf("IP4:%+v, ", *r.IP4)
} }
if r.IP6 != nil { return newResult.Print()
str += fmt.Sprintf("IP6:%+v, ", *r.IP6)
}
return fmt.Sprintf("%sDNS:%+v", str, r.DNS)
}
// IPConfig contains values necessary to configure an interface
type IPConfig struct {
IP net.IPNet
Gateway net.IP
Routes []Route
} }
// DNS contains values interesting for DNS resolvers // DNS contains values interesting for DNS resolvers
@ -112,6 +117,10 @@ type Route struct {
GW net.IP GW net.IP
} }
func (r *Route) String() string {
return fmt.Sprintf("%+v", *r)
}
// Well known error codes // Well known error codes
// see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
const ( const (
@ -127,7 +136,11 @@ type Error struct {
} }
func (e *Error) Error() string { func (e *Error) Error() string {
return e.Msg details := ""
if e.Details != "" {
details = fmt.Sprintf("; %v", e.Details)
}
return fmt.Sprintf("%v%v", e.Msg, details)
} }
func (e *Error) Print() error { func (e *Error) Print() error {
@ -138,39 +151,11 @@ func (e *Error) Print() error {
// for our custom IPNet type // for our custom IPNet type
// JSON (un)marshallable types // JSON (un)marshallable types
type ipConfig struct {
IP IPNet `json:"ip"`
Gateway net.IP `json:"gateway,omitempty"`
Routes []Route `json:"routes,omitempty"`
}
type route struct { type route struct {
Dst IPNet `json:"dst"` Dst IPNet `json:"dst"`
GW net.IP `json:"gw,omitempty"` GW net.IP `json:"gw,omitempty"`
} }
func (c *IPConfig) MarshalJSON() ([]byte, error) {
ipc := ipConfig{
IP: IPNet(c.IP),
Gateway: c.Gateway,
Routes: c.Routes,
}
return json.Marshal(ipc)
}
func (c *IPConfig) UnmarshalJSON(data []byte) error {
ipc := ipConfig{}
if err := json.Unmarshal(data, &ipc); err != nil {
return err
}
c.IP = net.IPNet(ipc.IP)
c.Gateway = ipc.Gateway
c.Routes = ipc.Routes
return nil
}
func (r *Route) UnmarshalJSON(data []byte) error { func (r *Route) UnmarshalJSON(data []byte) error {
rt := route{} rt := route{}
if err := json.Unmarshal(data, &rt); err != nil { if err := json.Unmarshal(data, &rt); err != nil {
@ -199,3 +184,6 @@ func prettyPrint(obj interface{}) error {
_, err = os.Stdout.Write(data) _, err = os.Stdout.Write(data)
return err return err
} }
// NotImplementedError is used to indicate that a method is not implemented for the given platform
var NotImplementedError = errors.New("Not Implemented")

View File

@ -18,11 +18,11 @@ import "fmt"
type ErrorIncompatible struct { type ErrorIncompatible struct {
Config string Config string
Plugin []string Supported []string
} }
func (e *ErrorIncompatible) Details() string { func (e *ErrorIncompatible) Details() string {
return fmt.Sprintf("config is %q, plugin supports %q", e.Config, e.Plugin) return fmt.Sprintf("config is %q, plugin supports %q", e.Config, e.Supported)
} }
func (e *ErrorIncompatible) Error() string { func (e *ErrorIncompatible) Error() string {
@ -31,17 +31,19 @@ func (e *ErrorIncompatible) Error() string {
type Reconciler struct{} type Reconciler struct{}
func (*Reconciler) Check(configVersion string, pluginInfo PluginInfo) *ErrorIncompatible { func (r *Reconciler) Check(configVersion string, pluginInfo PluginInfo) *ErrorIncompatible {
pluginVersions := pluginInfo.SupportedVersions() return r.CheckRaw(configVersion, pluginInfo.SupportedVersions())
}
for _, pluginVersion := range pluginVersions { func (*Reconciler) CheckRaw(configVersion string, supportedVersions []string) *ErrorIncompatible {
if configVersion == pluginVersion { for _, supportedVersion := range supportedVersions {
if configVersion == supportedVersion {
return nil return nil
} }
} }
return &ErrorIncompatible{ return &ErrorIncompatible{
Config: configVersion, Config: configVersion,
Plugin: pluginVersions, Supported: supportedVersions,
} }
} }

View File

@ -14,9 +14,17 @@
package version package version
import (
"fmt"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/020"
"github.com/containernetworking/cni/pkg/types/current"
)
// 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.3.1"
} }
// Legacy PluginInfo describes a plugin that is backwards compatible with the // Legacy PluginInfo describes a plugin that is backwards compatible with the
@ -27,3 +35,27 @@ func Current() string {
// Any future CNI spec versions which meet this definition should be added to // Any future CNI spec versions which meet this definition should be added to
// this list. // this list.
var Legacy = PluginSupports("0.1.0", "0.2.0") var Legacy = PluginSupports("0.1.0", "0.2.0")
var All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1")
var resultFactories = []struct {
supportedVersions []string
newResult types.ResultFactoryFunc
}{
{current.SupportedVersions, current.NewResult},
{types020.SupportedVersions, types020.NewResult},
}
// Finds a Result object matching the requested version (if any) and asks
// that object to parse the plugin result, returning an error if parsing failed.
func NewResult(version string, resultBytes []byte) (types.Result, error) {
reconciler := &Reconciler{}
for _, resultFactory := range resultFactories {
err := reconciler.CheckRaw(version, resultFactory.supportedVersions)
if err == nil {
// Result supports this version
return resultFactory.newResult(resultBytes)
}
}
return nil, fmt.Errorf("unsupported CNI result version %q", version)
}