github.com/baraj55/containernetworking-cni@v0.7.2-0.20200219164625-56ace59a9e7f/libcni/api.go (about)

     1  // Copyright 2015 CNI authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package libcni
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"io/ioutil"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  
    26  	"github.com/containernetworking/cni/pkg/invoke"
    27  	"github.com/containernetworking/cni/pkg/types"
    28  	"github.com/containernetworking/cni/pkg/utils"
    29  	"github.com/containernetworking/cni/pkg/version"
    30  )
    31  
    32  var (
    33  	CacheDir = "/var/lib/cni"
    34  )
    35  
    36  const (
    37  	CNICacheV1 = "cniCacheV1"
    38  )
    39  
    40  // A RuntimeConf holds the arguments to one invocation of a CNI plugin
    41  // excepting the network configuration, with the nested exception that
    42  // the `runtimeConfig` from the network configuration is included
    43  // here.
    44  type RuntimeConf struct {
    45  	ContainerID string
    46  	NetNS       string
    47  	IfName      string
    48  	Args        [][2]string
    49  	// A dictionary of capability-specific data passed by the runtime
    50  	// to plugins as top-level keys in the 'runtimeConfig' dictionary
    51  	// of the plugin's stdin data.  libcni will ensure that only keys
    52  	// in this map which match the capabilities of the plugin are passed
    53  	// to the plugin
    54  	CapabilityArgs map[string]interface{}
    55  
    56  	// DEPRECATED. Will be removed in a future release.
    57  	CacheDir string
    58  }
    59  
    60  type NetworkConfig struct {
    61  	Network *types.NetConf
    62  	Bytes   []byte
    63  }
    64  
    65  type NetworkConfigList struct {
    66  	Name         string
    67  	CNIVersion   string
    68  	DisableCheck bool
    69  	Plugins      []*NetworkConfig
    70  	Bytes        []byte
    71  }
    72  
    73  type CNI interface {
    74  	AddNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
    75  	CheckNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
    76  	DelNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
    77  	GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
    78  	GetNetworkListCachedConfig(net *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
    79  
    80  	AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
    81  	CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error
    82  	DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error
    83  	GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
    84  	GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
    85  
    86  	ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, error)
    87  	ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error)
    88  }
    89  
    90  type CNIConfig struct {
    91  	Path     []string
    92  	exec     invoke.Exec
    93  	cacheDir string
    94  }
    95  
    96  // CNIConfig implements the CNI interface
    97  var _ CNI = &CNIConfig{}
    98  
    99  // NewCNIConfig returns a new CNIConfig object that will search for plugins
   100  // in the given paths and use the given exec interface to run those plugins,
   101  // or if the exec interface is not given, will use a default exec handler.
   102  func NewCNIConfig(path []string, exec invoke.Exec) *CNIConfig {
   103  	return NewCNIConfigWithCacheDir(path, "", exec)
   104  }
   105  
   106  // NewCNIConfigWithCacheDir returns a new CNIConfig object that will search for plugins
   107  // in the given paths use the given exec interface to run those plugins,
   108  // or if the exec interface is not given, will use a default exec handler.
   109  // The given cache directory will be used for temporary data storage when needed.
   110  func NewCNIConfigWithCacheDir(path []string, cacheDir string, exec invoke.Exec) *CNIConfig {
   111  	return &CNIConfig{
   112  		Path:     path,
   113  		cacheDir: cacheDir,
   114  		exec:     exec,
   115  	}
   116  }
   117  
   118  func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) {
   119  	var err error
   120  
   121  	inject := map[string]interface{}{
   122  		"name":       name,
   123  		"cniVersion": cniVersion,
   124  	}
   125  	// Add previous plugin result
   126  	if prevResult != nil {
   127  		inject["prevResult"] = prevResult
   128  	}
   129  
   130  	// Ensure every config uses the same name and version
   131  	orig, err = InjectConf(orig, inject)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	return injectRuntimeConfig(orig, rt)
   137  }
   138  
   139  // This function takes a libcni RuntimeConf structure and injects values into
   140  // a "runtimeConfig" dictionary in the CNI network configuration JSON that
   141  // will be passed to the plugin on stdin.
   142  //
   143  // Only "capabilities arguments" passed by the runtime are currently injected.
   144  // These capabilities arguments are filtered through the plugin's advertised
   145  // capabilities from its config JSON, and any keys in the CapabilityArgs
   146  // matching plugin capabilities are added to the "runtimeConfig" dictionary
   147  // sent to the plugin via JSON on stdin.  For example, if the plugin's
   148  // capabilities include "portMappings", and the CapabilityArgs map includes a
   149  // "portMappings" key, that key and its value are added to the "runtimeConfig"
   150  // dictionary to be passed to the plugin's stdin.
   151  func injectRuntimeConfig(orig *NetworkConfig, rt *RuntimeConf) (*NetworkConfig, error) {
   152  	var err error
   153  
   154  	rc := make(map[string]interface{})
   155  	for capability, supported := range orig.Network.Capabilities {
   156  		if !supported {
   157  			continue
   158  		}
   159  		if data, ok := rt.CapabilityArgs[capability]; ok {
   160  			rc[capability] = data
   161  		}
   162  	}
   163  
   164  	if len(rc) > 0 {
   165  		orig, err = InjectConf(orig, map[string]interface{}{"runtimeConfig": rc})
   166  		if err != nil {
   167  			return nil, err
   168  		}
   169  	}
   170  
   171  	return orig, nil
   172  }
   173  
   174  // ensure we have a usable exec if the CNIConfig was not given one
   175  func (c *CNIConfig) ensureExec() invoke.Exec {
   176  	if c.exec == nil {
   177  		c.exec = &invoke.DefaultExec{
   178  			RawExec:       &invoke.RawExec{Stderr: os.Stderr},
   179  			PluginDecoder: version.PluginDecoder{},
   180  		}
   181  	}
   182  	return c.exec
   183  }
   184  
   185  type cachedInfo struct {
   186  	Kind           string                 `json:"kind"`
   187  	ContainerID    string                 `json:"containerId"`
   188  	Config         []byte                 `json:"config"`
   189  	IfName         string                 `json:"ifName"`
   190  	NetworkName    string                 `json:"networkName"`
   191  	CniArgs        [][2]string            `json:"cniArgs,omitempty"`
   192  	CapabilityArgs map[string]interface{} `json:"capabilityArgs,omitempty"`
   193  	RawResult      map[string]interface{} `json:"result,omitempty"`
   194  	Result         types.Result           `json:"-"`
   195  }
   196  
   197  // getCacheDir returns the cache directory in this order:
   198  // 1) global cacheDir from CNIConfig object
   199  // 2) deprecated cacheDir from RuntimeConf object
   200  // 3) fall back to default cache directory
   201  func (c *CNIConfig) getCacheDir(rt *RuntimeConf) string {
   202  	if c.cacheDir != "" {
   203  		return c.cacheDir
   204  	}
   205  	if rt.CacheDir != "" {
   206  		return rt.CacheDir
   207  	}
   208  	return CacheDir
   209  }
   210  
   211  func (c *CNIConfig) getCacheFilePath(netName string, rt *RuntimeConf) (string, error) {
   212  	if netName == "" || rt.ContainerID == "" || rt.IfName == "" {
   213  		return "", fmt.Errorf("cache file path requires network name (%q), container ID (%q), and interface name (%q)", netName, rt.ContainerID, rt.IfName)
   214  	}
   215  	return filepath.Join(c.getCacheDir(rt), "results", fmt.Sprintf("%s-%s-%s", netName, rt.ContainerID, rt.IfName)), nil
   216  }
   217  
   218  func (c *CNIConfig) cacheAdd(result types.Result, config []byte, netName string, rt *RuntimeConf) error {
   219  	cached := cachedInfo{
   220  		Kind:           CNICacheV1,
   221  		ContainerID:    rt.ContainerID,
   222  		Config:         config,
   223  		IfName:         rt.IfName,
   224  		NetworkName:    netName,
   225  		CniArgs:        rt.Args,
   226  		CapabilityArgs: rt.CapabilityArgs,
   227  	}
   228  
   229  	// We need to get type.Result into cachedInfo as JSON map
   230  	// Marshal to []byte, then Unmarshal into cached.RawResult
   231  	data, err := json.Marshal(result)
   232  	if err != nil {
   233  		return err
   234  	}
   235  
   236  	err = json.Unmarshal(data, &cached.RawResult)
   237  	if err != nil {
   238  		return err
   239  	}
   240  
   241  	newBytes, err := json.Marshal(&cached)
   242  	if err != nil {
   243  		return err
   244  	}
   245  
   246  	fname, err := c.getCacheFilePath(netName, rt)
   247  	if err != nil {
   248  		return err
   249  	}
   250  	if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil {
   251  		return err
   252  	}
   253  
   254  	return ioutil.WriteFile(fname, newBytes, 0600)
   255  }
   256  
   257  func (c *CNIConfig) cacheDel(netName string, rt *RuntimeConf) error {
   258  	fname, err := c.getCacheFilePath(netName, rt)
   259  	if err != nil {
   260  		// Ignore error
   261  		return nil
   262  	}
   263  	return os.Remove(fname)
   264  }
   265  
   266  func (c *CNIConfig) getCachedConfig(netName string, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
   267  	var bytes []byte
   268  
   269  	fname, err := c.getCacheFilePath(netName, rt)
   270  	if err != nil {
   271  		return nil, nil, err
   272  	}
   273  	bytes, err = ioutil.ReadFile(fname)
   274  	if err != nil {
   275  		// Ignore read errors; the cached result may not exist on-disk
   276  		return nil, nil, nil
   277  	}
   278  
   279  	unmarshaled := cachedInfo{}
   280  	if err := json.Unmarshal(bytes, &unmarshaled); err != nil {
   281  		return nil, nil, fmt.Errorf("failed to unmarshal cached network %q config: %v", netName, err)
   282  	}
   283  	if unmarshaled.Kind != CNICacheV1 {
   284  		return nil, nil, fmt.Errorf("read cached network %q config has wrong kind: %v", netName, unmarshaled.Kind)
   285  	}
   286  
   287  	newRt := *rt
   288  	if unmarshaled.CniArgs != nil {
   289  		newRt.Args = unmarshaled.CniArgs
   290  	}
   291  	newRt.CapabilityArgs = unmarshaled.CapabilityArgs
   292  
   293  	return unmarshaled.Config, &newRt, nil
   294  }
   295  
   296  func (c *CNIConfig) getLegacyCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) {
   297  	fname, err := c.getCacheFilePath(netName, rt)
   298  	if err != nil {
   299  		return nil, err
   300  	}
   301  	data, err := ioutil.ReadFile(fname)
   302  	if err != nil {
   303  		// Ignore read errors; the cached result may not exist on-disk
   304  		return nil, nil
   305  	}
   306  
   307  	// Read the version of the cached result
   308  	decoder := version.ConfigDecoder{}
   309  	resultCniVersion, err := decoder.Decode(data)
   310  	if err != nil {
   311  		return nil, err
   312  	}
   313  
   314  	// Ensure we can understand the result
   315  	result, err := version.NewResult(resultCniVersion, data)
   316  	if err != nil {
   317  		return nil, err
   318  	}
   319  
   320  	// Convert to the config version to ensure plugins get prevResult
   321  	// in the same version as the config.  The cached result version
   322  	// should match the config version unless the config was changed
   323  	// while the container was running.
   324  	result, err = result.GetAsVersion(cniVersion)
   325  	if err != nil && resultCniVersion != cniVersion {
   326  		return nil, fmt.Errorf("failed to convert cached result version %q to config version %q: %v", resultCniVersion, cniVersion, err)
   327  	}
   328  	return result, err
   329  }
   330  
   331  func (c *CNIConfig) getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) {
   332  	fname, err := c.getCacheFilePath(netName, rt)
   333  	if err != nil {
   334  		return nil, err
   335  	}
   336  	fdata, err := ioutil.ReadFile(fname)
   337  	if err != nil {
   338  		// Ignore read errors; the cached result may not exist on-disk
   339  		return nil, nil
   340  	}
   341  
   342  	cachedInfo := cachedInfo{}
   343  	if err := json.Unmarshal(fdata, &cachedInfo); err != nil || cachedInfo.Kind != CNICacheV1 {
   344  		return c.getLegacyCachedResult(netName, cniVersion, rt)
   345  	}
   346  
   347  	newBytes, err := json.Marshal(&cachedInfo.RawResult)
   348  	if err != nil {
   349  		return nil, fmt.Errorf("failed to marshal cached network %q config: %v", netName, err)
   350  	}
   351  
   352  	// Read the version of the cached result
   353  	decoder := version.ConfigDecoder{}
   354  	resultCniVersion, err := decoder.Decode(newBytes)
   355  	if err != nil {
   356  		return nil, err
   357  	}
   358  
   359  	// Ensure we can understand the result
   360  	result, err := version.NewResult(resultCniVersion, newBytes)
   361  	if err != nil {
   362  		return nil, err
   363  	}
   364  
   365  	// Convert to the config version to ensure plugins get prevResult
   366  	// in the same version as the config.  The cached result version
   367  	// should match the config version unless the config was changed
   368  	// while the container was running.
   369  	result, err = result.GetAsVersion(cniVersion)
   370  	if err != nil && resultCniVersion != cniVersion {
   371  		return nil, fmt.Errorf("failed to convert cached result version %q to config version %q: %v", resultCniVersion, cniVersion, err)
   372  	}
   373  	return result, err
   374  }
   375  
   376  // GetNetworkListCachedResult returns the cached Result of the previous
   377  // AddNetworkList() operation for a network list, or an error.
   378  func (c *CNIConfig) GetNetworkListCachedResult(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
   379  	return c.getCachedResult(list.Name, list.CNIVersion, rt)
   380  }
   381  
   382  // GetNetworkCachedResult returns the cached Result of the previous
   383  // AddNetwork() operation for a network, or an error.
   384  func (c *CNIConfig) GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
   385  	return c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
   386  }
   387  
   388  // GetNetworkListCachedConfig copies the input RuntimeConf to output
   389  // RuntimeConf with fields updated with info from the cached Config.
   390  func (c *CNIConfig) GetNetworkListCachedConfig(list *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
   391  	return c.getCachedConfig(list.Name, rt)
   392  }
   393  
   394  // GetNetworkCachedConfig copies the input RuntimeConf to output
   395  // RuntimeConf with fields updated with info from the cached Config.
   396  func (c *CNIConfig) GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
   397  	return c.getCachedConfig(net.Network.Name, rt)
   398  }
   399  
   400  func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {
   401  	c.ensureExec()
   402  	pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
   403  	if err != nil {
   404  		return nil, err
   405  	}
   406  	if err := utils.ValidateContainerID(rt.ContainerID); err != nil {
   407  		return nil, err
   408  	}
   409  	if err := utils.ValidateNetworkName(name); err != nil {
   410  		return nil, err
   411  	}
   412  	if err := utils.ValidateInterfaceName(rt.IfName); err != nil {
   413  		return nil, err
   414  	}
   415  
   416  	newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
   417  	if err != nil {
   418  		return nil, err
   419  	}
   420  
   421  	return invoke.ExecPluginWithResult(ctx, pluginPath, newConf.Bytes, c.args("ADD", rt), c.exec)
   422  }
   423  
   424  // AddNetworkList executes a sequence of plugins with the ADD command
   425  func (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
   426  	var err error
   427  	var result types.Result
   428  	for _, net := range list.Plugins {
   429  		result, err = c.addNetwork(ctx, list.Name, list.CNIVersion, net, result, rt)
   430  		if err != nil {
   431  			return nil, err
   432  		}
   433  	}
   434  
   435  	if err = c.cacheAdd(result, list.Bytes, list.Name, rt); err != nil {
   436  		return nil, fmt.Errorf("failed to set network %q cached result: %v", list.Name, err)
   437  	}
   438  
   439  	return result, nil
   440  }
   441  
   442  func (c *CNIConfig) checkNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error {
   443  	c.ensureExec()
   444  	pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
   445  	if err != nil {
   446  		return err
   447  	}
   448  
   449  	newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
   450  	if err != nil {
   451  		return err
   452  	}
   453  
   454  	return invoke.ExecPluginWithoutResult(ctx, pluginPath, newConf.Bytes, c.args("CHECK", rt), c.exec)
   455  }
   456  
   457  // CheckNetworkList executes a sequence of plugins with the CHECK command
   458  func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) error {
   459  	// CHECK was added in CNI spec version 0.4.0 and higher
   460  	if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
   461  		return err
   462  	} else if !gtet {
   463  		return fmt.Errorf("configuration version %q does not support the CHECK command", list.CNIVersion)
   464  	}
   465  
   466  	if list.DisableCheck {
   467  		return nil
   468  	}
   469  
   470  	cachedResult, err := c.getCachedResult(list.Name, list.CNIVersion, rt)
   471  	if err != nil {
   472  		return fmt.Errorf("failed to get network %q cached result: %v", list.Name, err)
   473  	}
   474  
   475  	for _, net := range list.Plugins {
   476  		if err := c.checkNetwork(ctx, list.Name, list.CNIVersion, net, cachedResult, rt); err != nil {
   477  			return err
   478  		}
   479  	}
   480  
   481  	return nil
   482  }
   483  
   484  func (c *CNIConfig) delNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error {
   485  	c.ensureExec()
   486  	pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
   487  	if err != nil {
   488  		return err
   489  	}
   490  
   491  	newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
   492  	if err != nil {
   493  		return err
   494  	}
   495  
   496  	return invoke.ExecPluginWithoutResult(ctx, pluginPath, newConf.Bytes, c.args("DEL", rt), c.exec)
   497  }
   498  
   499  // DelNetworkList executes a sequence of plugins with the DEL command
   500  func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) error {
   501  	var cachedResult types.Result
   502  
   503  	// Cached result on DEL was added in CNI spec version 0.4.0 and higher
   504  	if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
   505  		return err
   506  	} else if gtet {
   507  		cachedResult, err = c.getCachedResult(list.Name, list.CNIVersion, rt)
   508  		if err != nil {
   509  			return fmt.Errorf("failed to get network %q cached result: %v", list.Name, err)
   510  		}
   511  	}
   512  
   513  	for i := len(list.Plugins) - 1; i >= 0; i-- {
   514  		net := list.Plugins[i]
   515  		if err := c.delNetwork(ctx, list.Name, list.CNIVersion, net, cachedResult, rt); err != nil {
   516  			return err
   517  		}
   518  	}
   519  	_ = c.cacheDel(list.Name, rt)
   520  
   521  	return nil
   522  }
   523  
   524  // AddNetwork executes the plugin with the ADD command
   525  func (c *CNIConfig) AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
   526  	result, err := c.addNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, nil, rt)
   527  	if err != nil {
   528  		return nil, err
   529  	}
   530  
   531  	if err = c.cacheAdd(result, net.Bytes, net.Network.Name, rt); err != nil {
   532  		return nil, fmt.Errorf("failed to set network %q cached result: %v", net.Network.Name, err)
   533  	}
   534  
   535  	return result, nil
   536  }
   537  
   538  // CheckNetwork executes the plugin with the CHECK command
   539  func (c *CNIConfig) CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error {
   540  	// CHECK was added in CNI spec version 0.4.0 and higher
   541  	if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil {
   542  		return err
   543  	} else if !gtet {
   544  		return fmt.Errorf("configuration version %q does not support the CHECK command", net.Network.CNIVersion)
   545  	}
   546  
   547  	cachedResult, err := c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
   548  	if err != nil {
   549  		return fmt.Errorf("failed to get network %q cached result: %v", net.Network.Name, err)
   550  	}
   551  	return c.checkNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt)
   552  }
   553  
   554  // DelNetwork executes the plugin with the DEL command
   555  func (c *CNIConfig) DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error {
   556  	var cachedResult types.Result
   557  
   558  	// Cached result on DEL was added in CNI spec version 0.4.0 and higher
   559  	if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil {
   560  		return err
   561  	} else if gtet {
   562  		cachedResult, err = c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
   563  		if err != nil {
   564  			return fmt.Errorf("failed to get network %q cached result: %v", net.Network.Name, err)
   565  		}
   566  	}
   567  
   568  	if err := c.delNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt); err != nil {
   569  		return err
   570  	}
   571  	_ = c.cacheDel(net.Network.Name, rt)
   572  	return nil
   573  }
   574  
   575  // ValidateNetworkList checks that a configuration is reasonably valid.
   576  // - all the specified plugins exist on disk
   577  // - every plugin supports the desired version.
   578  //
   579  // Returns a list of all capabilities supported by the configuration, or error
   580  func (c *CNIConfig) ValidateNetworkList(ctx context.Context, list *NetworkConfigList) ([]string, error) {
   581  	version := list.CNIVersion
   582  
   583  	// holding map for seen caps (in case of duplicates)
   584  	caps := map[string]interface{}{}
   585  
   586  	errs := []error{}
   587  	for _, net := range list.Plugins {
   588  		if err := c.validatePlugin(ctx, net.Network.Type, version); err != nil {
   589  			errs = append(errs, err)
   590  		}
   591  		for c, enabled := range net.Network.Capabilities {
   592  			if !enabled {
   593  				continue
   594  			}
   595  			caps[c] = struct{}{}
   596  		}
   597  	}
   598  
   599  	if len(errs) > 0 {
   600  		return nil, fmt.Errorf("%v", errs)
   601  	}
   602  
   603  	// make caps list
   604  	cc := make([]string, 0, len(caps))
   605  	for c := range caps {
   606  		cc = append(cc, c)
   607  	}
   608  
   609  	return cc, nil
   610  }
   611  
   612  // ValidateNetwork checks that a configuration is reasonably valid.
   613  // It uses the same logic as ValidateNetworkList)
   614  // Returns a list of capabilities
   615  func (c *CNIConfig) ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error) {
   616  	caps := []string{}
   617  	for c, ok := range net.Network.Capabilities {
   618  		if ok {
   619  			caps = append(caps, c)
   620  		}
   621  	}
   622  	if err := c.validatePlugin(ctx, net.Network.Type, net.Network.CNIVersion); err != nil {
   623  		return nil, err
   624  	}
   625  	return caps, nil
   626  }
   627  
   628  // validatePlugin checks that an individual plugin's configuration is sane
   629  func (c *CNIConfig) validatePlugin(ctx context.Context, pluginName, expectedVersion string) error {
   630  	c.ensureExec()
   631  	pluginPath, err := c.exec.FindInPath(pluginName, c.Path)
   632  	if err != nil {
   633  		return err
   634  	}
   635  	if expectedVersion == "" {
   636  		expectedVersion = "0.1.0"
   637  	}
   638  
   639  	vi, err := invoke.GetVersionInfo(ctx, pluginPath, c.exec)
   640  	if err != nil {
   641  		return err
   642  	}
   643  	for _, vers := range vi.SupportedVersions() {
   644  		if vers == expectedVersion {
   645  			return nil
   646  		}
   647  	}
   648  	return fmt.Errorf("plugin %s does not support config version %q", pluginName, expectedVersion)
   649  }
   650  
   651  // GetVersionInfo reports which versions of the CNI spec are supported by
   652  // the given plugin.
   653  func (c *CNIConfig) GetVersionInfo(ctx context.Context, pluginType string) (version.PluginInfo, error) {
   654  	c.ensureExec()
   655  	pluginPath, err := c.exec.FindInPath(pluginType, c.Path)
   656  	if err != nil {
   657  		return nil, err
   658  	}
   659  
   660  	return invoke.GetVersionInfo(ctx, pluginPath, c.exec)
   661  }
   662  
   663  // =====
   664  func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args {
   665  	return &invoke.Args{
   666  		Command:     action,
   667  		ContainerID: rt.ContainerID,
   668  		NetNS:       rt.NetNS,
   669  		PluginArgs:  rt.Args,
   670  		IfName:      rt.IfName,
   671  		Path:        strings.Join(c.Path, string(os.PathListSeparator)),
   672  	}
   673  }