Quellcode durchsuchen

build(deps): bump github.com/containernetworking/cni from 0.6.0 to 0.8.1 (#293)

dependabot[bot] vor 4 Jahren
Ursprung
Commit
9a9131d965

+ 1 - 1
go.mod

@@ -5,7 +5,7 @@ go 1.17
 require (
 	github.com/awalterschulze/gographviz v0.0.0-20181013152038-b2885df04310
 	github.com/campoy/embedmd v1.0.0
-	github.com/containernetworking/cni v0.6.0
+	github.com/containernetworking/cni v0.8.1
 	github.com/containernetworking/plugins v0.6.0
 	github.com/coreos/go-iptables v0.4.0
 	github.com/go-kit/kit v0.9.0

+ 2 - 2
go.sum

@@ -68,8 +68,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
 github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
-github.com/containernetworking/cni v0.6.0 h1:FXICGBZNMtdHlW65trpoHviHctQD3seWhRRcqp2hMOU=
-github.com/containernetworking/cni v0.6.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
+github.com/containernetworking/cni v0.8.1 h1:7zpDnQ3T3s4ucOuJ/ZCLrYBxzkg0AELFfII3Epo9TmI=
+github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
 github.com/containernetworking/plugins v0.6.0 h1:bqPT7yYisnWs+FrtgY5/qLEB9QZ/6z11wMNCwSdzZm0=
 github.com/containernetworking/plugins v0.6.0/go.mod h1:dagHaAhNjXjT9QYOklkKJDGaQPTg4pf//FrUcJeb7FU=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=

+ 503 - 49
vendor/github.com/containernetworking/cni/libcni/api.go

@@ -15,14 +15,32 @@
 package libcni
 
 import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
 	"os"
+	"path/filepath"
 	"strings"
 
 	"github.com/containernetworking/cni/pkg/invoke"
 	"github.com/containernetworking/cni/pkg/types"
+	"github.com/containernetworking/cni/pkg/utils"
 	"github.com/containernetworking/cni/pkg/version"
 )
 
+var (
+	CacheDir = "/var/lib/cni"
+)
+
+const (
+	CNICacheV1 = "cniCacheV1"
+)
+
+// A RuntimeConf holds the arguments to one invocation of a CNI plugin
+// excepting the network configuration, with the nested exception that
+// the `runtimeConfig` from the network configuration is included
+// here.
 type RuntimeConf struct {
 	ContainerID string
 	NetNS       string
@@ -34,6 +52,9 @@ type RuntimeConf struct {
 	// in this map which match the capabilities of the plugin are passed
 	// to the plugin
 	CapabilityArgs map[string]interface{}
+
+	// DEPRECATED. Will be removed in a future release.
+	CacheDir string
 }
 
 type NetworkConfig struct {
@@ -42,33 +63,64 @@ type NetworkConfig struct {
 }
 
 type NetworkConfigList struct {
-	Name       string
-	CNIVersion string
-	Plugins    []*NetworkConfig
-	Bytes      []byte
+	Name         string
+	CNIVersion   string
+	DisableCheck bool
+	Plugins      []*NetworkConfig
+	Bytes        []byte
 }
 
 type CNI interface {
-	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
+	AddNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
+	CheckNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
+	DelNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
+	GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
+	GetNetworkListCachedConfig(net *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
+
+	AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
+	CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error
+	DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error
+	GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
+	GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
+
+	ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, error)
+	ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error)
 }
 
 type CNIConfig struct {
-	Path []string
+	Path     []string
+	exec     invoke.Exec
+	cacheDir string
 }
 
 // CNIConfig implements the CNI interface
 var _ CNI = &CNIConfig{}
 
-func buildOneConfig(list *NetworkConfigList, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) {
+// NewCNIConfig returns a new CNIConfig object that will search for plugins
+// in the given paths and use the given exec interface to run those plugins,
+// or if the exec interface is not given, will use a default exec handler.
+func NewCNIConfig(path []string, exec invoke.Exec) *CNIConfig {
+	return NewCNIConfigWithCacheDir(path, "", exec)
+}
+
+// NewCNIConfigWithCacheDir returns a new CNIConfig object that will search for plugins
+// in the given paths use the given exec interface to run those plugins,
+// or if the exec interface is not given, will use a default exec handler.
+// The given cache directory will be used for temporary data storage when needed.
+func NewCNIConfigWithCacheDir(path []string, cacheDir string, exec invoke.Exec) *CNIConfig {
+	return &CNIConfig{
+		Path:     path,
+		cacheDir: cacheDir,
+		exec:     exec,
+	}
+}
+
+func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) {
 	var err error
 
 	inject := map[string]interface{}{
-		"name":       list.Name,
-		"cniVersion": list.CNIVersion,
+		"name":       name,
+		"cniVersion": cniVersion,
 	}
 	// Add previous plugin result
 	if prevResult != nil {
@@ -92,7 +144,7 @@ func buildOneConfig(list *NetworkConfigList, orig *NetworkConfig, prevResult typ
 // 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
+// sent to the plugin via JSON on stdin.  For example, 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.
@@ -119,91 +171,493 @@ func injectRuntimeConfig(orig *NetworkConfig, rt *RuntimeConf) (*NetworkConfig,
 	return orig, nil
 }
 
+// ensure we have a usable exec if the CNIConfig was not given one
+func (c *CNIConfig) ensureExec() invoke.Exec {
+	if c.exec == nil {
+		c.exec = &invoke.DefaultExec{
+			RawExec:       &invoke.RawExec{Stderr: os.Stderr},
+			PluginDecoder: version.PluginDecoder{},
+		}
+	}
+	return c.exec
+}
+
+type cachedInfo struct {
+	Kind           string                 `json:"kind"`
+	ContainerID    string                 `json:"containerId"`
+	Config         []byte                 `json:"config"`
+	IfName         string                 `json:"ifName"`
+	NetworkName    string                 `json:"networkName"`
+	CniArgs        [][2]string            `json:"cniArgs,omitempty"`
+	CapabilityArgs map[string]interface{} `json:"capabilityArgs,omitempty"`
+	RawResult      map[string]interface{} `json:"result,omitempty"`
+	Result         types.Result           `json:"-"`
+}
+
+// getCacheDir returns the cache directory in this order:
+// 1) global cacheDir from CNIConfig object
+// 2) deprecated cacheDir from RuntimeConf object
+// 3) fall back to default cache directory
+func (c *CNIConfig) getCacheDir(rt *RuntimeConf) string {
+	if c.cacheDir != "" {
+		return c.cacheDir
+	}
+	if rt.CacheDir != "" {
+		return rt.CacheDir
+	}
+	return CacheDir
+}
+
+func (c *CNIConfig) getCacheFilePath(netName string, rt *RuntimeConf) (string, error) {
+	if netName == "" || rt.ContainerID == "" || rt.IfName == "" {
+		return "", fmt.Errorf("cache file path requires network name (%q), container ID (%q), and interface name (%q)", netName, rt.ContainerID, rt.IfName)
+	}
+	return filepath.Join(c.getCacheDir(rt), "results", fmt.Sprintf("%s-%s-%s", netName, rt.ContainerID, rt.IfName)), nil
+}
+
+func (c *CNIConfig) cacheAdd(result types.Result, config []byte, netName string, rt *RuntimeConf) error {
+	cached := cachedInfo{
+		Kind:           CNICacheV1,
+		ContainerID:    rt.ContainerID,
+		Config:         config,
+		IfName:         rt.IfName,
+		NetworkName:    netName,
+		CniArgs:        rt.Args,
+		CapabilityArgs: rt.CapabilityArgs,
+	}
+
+	// We need to get type.Result into cachedInfo as JSON map
+	// Marshal to []byte, then Unmarshal into cached.RawResult
+	data, err := json.Marshal(result)
+	if err != nil {
+		return err
+	}
+
+	err = json.Unmarshal(data, &cached.RawResult)
+	if err != nil {
+		return err
+	}
+
+	newBytes, err := json.Marshal(&cached)
+	if err != nil {
+		return err
+	}
+
+	fname, err := c.getCacheFilePath(netName, rt)
+	if err != nil {
+		return err
+	}
+	if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil {
+		return err
+	}
+
+	return ioutil.WriteFile(fname, newBytes, 0600)
+}
+
+func (c *CNIConfig) cacheDel(netName string, rt *RuntimeConf) error {
+	fname, err := c.getCacheFilePath(netName, rt)
+	if err != nil {
+		// Ignore error
+		return nil
+	}
+	return os.Remove(fname)
+}
+
+func (c *CNIConfig) getCachedConfig(netName string, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
+	var bytes []byte
+
+	fname, err := c.getCacheFilePath(netName, rt)
+	if err != nil {
+		return nil, nil, err
+	}
+	bytes, err = ioutil.ReadFile(fname)
+	if err != nil {
+		// Ignore read errors; the cached result may not exist on-disk
+		return nil, nil, nil
+	}
+
+	unmarshaled := cachedInfo{}
+	if err := json.Unmarshal(bytes, &unmarshaled); err != nil {
+		return nil, nil, fmt.Errorf("failed to unmarshal cached network %q config: %v", netName, err)
+	}
+	if unmarshaled.Kind != CNICacheV1 {
+		return nil, nil, fmt.Errorf("read cached network %q config has wrong kind: %v", netName, unmarshaled.Kind)
+	}
+
+	newRt := *rt
+	if unmarshaled.CniArgs != nil {
+		newRt.Args = unmarshaled.CniArgs
+	}
+	newRt.CapabilityArgs = unmarshaled.CapabilityArgs
+
+	return unmarshaled.Config, &newRt, nil
+}
+
+func (c *CNIConfig) getLegacyCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) {
+	fname, err := c.getCacheFilePath(netName, rt)
+	if err != nil {
+		return nil, err
+	}
+	data, err := ioutil.ReadFile(fname)
+	if err != nil {
+		// Ignore read errors; the cached result may not exist on-disk
+		return nil, nil
+	}
+
+	// Read the version of the cached result
+	decoder := version.ConfigDecoder{}
+	resultCniVersion, err := decoder.Decode(data)
+	if err != nil {
+		return nil, err
+	}
+
+	// Ensure we can understand the result
+	result, err := version.NewResult(resultCniVersion, data)
+	if err != nil {
+		return nil, err
+	}
+
+	// Convert to the config version to ensure plugins get prevResult
+	// in the same version as the config.  The cached result version
+	// should match the config version unless the config was changed
+	// while the container was running.
+	result, err = result.GetAsVersion(cniVersion)
+	if err != nil && resultCniVersion != cniVersion {
+		return nil, fmt.Errorf("failed to convert cached result version %q to config version %q: %v", resultCniVersion, cniVersion, err)
+	}
+	return result, err
+}
+
+func (c *CNIConfig) getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) {
+	fname, err := c.getCacheFilePath(netName, rt)
+	if err != nil {
+		return nil, err
+	}
+	fdata, err := ioutil.ReadFile(fname)
+	if err != nil {
+		// Ignore read errors; the cached result may not exist on-disk
+		return nil, nil
+	}
+
+	cachedInfo := cachedInfo{}
+	if err := json.Unmarshal(fdata, &cachedInfo); err != nil || cachedInfo.Kind != CNICacheV1 {
+		return c.getLegacyCachedResult(netName, cniVersion, rt)
+	}
+
+	newBytes, err := json.Marshal(&cachedInfo.RawResult)
+	if err != nil {
+		return nil, fmt.Errorf("failed to marshal cached network %q config: %v", netName, err)
+	}
+
+	// Read the version of the cached result
+	decoder := version.ConfigDecoder{}
+	resultCniVersion, err := decoder.Decode(newBytes)
+	if err != nil {
+		return nil, err
+	}
+
+	// Ensure we can understand the result
+	result, err := version.NewResult(resultCniVersion, newBytes)
+	if err != nil {
+		return nil, err
+	}
+
+	// Convert to the config version to ensure plugins get prevResult
+	// in the same version as the config.  The cached result version
+	// should match the config version unless the config was changed
+	// while the container was running.
+	result, err = result.GetAsVersion(cniVersion)
+	if err != nil && resultCniVersion != cniVersion {
+		return nil, fmt.Errorf("failed to convert cached result version %q to config version %q: %v", resultCniVersion, cniVersion, err)
+	}
+	return result, err
+}
+
+// GetNetworkListCachedResult returns the cached Result of the previous
+// AddNetworkList() operation for a network list, or an error.
+func (c *CNIConfig) GetNetworkListCachedResult(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
+	return c.getCachedResult(list.Name, list.CNIVersion, rt)
+}
+
+// GetNetworkCachedResult returns the cached Result of the previous
+// AddNetwork() operation for a network, or an error.
+func (c *CNIConfig) GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
+	return c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
+}
+
+// GetNetworkListCachedConfig copies the input RuntimeConf to output
+// RuntimeConf with fields updated with info from the cached Config.
+func (c *CNIConfig) GetNetworkListCachedConfig(list *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
+	return c.getCachedConfig(list.Name, rt)
+}
+
+// GetNetworkCachedConfig copies the input RuntimeConf to output
+// RuntimeConf with fields updated with info from the cached Config.
+func (c *CNIConfig) GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
+	return c.getCachedConfig(net.Network.Name, rt)
+}
+
+func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {
+	c.ensureExec()
+	pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
+	if err != nil {
+		return nil, err
+	}
+	if err := utils.ValidateContainerID(rt.ContainerID); err != nil {
+		return nil, err
+	}
+	if err := utils.ValidateNetworkName(name); err != nil {
+		return nil, err
+	}
+	if err := utils.ValidateInterfaceName(rt.IfName); err != nil {
+		return nil, err
+	}
+
+	newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
+	if err != nil {
+		return nil, err
+	}
+
+	return invoke.ExecPluginWithResult(ctx, pluginPath, newConf.Bytes, c.args("ADD", rt), c.exec)
+}
+
 // 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
+func (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
+	var err error
+	var result types.Result
 	for _, net := range list.Plugins {
-		pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
+		result, err = c.addNetwork(ctx, list.Name, list.CNIVersion, net, result, rt)
 		if err != nil {
 			return nil, err
 		}
+	}
 
-		newConf, err := buildOneConfig(list, net, prevResult, rt)
-		if err != nil {
-			return nil, err
-		}
+	if err = c.cacheAdd(result, list.Bytes, list.Name, rt); err != nil {
+		return nil, fmt.Errorf("failed to set network %q cached result: %v", list.Name, err)
+	}
 
-		prevResult, err = invoke.ExecPluginWithResult(pluginPath, newConf.Bytes, c.args("ADD", rt))
-		if err != nil {
-			return nil, err
-		}
+	return result, nil
+}
+
+func (c *CNIConfig) checkNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error {
+	c.ensureExec()
+	pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
+	if err != nil {
+		return err
 	}
 
-	return prevResult, nil
+	newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
+	if err != nil {
+		return err
+	}
+
+	return invoke.ExecPluginWithoutResult(ctx, pluginPath, newConf.Bytes, c.args("CHECK", rt), c.exec)
 }
 
-// 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]
+// CheckNetworkList executes a sequence of plugins with the CHECK command
+func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) error {
+	// CHECK was added in CNI spec version 0.4.0 and higher
+	if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
+		return err
+	} else if !gtet {
+		return fmt.Errorf("configuration version %q does not support the CHECK command", list.CNIVersion)
+	}
 
-		pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
-		if err != nil {
+	if list.DisableCheck {
+		return nil
+	}
+
+	cachedResult, err := c.getCachedResult(list.Name, list.CNIVersion, rt)
+	if err != nil {
+		return fmt.Errorf("failed to get network %q cached result: %v", list.Name, err)
+	}
+
+	for _, net := range list.Plugins {
+		if err := c.checkNetwork(ctx, list.Name, list.CNIVersion, net, cachedResult, rt); err != nil {
 			return err
 		}
+	}
+
+	return nil
+}
+
+func (c *CNIConfig) delNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error {
+	c.ensureExec()
+	pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
+	if err != nil {
+		return err
+	}
+
+	newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
+	if err != nil {
+		return err
+	}
+
+	return invoke.ExecPluginWithoutResult(ctx, pluginPath, newConf.Bytes, c.args("DEL", rt), c.exec)
+}
 
-		newConf, err := buildOneConfig(list, net, nil, rt)
+// DelNetworkList executes a sequence of plugins with the DEL command
+func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) error {
+	var cachedResult types.Result
+
+	// Cached result on DEL was added in CNI spec version 0.4.0 and higher
+	if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
+		return err
+	} else if gtet {
+		cachedResult, err = c.getCachedResult(list.Name, list.CNIVersion, rt)
 		if err != nil {
-			return err
+			return fmt.Errorf("failed to get network %q cached result: %v", list.Name, err)
 		}
+	}
 
-		if err := invoke.ExecPluginWithoutResult(pluginPath, newConf.Bytes, c.args("DEL", rt)); err != nil {
+	for i := len(list.Plugins) - 1; i >= 0; i-- {
+		net := list.Plugins[i]
+		if err := c.delNetwork(ctx, list.Name, list.CNIVersion, net, cachedResult, rt); err != nil {
 			return err
 		}
 	}
+	_ = c.cacheDel(list.Name, rt)
 
 	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)
+func (c *CNIConfig) AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
+	result, err := c.addNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, nil, rt)
 	if err != nil {
 		return nil, err
 	}
 
-	net, err = injectRuntimeConfig(net, rt)
-	if err != nil {
-		return nil, err
+	if err = c.cacheAdd(result, net.Bytes, net.Network.Name, rt); err != nil {
+		return nil, fmt.Errorf("failed to set network %q cached result: %v", net.Network.Name, err)
 	}
 
-	return invoke.ExecPluginWithResult(pluginPath, net.Bytes, c.args("ADD", rt))
+	return result, nil
 }
 
-// DelNetwork executes the plugin with the DEL command
-func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error {
-	pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
+// CheckNetwork executes the plugin with the CHECK command
+func (c *CNIConfig) CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error {
+	// CHECK was added in CNI spec version 0.4.0 and higher
+	if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil {
+		return err
+	} else if !gtet {
+		return fmt.Errorf("configuration version %q does not support the CHECK command", net.Network.CNIVersion)
+	}
+
+	cachedResult, err := c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
 	if err != nil {
+		return fmt.Errorf("failed to get network %q cached result: %v", net.Network.Name, err)
+	}
+	return c.checkNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt)
+}
+
+// DelNetwork executes the plugin with the DEL command
+func (c *CNIConfig) DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error {
+	var cachedResult types.Result
+
+	// Cached result on DEL was added in CNI spec version 0.4.0 and higher
+	if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil {
 		return err
+	} else if gtet {
+		cachedResult, err = c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
+		if err != nil {
+			return fmt.Errorf("failed to get network %q cached result: %v", net.Network.Name, err)
+		}
 	}
 
-	net, err = injectRuntimeConfig(net, rt)
+	if err := c.delNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt); err != nil {
+		return err
+	}
+	_ = c.cacheDel(net.Network.Name, rt)
+	return nil
+}
+
+// ValidateNetworkList checks that a configuration is reasonably valid.
+// - all the specified plugins exist on disk
+// - every plugin supports the desired version.
+//
+// Returns a list of all capabilities supported by the configuration, or error
+func (c *CNIConfig) ValidateNetworkList(ctx context.Context, list *NetworkConfigList) ([]string, error) {
+	version := list.CNIVersion
+
+	// holding map for seen caps (in case of duplicates)
+	caps := map[string]interface{}{}
+
+	errs := []error{}
+	for _, net := range list.Plugins {
+		if err := c.validatePlugin(ctx, net.Network.Type, version); err != nil {
+			errs = append(errs, err)
+		}
+		for c, enabled := range net.Network.Capabilities {
+			if !enabled {
+				continue
+			}
+			caps[c] = struct{}{}
+		}
+	}
+
+	if len(errs) > 0 {
+		return nil, fmt.Errorf("%v", errs)
+	}
+
+	// make caps list
+	cc := make([]string, 0, len(caps))
+	for c := range caps {
+		cc = append(cc, c)
+	}
+
+	return cc, nil
+}
+
+// ValidateNetwork checks that a configuration is reasonably valid.
+// It uses the same logic as ValidateNetworkList)
+// Returns a list of capabilities
+func (c *CNIConfig) ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error) {
+	caps := []string{}
+	for c, ok := range net.Network.Capabilities {
+		if ok {
+			caps = append(caps, c)
+		}
+	}
+	if err := c.validatePlugin(ctx, net.Network.Type, net.Network.CNIVersion); err != nil {
+		return nil, err
+	}
+	return caps, nil
+}
+
+// validatePlugin checks that an individual plugin's configuration is sane
+func (c *CNIConfig) validatePlugin(ctx context.Context, pluginName, expectedVersion string) error {
+	c.ensureExec()
+	pluginPath, err := c.exec.FindInPath(pluginName, c.Path)
 	if err != nil {
 		return err
 	}
+	if expectedVersion == "" {
+		expectedVersion = "0.1.0"
+	}
 
-	return invoke.ExecPluginWithoutResult(pluginPath, net.Bytes, c.args("DEL", rt))
+	vi, err := invoke.GetVersionInfo(ctx, pluginPath, c.exec)
+	if err != nil {
+		return err
+	}
+	for _, vers := range vi.SupportedVersions() {
+		if vers == expectedVersion {
+			return nil
+		}
+	}
+	return fmt.Errorf("plugin %s does not support config version %q", pluginName, expectedVersion)
 }
 
 // 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)
+func (c *CNIConfig) GetVersionInfo(ctx context.Context, pluginType string) (version.PluginInfo, error) {
+	c.ensureExec()
+	pluginPath, err := c.exec.FindInPath(pluginType, c.Path)
 	if err != nil {
 		return nil, err
 	}
 
-	return invoke.GetVersionInfo(pluginPath)
+	return invoke.GetVersionInfo(ctx, pluginPath, c.exec)
 }
 
 // =====

+ 17 - 5
vendor/github.com/containernetworking/cni/libcni/conf.go

@@ -45,6 +45,9 @@ func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
 	if err := json.Unmarshal(bytes, &conf.Network); err != nil {
 		return nil, fmt.Errorf("error parsing configuration: %s", err)
 	}
+	if conf.Network.Type == "" {
+		return nil, fmt.Errorf("error parsing configuration: missing 'type'")
+	}
 	return conf, nil
 }
 
@@ -80,10 +83,19 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
 		}
 	}
 
+	disableCheck := false
+	if rawDisableCheck, ok := rawList["disableCheck"]; ok {
+		disableCheck, ok = rawDisableCheck.(bool)
+		if !ok {
+			return nil, fmt.Errorf("error parsing configuration list: invalid disableCheck type %T", rawDisableCheck)
+		}
+	}
+
 	list := &NetworkConfigList{
-		Name:       name,
-		CNIVersion: cniVersion,
-		Bytes:      bytes,
+		Name:         name,
+		DisableCheck: disableCheck,
+		CNIVersion:   cniVersion,
+		Bytes:        bytes,
 	}
 
 	var plugins []interface{}
@@ -102,11 +114,11 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
 	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)
+			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)
+			return nil, fmt.Errorf("failed to parse plugin config %d: %v", i, err)
 		}
 		list.Plugins = append(list.Plugins, netConf)
 	}

+ 58 - 12
vendor/github.com/containernetworking/cni/pkg/invoke/args.go

@@ -15,6 +15,7 @@
 package invoke
 
 import (
+	"fmt"
 	"os"
 	"strings"
 )
@@ -22,6 +23,8 @@ import (
 type CNIArgs interface {
 	// For use with os/exec; i.e., return nil to inherit the
 	// environment from this process
+	// For use in delegation; inherit the environment from this
+	// process and allow overrides
 	AsEnv() []string
 }
 
@@ -29,7 +32,7 @@ type inherited struct{}
 
 var inheritArgsFromEnv inherited
 
-func (_ *inherited) AsEnv() []string {
+func (*inherited) AsEnv() []string {
 	return nil
 }
 
@@ -57,17 +60,17 @@ func (args *Args) AsEnv() []string {
 		pluginArgsStr = stringify(args.PluginArgs)
 	}
 
-	// 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_CONTAINERID=" + args.ContainerID,
-		"CNI_NETNS=" + args.NetNS,
-		"CNI_ARGS=" + pluginArgsStr,
-		"CNI_IFNAME=" + args.IfName,
-		"CNI_PATH=" + args.Path,
-	}, env...)
-	return env
+	// Duplicated values which come first will be overridden, so we must put the
+	// custom values in the end to avoid being overridden by the process environments.
+	env = append(env,
+		"CNI_COMMAND="+args.Command,
+		"CNI_CONTAINERID="+args.ContainerID,
+		"CNI_NETNS="+args.NetNS,
+		"CNI_ARGS="+pluginArgsStr,
+		"CNI_IFNAME="+args.IfName,
+		"CNI_PATH="+args.Path,
+	)
+	return dedupEnv(env)
 }
 
 // taken from rkt/networking/net_plugin.go
@@ -80,3 +83,46 @@ func stringify(pluginArgs [][2]string) string {
 
 	return strings.Join(entries, ";")
 }
+
+// DelegateArgs implements the CNIArgs interface
+// used for delegation to inherit from environments
+// and allow some overrides like CNI_COMMAND
+var _ CNIArgs = &DelegateArgs{}
+
+type DelegateArgs struct {
+	Command string
+}
+
+func (d *DelegateArgs) AsEnv() []string {
+	env := os.Environ()
+
+	// The custom values should come in the end to override the existing
+	// process environment of the same key.
+	env = append(env,
+		"CNI_COMMAND="+d.Command,
+	)
+	return dedupEnv(env)
+}
+
+// dedupEnv returns a copy of env with any duplicates removed, in favor of later values.
+// Items not of the normal environment "key=value" form are preserved unchanged.
+func dedupEnv(env []string) []string {
+	out := make([]string, 0, len(env))
+	envMap := map[string]string{}
+
+	for _, kv := range env {
+		// find the first "=" in environment, if not, just keep it
+		eq := strings.Index(kv, "=")
+		if eq < 0 {
+			out = append(out, kv)
+			continue
+		}
+		envMap[kv[:eq]] = kv[eq+1:]
+	}
+
+	for k, v := range envMap {
+		out = append(out, fmt.Sprintf("%s=%s", k, v))
+	}
+
+	return out
+}

+ 39 - 12
vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go

@@ -15,39 +15,66 @@
 package invoke
 
 import (
-	"fmt"
+	"context"
 	"os"
 	"path/filepath"
 
 	"github.com/containernetworking/cni/pkg/types"
 )
 
-func DelegateAdd(delegatePlugin string, netconf []byte) (types.Result, error) {
-	if os.Getenv("CNI_COMMAND") != "ADD" {
-		return nil, fmt.Errorf("CNI_COMMAND is not ADD")
+func delegateCommon(delegatePlugin string, exec Exec) (string, Exec, error) {
+	if exec == nil {
+		exec = defaultExec
 	}
 
 	paths := filepath.SplitList(os.Getenv("CNI_PATH"))
+	pluginPath, err := exec.FindInPath(delegatePlugin, paths)
+	if err != nil {
+		return "", nil, err
+	}
+
+	return pluginPath, exec, nil
+}
 
-	pluginPath, err := FindInPath(delegatePlugin, paths)
+// DelegateAdd calls the given delegate plugin with the CNI ADD action and
+// JSON configuration
+func DelegateAdd(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) (types.Result, error) {
+	pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
 	if err != nil {
 		return nil, err
 	}
 
-	return ExecPluginWithResult(pluginPath, netconf, ArgsFromEnv())
+	// DelegateAdd will override the original "CNI_COMMAND" env from process with ADD
+	return ExecPluginWithResult(ctx, pluginPath, netconf, delegateArgs("ADD"), realExec)
 }
 
-func DelegateDel(delegatePlugin string, netconf []byte) error {
-	if os.Getenv("CNI_COMMAND") != "DEL" {
-		return fmt.Errorf("CNI_COMMAND is not DEL")
+// DelegateCheck calls the given delegate plugin with the CNI CHECK action and
+// JSON configuration
+func DelegateCheck(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
+	pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
+	if err != nil {
+		return err
 	}
 
-	paths := filepath.SplitList(os.Getenv("CNI_PATH"))
+	// DelegateCheck will override the original CNI_COMMAND env from process with CHECK
+	return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("CHECK"), realExec)
+}
 
-	pluginPath, err := FindInPath(delegatePlugin, paths)
+// DelegateDel calls the given delegate plugin with the CNI DEL action and
+// JSON configuration
+func DelegateDel(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
+	pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
 	if err != nil {
 		return err
 	}
 
-	return ExecPluginWithoutResult(pluginPath, netconf, ArgsFromEnv())
+	// DelegateDel will override the original CNI_COMMAND env from process with DEL
+	return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("DEL"), realExec)
+}
+
+// return CNIArgs used by delegation
+func delegateArgs(action string) *DelegateArgs {
+	return &DelegateArgs{
+		Command: action,
+	}
 }

+ 77 - 28
vendor/github.com/containernetworking/cni/pkg/invoke/exec.go

@@ -15,6 +15,7 @@
 package invoke
 
 import (
+	"context"
 	"fmt"
 	"os"
 
@@ -22,34 +23,62 @@ import (
 	"github.com/containernetworking/cni/pkg/version"
 )
 
-func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (types.Result, error) {
-	return defaultPluginExec.WithResult(pluginPath, netconf, args)
+// Exec is an interface encapsulates all operations that deal with finding
+// and executing a CNI plugin. Tests may provide a fake implementation
+// to avoid writing fake plugins to temporary directories during the test.
+type Exec interface {
+	ExecPlugin(ctx context.Context, pluginPath string, stdinData []byte, environ []string) ([]byte, error)
+	FindInPath(plugin string, paths []string) (string, error)
+	Decode(jsonBytes []byte) (version.PluginInfo, error)
 }
 
-func ExecPluginWithoutResult(pluginPath string, netconf []byte, args CNIArgs) error {
-	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{},
-}
+// For example, a testcase could pass an instance of the following fakeExec
+// object to ExecPluginWithResult() to verify the incoming stdin and environment
+// and provide a tailored response:
+//
+//import (
+//	"encoding/json"
+//	"path"
+//	"strings"
+//)
+//
+//type fakeExec struct {
+//	version.PluginDecoder
+//}
+//
+//func (f *fakeExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
+//	net := &types.NetConf{}
+//	err := json.Unmarshal(stdinData, net)
+//	if err != nil {
+//		return nil, fmt.Errorf("failed to unmarshal configuration: %v", err)
+//	}
+//	pluginName := path.Base(pluginPath)
+//	if pluginName != net.Type {
+//		return nil, fmt.Errorf("plugin name %q did not match config type %q", pluginName, net.Type)
+//	}
+//	for _, e := range environ {
+//		// Check environment for forced failure request
+//		parts := strings.Split(e, "=")
+//		if len(parts) > 0 && parts[0] == "FAIL" {
+//			return nil, fmt.Errorf("failed to execute plugin %s", pluginName)
+//		}
+//	}
+//	return []byte("{\"CNIVersion\":\"0.4.0\"}"), nil
+//}
+//
+//func (f *fakeExec) FindInPath(plugin string, paths []string) (string, error) {
+//	if len(paths) > 0 {
+//		return path.Join(paths[0], plugin), nil
+//	}
+//	return "", fmt.Errorf("failed to find plugin %s in paths %v", plugin, paths)
+//}
 
-type PluginExec struct {
-	RawExec interface {
-		ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error)
+func ExecPluginWithResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) (types.Result, error) {
+	if exec == nil {
+		exec = defaultExec
 	}
-	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())
+	stdoutBytes, err := exec.ExecPlugin(ctx, pluginPath, netconf, args.AsEnv())
 	if err != nil {
 		return nil, err
 	}
@@ -64,8 +93,11 @@ func (e *PluginExec) WithResult(pluginPath string, netconf []byte, args CNIArgs)
 	return version.NewResult(confVersion, stdoutBytes)
 }
 
-func (e *PluginExec) WithoutResult(pluginPath string, netconf []byte, args CNIArgs) error {
-	_, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv())
+func ExecPluginWithoutResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) error {
+	if exec == nil {
+		exec = defaultExec
+	}
+	_, err := exec.ExecPlugin(ctx, pluginPath, netconf, args.AsEnv())
 	return err
 }
 
@@ -73,7 +105,10 @@ func (e *PluginExec) WithoutResult(pluginPath string, netconf []byte, args CNIAr
 // 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) {
+func GetVersionInfo(ctx context.Context, pluginPath string, exec Exec) (version.PluginInfo, error) {
+	if exec == nil {
+		exec = defaultExec
+	}
 	args := &Args{
 		Command: "VERSION",
 
@@ -83,7 +118,7 @@ func (e *PluginExec) GetVersionInfo(pluginPath string) (version.PluginInfo, erro
 		Path:   "dummy",
 	}
 	stdin := []byte(fmt.Sprintf(`{"cniVersion":%q}`, version.Current()))
-	stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, stdin, args.AsEnv())
+	stdoutBytes, err := exec.ExecPlugin(ctx, pluginPath, stdin, args.AsEnv())
 	if err != nil {
 		if err.Error() == "unknown CNI_COMMAND: VERSION" {
 			return version.PluginSupports("0.1.0"), nil
@@ -91,5 +126,19 @@ func (e *PluginExec) GetVersionInfo(pluginPath string) (version.PluginInfo, erro
 		return nil, err
 	}
 
-	return e.VersionDecoder.Decode(stdoutBytes)
+	return exec.Decode(stdoutBytes)
+}
+
+// DefaultExec is an object that implements the Exec interface which looks
+// for and executes plugins from disk.
+type DefaultExec struct {
+	*RawExec
+	version.PluginDecoder
+}
+
+// DefaultExec implements the Exec interface
+var _ Exec = &DefaultExec{}
+
+var defaultExec = &DefaultExec{
+	RawExec: &RawExec{Stderr: os.Stderr},
 }

+ 5 - 0
vendor/github.com/containernetworking/cni/pkg/invoke/find.go

@@ -18,6 +18,7 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"strings"
 )
 
 // FindInPath returns the full path of the plugin by searching in the provided path
@@ -26,6 +27,10 @@ func FindInPath(plugin string, paths []string) (string, error) {
 		return "", fmt.Errorf("no plugin name provided")
 	}
 
+	if strings.ContainsRune(plugin, os.PathSeparator) {
+		return "", fmt.Errorf("invalid plugin name: %s", plugin)
+	}
+
 	if len(paths) == 0 {
 		return "", fmt.Errorf("no paths provided")
 	}

+ 1 - 1
vendor/github.com/containernetworking/cni/pkg/invoke/os_unix.go

@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build darwin dragonfly freebsd linux netbsd opensbd solaris
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris
 
 package invoke
 

+ 47 - 18
vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go

@@ -16,10 +16,13 @@ package invoke
 
 import (
 	"bytes"
+	"context"
 	"encoding/json"
 	"fmt"
 	"io"
 	"os/exec"
+	"strings"
+	"time"
 
 	"github.com/containernetworking/cni/pkg/types"
 )
@@ -28,32 +31,58 @@ type RawExec struct {
 	Stderr io.Writer
 }
 
-func (e *RawExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
+func (e *RawExec) ExecPlugin(ctx context.Context, pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
 	stdout := &bytes.Buffer{}
+	stderr := &bytes.Buffer{}
+	c := exec.CommandContext(ctx, pluginPath)
+	c.Env = environ
+	c.Stdin = bytes.NewBuffer(stdinData)
+	c.Stdout = stdout
+	c.Stderr = stderr
 
-	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())
+	// Retry the command on "text file busy" errors
+	for i := 0; i <= 5; i++ {
+		err := c.Run()
+
+		// Command succeeded
+		if err == nil {
+			break
+		}
+
+		// If the plugin is currently about to be written, then we wait a
+		// second and try it again
+		if strings.Contains(err.Error(), "text file busy") {
+			time.Sleep(time.Second)
+			continue
+		}
+
+		// All other errors except than the busy text file
+		return nil, e.pluginErr(err, stdout.Bytes(), stderr.Bytes())
 	}
 
+	// Copy stderr to caller's buffer in case plugin printed to both
+	// stdout and stderr for some reason. Ignore failures as stderr is
+	// only informational.
+	if e.Stderr != nil && stderr.Len() > 0 {
+		_, _ = stderr.WriteTo(e.Stderr)
+	}
 	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 {
-			emsg.Msg = fmt.Sprintf("netplugin failed but error parsing its diagnostic message %q: %v", string(output), perr)
+func (e *RawExec) pluginErr(err error, stdout, stderr []byte) error {
+	emsg := types.Error{}
+	if len(stdout) == 0 {
+		if len(stderr) == 0 {
+			emsg.Msg = fmt.Sprintf("netplugin failed with no error message: %v", err)
+		} else {
+			emsg.Msg = fmt.Sprintf("netplugin failed: %q", string(stderr))
 		}
-		return &emsg
+	} else if perr := json.Unmarshal(stdout, &emsg); perr != nil {
+		emsg.Msg = fmt.Sprintf("netplugin failed but error parsing its diagnostic message %q: %v", string(stdout), perr)
 	}
+	return &emsg
+}
 
-	return err
+func (e *RawExec) FindInPath(plugin string, paths []string) (string, error) {
+	return FindInPath(plugin, paths)
 }

+ 6 - 15
vendor/github.com/containernetworking/cni/pkg/types/020/types.go

@@ -17,6 +17,7 @@ package types020
 import (
 	"encoding/json"
 	"fmt"
+	"io"
 	"net"
 	"os"
 
@@ -73,28 +74,18 @@ func (r *Result) GetAsVersion(version string) (types.Result, error) {
 }
 
 func (r *Result) Print() error {
+	return r.PrintTo(os.Stdout)
+}
+
+func (r *Result) PrintTo(writer io.Writer) error {
 	data, err := json.MarshalIndent(r, "", "    ")
 	if err != nil {
 		return err
 	}
-	_, err = os.Stdout.Write(data)
+	_, err = writer.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

+ 1 - 1
vendor/github.com/containernetworking/cni/pkg/types/args.go

@@ -36,7 +36,7 @@ func (b *UnmarshallableBool) UnmarshalText(data []byte) error {
 	case "0", "false":
 		*b = false
 	default:
-		return fmt.Errorf("Boolean unmarshal error: invalid input %s", s)
+		return fmt.Errorf("boolean unmarshal error: invalid input %s", s)
 	}
 	return nil
 }

+ 11 - 35
vendor/github.com/containernetworking/cni/pkg/types/current/types.go

@@ -17,6 +17,7 @@ package current
 import (
 	"encoding/json"
 	"fmt"
+	"io"
 	"net"
 	"os"
 
@@ -24,9 +25,9 @@ import (
 	"github.com/containernetworking/cni/pkg/types/020"
 )
 
-const ImplementedSpecVersion string = "0.3.1"
+const ImplementedSpecVersion string = "0.4.0"
 
-var SupportedVersions = []string{"0.3.0", ImplementedSpecVersion}
+var SupportedVersions = []string{"0.3.0", "0.3.1", ImplementedSpecVersion}
 
 func NewResult(data []byte) (types.Result, error) {
 	result := &Result{}
@@ -75,13 +76,9 @@ func convertFrom020(result types.Result) (*Result, error) {
 			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,
+				GW:  route.GW,
 			})
 		}
 	}
@@ -93,21 +90,13 @@ func convertFrom020(result types.Result) (*Result, error) {
 			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,
+				GW:  route.GW,
 			})
 		}
 	}
 
-	if len(newResult.IPs) == 0 {
-		return nil, fmt.Errorf("cannot convert: no valid IP addresses")
-	}
-
 	return newResult, nil
 }
 
@@ -196,7 +185,7 @@ func (r *Result) Version() string {
 
 func (r *Result) GetAsVersion(version string) (types.Result, error) {
 	switch version {
-	case "0.3.0", ImplementedSpecVersion:
+	case "0.3.0", "0.3.1", ImplementedSpecVersion:
 		r.CNIVersion = version
 		return r, nil
 	case types020.SupportedVersions[0], types020.SupportedVersions[1], types020.SupportedVersions[2]:
@@ -206,31 +195,18 @@ func (r *Result) GetAsVersion(version string) (types.Result, error) {
 }
 
 func (r *Result) Print() error {
+	return r.PrintTo(os.Stdout)
+}
+
+func (r *Result) PrintTo(writer io.Writer) error {
 	data, err := json.MarshalIndent(r, "", "    ")
 	if err != nil {
 		return err
 	}
-	_, err = os.Stdout.Write(data)
+	_, err = writer.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

+ 35 - 17
vendor/github.com/containernetworking/cni/pkg/types/types.go

@@ -16,8 +16,8 @@ package types
 
 import (
 	"encoding/json"
-	"errors"
 	"fmt"
+	"io"
 	"net"
 	"os"
 )
@@ -63,25 +63,31 @@ type NetConf struct {
 	Name         string          `json:"name,omitempty"`
 	Type         string          `json:"type,omitempty"`
 	Capabilities map[string]bool `json:"capabilities,omitempty"`
-	IPAM         struct {
-		Type string `json:"type,omitempty"`
-	} `json:"ipam,omitempty"`
-	DNS DNS `json:"dns"`
+	IPAM         IPAM            `json:"ipam,omitempty"`
+	DNS          DNS             `json:"dns"`
+
+	RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
+	PrevResult    Result                 `json:"-"`
+}
+
+type IPAM struct {
+	Type string `json:"type,omitempty"`
 }
 
 // NetConfList describes an ordered list of networks.
 type NetConfList struct {
 	CNIVersion string `json:"cniVersion,omitempty"`
 
-	Name    string     `json:"name,omitempty"`
-	Plugins []*NetConf `json:"plugins,omitempty"`
+	Name         string     `json:"name,omitempty"`
+	DisableCheck bool       `json:"disableCheck,omitempty"`
+	Plugins      []*NetConf `json:"plugins,omitempty"`
 }
 
 type ResultFactoryFunc func([]byte) (Result, error)
 
 // Result is an interface that provides the result of plugin execution
 type Result interface {
-	// The highest CNI specification result verison the result supports
+	// The highest CNI specification result version the result supports
 	// without having to convert
 	Version() string
 
@@ -92,8 +98,8 @@ type Result interface {
 	// Prints the result in JSON format to stdout
 	Print() error
 
-	// Returns a JSON string representation of the result
-	String() string
+	// Prints the result in JSON format to provided writer
+	PrintTo(writer io.Writer) error
 }
 
 func PrintResult(result Result, version string) error {
@@ -124,9 +130,16 @@ func (r *Route) String() string {
 // Well known error codes
 // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
 const (
-	ErrUnknown                uint = iota // 0
-	ErrIncompatibleCNIVersion             // 1
-	ErrUnsupportedField                   // 2
+	ErrUnknown                     uint = iota // 0
+	ErrIncompatibleCNIVersion                  // 1
+	ErrUnsupportedField                        // 2
+	ErrUnknownContainer                        // 3
+	ErrInvalidEnvironmentVariables             // 4
+	ErrIOFailure                               // 5
+	ErrDecodingFailure                         // 6
+	ErrInvalidNetworkConfig                    // 7
+	ErrTryAgainLater               uint = 11
+	ErrInternal                    uint = 999
 )
 
 type Error struct {
@@ -135,6 +148,14 @@ type Error struct {
 	Details string `json:"details,omitempty"`
 }
 
+func NewError(code uint, msg, details string) *Error {
+	return &Error{
+		Code:    code,
+		Msg:     msg,
+		Details: details,
+	}
+}
+
 func (e *Error) Error() string {
 	details := ""
 	if e.Details != "" {
@@ -167,7 +188,7 @@ func (r *Route) UnmarshalJSON(data []byte) error {
 	return nil
 }
 
-func (r *Route) MarshalJSON() ([]byte, error) {
+func (r Route) MarshalJSON() ([]byte, error) {
 	rt := route{
 		Dst: IPNet(r.Dst),
 		GW:  r.GW,
@@ -184,6 +205,3 @@ func prettyPrint(obj interface{}) error {
 	_, err = os.Stdout.Write(data)
 	return err
 }
-
-// NotImplementedError is used to indicate that a method is not implemented for the given platform
-var NotImplementedError = errors.New("Not Implemented")

+ 84 - 0
vendor/github.com/containernetworking/cni/pkg/utils/utils.go

@@ -0,0 +1,84 @@
+// Copyright 2019 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 utils
+
+import (
+	"bytes"
+	"fmt"
+	"regexp"
+	"unicode"
+
+	"github.com/containernetworking/cni/pkg/types"
+)
+
+const (
+	// cniValidNameChars is the regexp used to validate valid characters in
+	// containerID and networkName
+	cniValidNameChars = `[a-zA-Z0-9][a-zA-Z0-9_.\-]`
+
+	// maxInterfaceNameLength is the length max of a valid interface name
+	maxInterfaceNameLength = 15
+)
+
+var cniReg = regexp.MustCompile(`^` + cniValidNameChars + `*$`)
+
+// ValidateContainerID will validate that the supplied containerID is not empty does not contain invalid characters
+func ValidateContainerID(containerID string) *types.Error {
+
+	if containerID == "" {
+		return types.NewError(types.ErrUnknownContainer, "missing containerID", "")
+	}
+	if !cniReg.MatchString(containerID) {
+		return types.NewError(types.ErrInvalidEnvironmentVariables, "invalid characters in containerID", containerID)
+	}
+	return nil
+}
+
+// ValidateNetworkName will validate that the supplied networkName does not contain invalid characters
+func ValidateNetworkName(networkName string) *types.Error {
+
+	if networkName == "" {
+		return types.NewError(types.ErrInvalidNetworkConfig, "missing network name:", "")
+	}
+	if !cniReg.MatchString(networkName) {
+		return types.NewError(types.ErrInvalidNetworkConfig, "invalid characters found in network name", networkName)
+	}
+	return nil
+}
+
+// ValidateInterfaceName will validate the interface name based on the three rules below
+// 1. The name must not be empty
+// 2. The name must be less than 16 characters
+// 3. The name must not be "." or ".."
+// 3. The name must not contain / or : or any whitespace characters
+// ref to https://github.com/torvalds/linux/blob/master/net/core/dev.c#L1024
+func ValidateInterfaceName(ifName string) *types.Error {
+	if len(ifName) == 0 {
+		return types.NewError(types.ErrInvalidEnvironmentVariables, "interface name is empty", "")
+	}
+	if len(ifName) > maxInterfaceNameLength {
+		return types.NewError(types.ErrInvalidEnvironmentVariables, "interface name is too long", fmt.Sprintf("interface name should be less than %d characters", maxInterfaceNameLength+1))
+	}
+	if ifName == "." || ifName == ".." {
+		return types.NewError(types.ErrInvalidEnvironmentVariables, "interface name is . or ..", "")
+	}
+	for _, r := range bytes.Runes([]byte(ifName)) {
+		if r == '/' || r == ':' || unicode.IsSpace(r) {
+			return types.NewError(types.ErrInvalidEnvironmentVariables, "interface name contains / or : or whitespace characters", "")
+		}
+	}
+
+	return nil
+}

+ 63 - 0
vendor/github.com/containernetworking/cni/pkg/version/plugin.go

@@ -18,6 +18,8 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
+	"strconv"
+	"strings"
 )
 
 // PluginInfo reports information about CNI versioning
@@ -79,3 +81,64 @@ func (*PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) {
 	}
 	return &info, nil
 }
+
+// ParseVersion parses a version string like "3.0.1" or "0.4.5" into major,
+// minor, and micro numbers or returns an error
+func ParseVersion(version string) (int, int, int, error) {
+	var major, minor, micro int
+	if version == "" {
+		return -1, -1, -1, fmt.Errorf("invalid version %q: the version is empty", version)
+	}
+
+	parts := strings.Split(version, ".")
+	if len(parts) >= 4 {
+		return -1, -1, -1, fmt.Errorf("invalid version %q: too many parts", version)
+	}
+
+	major, err := strconv.Atoi(parts[0])
+	if err != nil {
+		return -1, -1, -1, fmt.Errorf("failed to convert major version part %q: %v", parts[0], err)
+	}
+
+	if len(parts) >= 2 {
+		minor, err = strconv.Atoi(parts[1])
+		if err != nil {
+			return -1, -1, -1, fmt.Errorf("failed to convert minor version part %q: %v", parts[1], err)
+		}
+	}
+
+	if len(parts) >= 3 {
+		micro, err = strconv.Atoi(parts[2])
+		if err != nil {
+			return -1, -1, -1, fmt.Errorf("failed to convert micro version part %q: %v", parts[2], err)
+		}
+	}
+
+	return major, minor, micro, nil
+}
+
+// GreaterThanOrEqualTo takes two string versions, parses them into major/minor/micro
+// numbers, and compares them to determine whether the first version is greater
+// than or equal to the second
+func GreaterThanOrEqualTo(version, otherVersion string) (bool, error) {
+	firstMajor, firstMinor, firstMicro, err := ParseVersion(version)
+	if err != nil {
+		return false, err
+	}
+
+	secondMajor, secondMinor, secondMicro, err := ParseVersion(otherVersion)
+	if err != nil {
+		return false, err
+	}
+
+	if firstMajor > secondMajor {
+		return true, nil
+	} else if firstMajor == secondMajor {
+		if firstMinor > secondMinor {
+			return true, nil
+		} else if firstMinor == secondMinor && firstMicro >= secondMicro {
+			return true, nil
+		}
+	}
+	return false, nil
+}

+ 24 - 2
vendor/github.com/containernetworking/cni/pkg/version/version.go

@@ -15,6 +15,7 @@
 package version
 
 import (
+	"encoding/json"
 	"fmt"
 
 	"github.com/containernetworking/cni/pkg/types"
@@ -24,7 +25,7 @@ import (
 
 // Current reports the version of the CNI spec implemented by this library
 func Current() string {
-	return "0.3.1"
+	return "0.4.0"
 }
 
 // Legacy PluginInfo describes a plugin that is backwards compatible with the
@@ -35,7 +36,7 @@ func Current() string {
 // Any future CNI spec versions which meet this definition should be added to
 // this list.
 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 All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0")
 
 var resultFactories = []struct {
 	supportedVersions []string
@@ -59,3 +60,24 @@ func NewResult(version string, resultBytes []byte) (types.Result, error) {
 
 	return nil, fmt.Errorf("unsupported CNI result version %q", version)
 }
+
+// ParsePrevResult parses a prevResult in a NetConf structure and sets
+// the NetConf's PrevResult member to the parsed Result object.
+func ParsePrevResult(conf *types.NetConf) error {
+	if conf.RawPrevResult == nil {
+		return nil
+	}
+
+	resultBytes, err := json.Marshal(conf.RawPrevResult)
+	if err != nil {
+		return fmt.Errorf("could not serialize prevResult: %v", err)
+	}
+
+	conf.RawPrevResult = nil
+	conf.PrevResult, err = NewResult(conf.CNIVersion, resultBytes)
+	if err != nil {
+		return fmt.Errorf("could not parse prevResult: %v", err)
+	}
+
+	return nil
+}

+ 2 - 1
vendor/modules.txt

@@ -16,13 +16,14 @@ github.com/campoy/embedmd/embedmd
 # github.com/cespare/xxhash/v2 v2.1.1
 ## explicit; go 1.11
 github.com/cespare/xxhash/v2
-# github.com/containernetworking/cni v0.6.0
+# github.com/containernetworking/cni v0.8.1
 ## explicit
 github.com/containernetworking/cni/libcni
 github.com/containernetworking/cni/pkg/invoke
 github.com/containernetworking/cni/pkg/types
 github.com/containernetworking/cni/pkg/types/020
 github.com/containernetworking/cni/pkg/types/current
+github.com/containernetworking/cni/pkg/utils
 github.com/containernetworking/cni/pkg/version
 # github.com/containernetworking/plugins v0.6.0
 ## explicit