Update containernetworking/cni to v1.2.3 for GC

Signed-off-by: Dan Winship <danwinship@redhat.com>
This commit is contained in:
Dan Winship
2024-08-28 10:09:18 -04:00
parent deb8ef63f4
commit 06ba001d84
41 changed files with 977 additions and 193 deletions

View File

@ -15,7 +15,7 @@
package libcni
// Note this is the actual implementation of the CNI specification, which
// is reflected in the https://github.com/containernetworking/cni/blob/master/SPEC.md file
// is reflected in the SPEC.md file.
// it is typically bundled into runtime providers (i.e. containerd or cri-o would use this
// before calling runc or hcsshim). It is also bundled into CNI providers as well, for example,
// to add an IP to a container, to parse the configuration of the CNI and so on.
@ -23,10 +23,11 @@ package libcni
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"github.com/containernetworking/cni/pkg/invoke"
@ -38,6 +39,8 @@ import (
var (
CacheDir = "/var/lib/cni"
// slightly awkward wording to preserve anyone matching on error strings
ErrorCheckNotSupp = fmt.Errorf("does not support the CHECK command")
)
const (
@ -73,10 +76,25 @@ type NetworkConfigList struct {
Name string
CNIVersion string
DisableCheck bool
DisableGC bool
Plugins []*NetworkConfig
Bytes []byte
}
type NetworkAttachment struct {
ContainerID string
Network string
IfName string
Config []byte
NetNS string
CniArgs [][2]string
CapabilityArgs map[string]interface{}
}
type GCArgs struct {
ValidAttachments []types.GCAttachment
}
type CNI interface {
AddNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
CheckNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
@ -92,6 +110,13 @@ type CNI interface {
ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, error)
ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error)
GCNetworkList(ctx context.Context, net *NetworkConfigList, args *GCArgs) error
GetStatusNetworkList(ctx context.Context, net *NetworkConfigList) error
GetCachedAttachments(containerID string) ([]*NetworkAttachment, error)
GetVersionInfo(ctx context.Context, pluginType string) (version.PluginInfo, error)
}
type CNIConfig struct {
@ -139,8 +164,11 @@ func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult typ
if err != nil {
return nil, err
}
if rt != nil {
return injectRuntimeConfig(orig, rt)
}
return injectRuntimeConfig(orig, rt)
return orig, nil
}
// This function takes a libcni RuntimeConf structure and injects values into
@ -195,6 +223,7 @@ type cachedInfo struct {
Config []byte `json:"config"`
IfName string `json:"ifName"`
NetworkName string `json:"networkName"`
NetNS string `json:"netns,omitempty"`
CniArgs [][2]string `json:"cniArgs,omitempty"`
CapabilityArgs map[string]interface{} `json:"capabilityArgs,omitempty"`
RawResult map[string]interface{} `json:"result,omitempty"`
@ -229,6 +258,7 @@ func (c *CNIConfig) cacheAdd(result types.Result, config []byte, netName string,
Config: config,
IfName: rt.IfName,
NetworkName: netName,
NetNS: rt.NetNS,
CniArgs: rt.Args,
CapabilityArgs: rt.CapabilityArgs,
}
@ -254,11 +284,11 @@ func (c *CNIConfig) cacheAdd(result types.Result, config []byte, netName string,
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil {
if err := os.MkdirAll(filepath.Dir(fname), 0o700); err != nil {
return err
}
return ioutil.WriteFile(fname, newBytes, 0600)
return os.WriteFile(fname, newBytes, 0o600)
}
func (c *CNIConfig) cacheDel(netName string, rt *RuntimeConf) error {
@ -277,7 +307,7 @@ func (c *CNIConfig) getCachedConfig(netName string, rt *RuntimeConf) ([]byte, *R
if err != nil {
return nil, nil, err
}
bytes, err = ioutil.ReadFile(fname)
bytes, err = os.ReadFile(fname)
if err != nil {
// Ignore read errors; the cached result may not exist on-disk
return nil, nil, nil
@ -305,7 +335,7 @@ func (c *CNIConfig) getLegacyCachedResult(netName, cniVersion string, rt *Runtim
if err != nil {
return nil, err
}
data, err := ioutil.ReadFile(fname)
data, err := os.ReadFile(fname)
if err != nil {
// Ignore read errors; the cached result may not exist on-disk
return nil, nil
@ -333,7 +363,7 @@ func (c *CNIConfig) getCachedResult(netName, cniVersion string, rt *RuntimeConf)
if err != nil {
return nil, err
}
fdata, err := ioutil.ReadFile(fname)
fdata, err := os.ReadFile(fname)
if err != nil {
// Ignore read errors; the cached result may not exist on-disk
return nil, nil
@ -390,6 +420,68 @@ func (c *CNIConfig) GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf)
return c.getCachedConfig(net.Network.Name, rt)
}
// GetCachedAttachments returns a list of network attachments from the cache.
// The returned list will be filtered by the containerID if the value is not empty.
func (c *CNIConfig) GetCachedAttachments(containerID string) ([]*NetworkAttachment, error) {
dirPath := filepath.Join(c.getCacheDir(&RuntimeConf{}), "results")
entries, err := os.ReadDir(dirPath)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
fileNames := make([]string, 0, len(entries))
for _, e := range entries {
fileNames = append(fileNames, e.Name())
}
sort.Strings(fileNames)
attachments := []*NetworkAttachment{}
for _, fname := range fileNames {
if len(containerID) > 0 {
part := fmt.Sprintf("-%s-", containerID)
pos := strings.Index(fname, part)
if pos <= 0 || pos+len(part) >= len(fname) {
continue
}
}
cacheFile := filepath.Join(dirPath, fname)
bytes, err := os.ReadFile(cacheFile)
if err != nil {
continue
}
cachedInfo := cachedInfo{}
if err := json.Unmarshal(bytes, &cachedInfo); err != nil {
continue
}
if cachedInfo.Kind != CNICacheV1 {
continue
}
if len(containerID) > 0 && cachedInfo.ContainerID != containerID {
continue
}
if cachedInfo.IfName == "" || cachedInfo.NetworkName == "" {
continue
}
attachments = append(attachments, &NetworkAttachment{
ContainerID: cachedInfo.ContainerID,
Network: cachedInfo.NetworkName,
IfName: cachedInfo.IfName,
Config: cachedInfo.Config,
NetNS: cachedInfo.NetNS,
CniArgs: cachedInfo.CniArgs,
CapabilityArgs: cachedInfo.CapabilityArgs,
})
}
return attachments, nil
}
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)
@ -453,7 +545,7 @@ func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigLis
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)
return fmt.Errorf("configuration version %q %w", list.CNIVersion, ErrorCheckNotSupp)
}
if list.DisableCheck {
@ -497,9 +589,9 @@ func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList,
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 fmt.Errorf("failed to get network %q cached result: %w", list.Name, err)
if cachedResult, err = c.getCachedResult(list.Name, list.CNIVersion, rt); err != nil {
_ = c.cacheDel(list.Name, rt)
cachedResult = nil
}
}
@ -509,6 +601,7 @@ func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList,
return fmt.Errorf("plugin %s failed (delete): %w", pluginDescription(net.Network), err)
}
}
_ = c.cacheDel(list.Name, rt)
return nil
@ -547,7 +640,7 @@ func (c *CNIConfig) CheckNetwork(ctx context.Context, net *NetworkConfig, rt *Ru
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)
return fmt.Errorf("configuration version %q %w", net.Network.CNIVersion, ErrorCheckNotSupp)
}
cachedResult, err := c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
@ -666,6 +759,129 @@ func (c *CNIConfig) GetVersionInfo(ctx context.Context, pluginType string) (vers
return invoke.GetVersionInfo(ctx, pluginPath, c.exec)
}
// GCNetworkList will do two things
// - dump the list of cached attachments, and issue deletes as necessary
// - issue a GC to the underlying plugins (if the version is high enough)
func (c *CNIConfig) GCNetworkList(ctx context.Context, list *NetworkConfigList, args *GCArgs) error {
// If DisableGC is set, then don't bother GCing at all.
if list.DisableGC {
return nil
}
// First, get the list of cached attachments
cachedAttachments, err := c.GetCachedAttachments("")
if err != nil {
return nil
}
var validAttachments map[types.GCAttachment]interface{}
if args != nil {
validAttachments = make(map[types.GCAttachment]interface{}, len(args.ValidAttachments))
for _, a := range args.ValidAttachments {
validAttachments[a] = nil
}
}
var errs []error
for _, cachedAttachment := range cachedAttachments {
if cachedAttachment.Network != list.Name {
continue
}
// we found this attachment
gca := types.GCAttachment{
ContainerID: cachedAttachment.ContainerID,
IfName: cachedAttachment.IfName,
}
if _, ok := validAttachments[gca]; ok {
continue
}
// otherwise, this attachment wasn't valid and we should issue a CNI DEL
rt := RuntimeConf{
ContainerID: cachedAttachment.ContainerID,
NetNS: cachedAttachment.NetNS,
IfName: cachedAttachment.IfName,
Args: cachedAttachment.CniArgs,
CapabilityArgs: cachedAttachment.CapabilityArgs,
}
if err := c.DelNetworkList(ctx, list, &rt); err != nil {
errs = append(errs, fmt.Errorf("failed to delete stale attachment %s %s: %w", rt.ContainerID, rt.IfName, err))
}
}
// now, if the version supports it, issue a GC
if gt, _ := version.GreaterThanOrEqualTo(list.CNIVersion, "1.1.0"); gt {
inject := map[string]interface{}{
"name": list.Name,
"cniVersion": list.CNIVersion,
}
if args != nil {
inject["cni.dev/valid-attachments"] = args.ValidAttachments
// #1101: spec used incorrect variable name
inject["cni.dev/attachments"] = args.ValidAttachments
}
for _, plugin := range list.Plugins {
// build config here
pluginConfig, err := InjectConf(plugin, inject)
if err != nil {
errs = append(errs, fmt.Errorf("failed to generate configuration to GC plugin %s: %w", plugin.Network.Type, err))
}
if err := c.gcNetwork(ctx, pluginConfig); err != nil {
errs = append(errs, fmt.Errorf("failed to GC plugin %s: %w", plugin.Network.Type, err))
}
}
}
return errors.Join(errs...)
}
func (c *CNIConfig) gcNetwork(ctx context.Context, net *NetworkConfig) error {
c.ensureExec()
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
if err != nil {
return err
}
args := c.args("GC", &RuntimeConf{})
return invoke.ExecPluginWithoutResult(ctx, pluginPath, net.Bytes, args, c.exec)
}
func (c *CNIConfig) GetStatusNetworkList(ctx context.Context, list *NetworkConfigList) error {
// If the version doesn't support status, abort.
if gt, _ := version.GreaterThanOrEqualTo(list.CNIVersion, "1.1.0"); !gt {
return nil
}
inject := map[string]interface{}{
"name": list.Name,
"cniVersion": list.CNIVersion,
}
for _, plugin := range list.Plugins {
// build config here
pluginConfig, err := InjectConf(plugin, inject)
if err != nil {
return fmt.Errorf("failed to generate configuration to get plugin STATUS %s: %w", plugin.Network.Type, err)
}
if err := c.getStatusNetwork(ctx, pluginConfig); err != nil {
return err // Don't collect errors here, so we return a clean error code.
}
}
return nil
}
func (c *CNIConfig) getStatusNetwork(ctx context.Context, net *NetworkConfig) error {
c.ensureExec()
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
if err != nil {
return err
}
args := c.args("STATUS", &RuntimeConf{})
return invoke.ExecPluginWithoutResult(ctx, pluginPath, net.Bytes, args, c.exec)
}
// =====
func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args {
return &invoke.Args{

View File

@ -16,13 +16,16 @@ package libcni
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"slices"
"sort"
"strings"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version"
)
type NotFoundError struct {
@ -54,7 +57,7 @@ func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
}
func ConfFromFile(filename string) (*NetworkConfig, error) {
bytes, err := ioutil.ReadFile(filename)
bytes, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("error reading %s: %w", filename, err)
}
@ -85,17 +88,89 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
}
}
disableCheck := false
if rawDisableCheck, ok := rawList["disableCheck"]; ok {
disableCheck, ok = rawDisableCheck.(bool)
rawVersions, ok := rawList["cniVersions"]
if ok {
// Parse the current package CNI version
rvs, ok := rawVersions.([]interface{})
if !ok {
return nil, fmt.Errorf("error parsing configuration list: invalid disableCheck type %T", rawDisableCheck)
return nil, fmt.Errorf("error parsing configuration list: invalid type for cniVersions: %T", rvs)
}
vs := make([]string, 0, len(rvs))
for i, rv := range rvs {
v, ok := rv.(string)
if !ok {
return nil, fmt.Errorf("error parsing configuration list: invalid type for cniVersions index %d: %T", i, rv)
}
gt, err := version.GreaterThan(v, version.Current())
if err != nil {
return nil, fmt.Errorf("error parsing configuration list: invalid cniVersions entry %s at index %d: %w", v, i, err)
} else if !gt {
// Skip versions "greater" than this implementation of the spec
vs = append(vs, v)
}
}
// if cniVersion was already set, append it to the list for sorting.
if cniVersion != "" {
gt, err := version.GreaterThan(cniVersion, version.Current())
if err != nil {
return nil, fmt.Errorf("error parsing configuration list: invalid cniVersion %s: %w", cniVersion, err)
} else if !gt {
// ignore any versions higher than the current implemented spec version
vs = append(vs, cniVersion)
}
}
slices.SortFunc[[]string](vs, func(v1, v2 string) int {
if v1 == v2 {
return 0
}
if gt, _ := version.GreaterThan(v1, v2); gt {
return 1
}
return -1
})
if len(vs) > 0 {
cniVersion = vs[len(vs)-1]
}
}
readBool := func(key string) (bool, error) {
rawVal, ok := rawList[key]
if !ok {
return false, nil
}
if b, ok := rawVal.(bool); ok {
return b, nil
}
s, ok := rawVal.(string)
if !ok {
return false, fmt.Errorf("error parsing configuration list: invalid type %T for %s", rawVal, key)
}
s = strings.ToLower(s)
switch s {
case "false":
return false, nil
case "true":
return true, nil
}
return false, fmt.Errorf("error parsing configuration list: invalid value %q for %s", s, key)
}
disableCheck, err := readBool("disableCheck")
if err != nil {
return nil, err
}
disableGC, err := readBool("disableGC")
if err != nil {
return nil, err
}
list := &NetworkConfigList{
Name: name,
DisableCheck: disableCheck,
DisableGC: disableGC,
CNIVersion: cniVersion,
Bytes: bytes,
}
@ -129,7 +204,7 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
}
func ConfListFromFile(filename string) (*NetworkConfigList, error) {
bytes, err := ioutil.ReadFile(filename)
bytes, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("error reading %s: %w", filename, err)
}
@ -138,7 +213,7 @@ func ConfListFromFile(filename string) (*NetworkConfigList, error) {
func ConfFiles(dir string, extensions []string) ([]string, error) {
// In part, adapted from rkt/networking/podenv.go#listFiles
files, err := ioutil.ReadDir(dir)
files, err := os.ReadDir(dir)
switch {
case err == nil: // break
case os.IsNotExist(err):
@ -206,7 +281,8 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
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 {
var ncfErr NoConfigsFoundError
if len(files) != 0 && errors.As(err, &ncfErr) {
// Config lists found but no config files found
return nil, NotFoundError{dir, name}
}

View File

@ -51,25 +51,34 @@ func DelegateAdd(ctx context.Context, delegatePlugin string, netconf []byte, exe
// 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 {
return delegateNoResult(ctx, delegatePlugin, netconf, exec, "CHECK")
}
func delegateNoResult(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec, verb string) error {
pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
if err != nil {
return err
}
// DelegateCheck will override the original CNI_COMMAND env from process with CHECK
return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("CHECK"), realExec)
return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs(verb), realExec)
}
// 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 delegateNoResult(ctx, delegatePlugin, netconf, exec, "DEL")
}
// DelegateDel will override the original CNI_COMMAND env from process with DEL
return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("DEL"), realExec)
// DelegateStatus calls the given delegate plugin with the CNI STATUS action and
// JSON configuration
func DelegateStatus(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
return delegateNoResult(ctx, delegatePlugin, netconf, exec, "STATUS")
}
// DelegateGC calls the given delegate plugin with the CNI GC action and
// JSON configuration
func DelegateGC(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
return delegateNoResult(ctx, delegatePlugin, netconf, exec, "GC")
}
// return CNIArgs used by delegation

View File

@ -81,17 +81,17 @@ func fixupResultVersion(netconf, result []byte) (string, []byte, error) {
// object to ExecPluginWithResult() to verify the incoming stdin and environment
// and provide a tailored response:
//
//import (
// import (
// "encoding/json"
// "path"
// "strings"
//)
// )
//
//type fakeExec struct {
// type fakeExec struct {
// version.PluginDecoder
//}
// }
//
//func (f *fakeExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
// func (f *fakeExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
// net := &types.NetConf{}
// err := json.Unmarshal(stdinData, net)
// if err != nil {
@ -109,14 +109,14 @@ func fixupResultVersion(netconf, result []byte) (string, []byte, error) {
// }
// }
// return []byte("{\"CNIVersion\":\"0.4.0\"}"), nil
//}
// }
//
//func (f *fakeExec) FindInPath(plugin string, paths []string) (string, error) {
// 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)
//}
// }
func ExecPluginWithResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) (types.Result, error) {
if exec == nil {

View File

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

View File

@ -0,0 +1,21 @@
// Copyright 2022 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 ns
import "github.com/containernetworking/cni/pkg/types"
func CheckNetNS(nsPath string) (bool, *types.Error) {
return false, nil
}

View File

@ -0,0 +1,50 @@
// Copyright 2022 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 ns
import (
"runtime"
"github.com/vishvananda/netns"
"github.com/containernetworking/cni/pkg/types"
)
// Returns an object representing the current OS thread's network namespace
func getCurrentNS() (netns.NsHandle, error) {
// Lock the thread in case other goroutine executes in it and changes its
// network namespace after getCurrentThreadNetNSPath(), otherwise it might
// return an unexpected network namespace.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
return netns.Get()
}
func CheckNetNS(nsPath string) (bool, *types.Error) {
ns, err := netns.GetFromPath(nsPath)
// Let plugins check whether nsPath from args is valid. Also support CNI DEL for empty nsPath as already-deleted nsPath.
if err != nil {
return false, nil
}
defer ns.Close()
pluginNS, err := getCurrentNS()
if err != nil {
return false, types.NewError(types.ErrInvalidNetNS, "get plugin's netns failed", "")
}
defer pluginNS.Close()
return pluginNS.Equal(ns), nil
}

View File

@ -0,0 +1,21 @@
// Copyright 2022 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 ns
import "github.com/containernetworking/cni/pkg/types"
func CheckNetNS(nsPath string) (bool, *types.Error) {
return false, nil
}

View File

@ -19,13 +19,14 @@ package skel
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strings"
"github.com/containernetworking/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/utils"
"github.com/containernetworking/cni/pkg/version"
@ -34,12 +35,13 @@ import (
// CmdArgs captures all the arguments passed in to the plugin
// via both env vars and stdin
type CmdArgs struct {
ContainerID string
Netns string
IfName string
Args string
Path string
StdinData []byte
ContainerID string
Netns string
IfName string
Args string
Path string
NetnsOverride string
StdinData []byte
}
type dispatcher struct {
@ -55,21 +57,25 @@ type dispatcher struct {
type reqForCmdEntry map[string]bool
func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
var cmd, contID, netns, ifName, args, path string
var cmd, contID, netns, ifName, args, path, netnsOverride string
vars := []struct {
name string
val *string
reqForCmd reqForCmdEntry
name string
val *string
reqForCmd reqForCmdEntry
validateFn func(string) *types.Error
}{
{
"CNI_COMMAND",
&cmd,
reqForCmdEntry{
"ADD": true,
"CHECK": true,
"DEL": true,
"ADD": true,
"CHECK": true,
"DEL": true,
"GC": true,
"STATUS": true,
},
nil,
},
{
"CNI_CONTAINERID",
@ -79,6 +85,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
"CHECK": true,
"DEL": true,
},
utils.ValidateContainerID,
},
{
"CNI_NETNS",
@ -88,6 +95,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
"CHECK": true,
"DEL": false,
},
nil,
},
{
"CNI_IFNAME",
@ -97,6 +105,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
"CHECK": true,
"DEL": true,
},
utils.ValidateInterfaceName,
},
{
"CNI_ARGS",
@ -106,15 +115,29 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
"CHECK": false,
"DEL": false,
},
nil,
},
{
"CNI_PATH",
&path,
reqForCmdEntry{
"ADD": true,
"CHECK": true,
"DEL": true,
"ADD": true,
"CHECK": true,
"DEL": true,
"GC": true,
"STATUS": true,
},
nil,
},
{
"CNI_NETNS_OVERRIDE",
&netnsOverride,
reqForCmdEntry{
"ADD": false,
"CHECK": false,
"DEL": false,
},
nil,
},
}
@ -125,6 +148,10 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
if v.reqForCmd[cmd] || v.name == "CNI_COMMAND" {
argsMissing = append(argsMissing, v.name)
}
} else if v.reqForCmd[cmd] && v.validateFn != nil {
if err := v.validateFn(*v.val); err != nil {
return "", nil, err
}
}
}
@ -137,18 +164,25 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
t.Stdin = bytes.NewReader(nil)
}
stdinData, err := ioutil.ReadAll(t.Stdin)
stdinData, err := io.ReadAll(t.Stdin)
if err != nil {
return "", nil, types.NewError(types.ErrIOFailure, fmt.Sprintf("error reading from stdin: %v", err), "")
}
if cmd != "VERSION" {
if err := validateConfig(stdinData); err != nil {
return "", nil, err
}
}
cmdArgs := &CmdArgs{
ContainerID: contID,
Netns: netns,
IfName: ifName,
Args: args,
Path: path,
StdinData: stdinData,
ContainerID: contID,
Netns: netns,
IfName: ifName,
Args: args,
Path: path,
StdinData: stdinData,
NetnsOverride: netnsOverride,
}
return cmd, cmdArgs, nil
}
@ -163,8 +197,13 @@ func (t *dispatcher) checkVersionAndCall(cmdArgs *CmdArgs, pluginVersionInfo ver
return types.NewError(types.ErrIncompatibleCNIVersion, "incompatible CNI versions", verErr.Details())
}
if toCall == nil {
return nil
}
if err = toCall(cmdArgs); err != nil {
if e, ok := err.(*types.Error); ok {
var e *types.Error
if errors.As(err, &e) {
// don't wrap Error in Error
return e
}
@ -190,7 +229,7 @@ func validateConfig(jsonBytes []byte) *types.Error {
return nil
}
func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) *types.Error {
func (t *dispatcher) pluginMain(funcs CNIFuncs, versionInfo version.PluginInfo, about string) *types.Error {
cmd, cmdArgs, err := t.getCmdArgsFromEnv()
if err != nil {
// Print the about string to stderr when no command is set
@ -202,21 +241,20 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error,
return err
}
if cmd != "VERSION" {
if err = validateConfig(cmdArgs.StdinData); err != nil {
return err
}
if err = utils.ValidateContainerID(cmdArgs.ContainerID); err != nil {
return err
}
if err = utils.ValidateInterfaceName(cmdArgs.IfName); err != nil {
return err
}
}
switch cmd {
case "ADD":
err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdAdd)
err = t.checkVersionAndCall(cmdArgs, versionInfo, funcs.Add)
if err != nil {
return err
}
if strings.ToUpper(cmdArgs.NetnsOverride) != "TRUE" && cmdArgs.NetnsOverride != "1" {
isPluginNetNS, checkErr := ns.CheckNetNS(cmdArgs.Netns)
if checkErr != nil {
return checkErr
} else if isPluginNetNS {
return types.NewError(types.ErrInvalidNetNS, "plugin's netns and netns from CNI_NETNS should not be the same", "")
}
}
case "CHECK":
configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData)
if err != nil {
@ -232,7 +270,7 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error,
if err != nil {
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
} else if gtet {
if err := t.checkVersionAndCall(cmdArgs, versionInfo, cmdCheck); err != nil {
if err := t.checkVersionAndCall(cmdArgs, versionInfo, funcs.Check); err != nil {
return err
}
return nil
@ -240,7 +278,62 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error,
}
return types.NewError(types.ErrIncompatibleCNIVersion, "plugin version does not allow CHECK", "")
case "DEL":
err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdDel)
err = t.checkVersionAndCall(cmdArgs, versionInfo, funcs.Del)
if err != nil {
return err
}
if strings.ToUpper(cmdArgs.NetnsOverride) != "TRUE" && cmdArgs.NetnsOverride != "1" {
isPluginNetNS, checkErr := ns.CheckNetNS(cmdArgs.Netns)
if checkErr != nil {
return checkErr
} else if isPluginNetNS {
return types.NewError(types.ErrInvalidNetNS, "plugin's netns and netns from CNI_NETNS should not be the same", "")
}
}
case "GC":
configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData)
if err != nil {
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
}
if gtet, err := version.GreaterThanOrEqualTo(configVersion, "1.1.0"); err != nil {
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
} else if !gtet {
return types.NewError(types.ErrIncompatibleCNIVersion, "config version does not allow GC", "")
}
for _, pluginVersion := range versionInfo.SupportedVersions() {
gtet, err := version.GreaterThanOrEqualTo(pluginVersion, configVersion)
if err != nil {
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
} else if gtet {
if err := t.checkVersionAndCall(cmdArgs, versionInfo, funcs.GC); err != nil {
return err
}
return nil
}
}
return types.NewError(types.ErrIncompatibleCNIVersion, "plugin version does not allow GC", "")
case "STATUS":
configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData)
if err != nil {
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
}
if gtet, err := version.GreaterThanOrEqualTo(configVersion, "1.1.0"); err != nil {
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
} else if !gtet {
return types.NewError(types.ErrIncompatibleCNIVersion, "config version does not allow STATUS", "")
}
for _, pluginVersion := range versionInfo.SupportedVersions() {
gtet, err := version.GreaterThanOrEqualTo(pluginVersion, configVersion)
if err != nil {
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
} else if gtet {
if err := t.checkVersionAndCall(cmdArgs, versionInfo, funcs.Status); err != nil {
return err
}
return nil
}
}
return types.NewError(types.ErrIncompatibleCNIVersion, "plugin version does not allow STATUS", "")
case "VERSION":
if err := versionInfo.Encode(t.Stdout); err != nil {
return types.NewError(types.ErrIOFailure, err.Error(), "")
@ -264,13 +357,63 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error,
//
// To let this package automatically handle errors and call os.Exit(1) for you,
// use PluginMain() instead.
//
// Deprecated: Use github.com/containernetworking/cni/pkg/skel.PluginMainFuncsWithError instead.
func PluginMainWithError(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) *types.Error {
return PluginMainFuncsWithError(CNIFuncs{Add: cmdAdd, Check: cmdCheck, Del: cmdDel}, versionInfo, about)
}
// CNIFuncs contains a group of callback command funcs to be passed in as
// parameters to the core "main" for a plugin.
type CNIFuncs struct {
Add func(_ *CmdArgs) error
Del func(_ *CmdArgs) error
Check func(_ *CmdArgs) error
GC func(_ *CmdArgs) error
Status func(_ *CmdArgs) error
}
// PluginMainFuncsWithError is the core "main" for a plugin. It accepts
// callback functions defined within CNIFuncs and returns an error.
//
// The caller must also specify what CNI spec versions the plugin supports.
//
// It is the responsibility of the caller to check for non-nil error return.
//
// For a plugin to comply with the CNI spec, it must print any error to stdout
// as JSON and then exit with nonzero status code.
//
// To let this package automatically handle errors and call os.Exit(1) for you,
// use PluginMainFuncs() instead.
func PluginMainFuncsWithError(funcs CNIFuncs, versionInfo version.PluginInfo, about string) *types.Error {
return (&dispatcher{
Getenv: os.Getenv,
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
}).pluginMain(cmdAdd, cmdCheck, cmdDel, versionInfo, about)
}).pluginMain(funcs, versionInfo, about)
}
// PluginMainFuncs is the core "main" for a plugin which includes automatic error handling.
// This is a newer alternative func to PluginMain which abstracts CNI commands within a
// CNIFuncs interface.
//
// The caller must also specify what CNI spec versions the plugin supports.
//
// The caller can specify an "about" string, which is printed on stderr
// when no CNI_COMMAND is specified. The recommended output is "CNI plugin <foo> v<version>"
//
// When an error occurs in any func in CNIFuncs, PluginMainFuncs will print the error
// as JSON to stdout and call os.Exit(1).
//
// To have more control over error handling, use PluginMainFuncsWithError() instead.
func PluginMainFuncs(funcs CNIFuncs, versionInfo version.PluginInfo, about string) {
if e := PluginMainFuncsWithError(funcs, versionInfo, about); e != nil {
if err := e.Print(); err != nil {
log.Print("Error writing error JSON to stdout: ", err)
}
os.Exit(1)
}
}
// PluginMain is the core "main" for a plugin which includes automatic error handling.
@ -284,6 +427,8 @@ func PluginMainWithError(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versio
// as JSON to stdout and call os.Exit(1).
//
// To have more control over error handling, use PluginMainWithError() instead.
//
// Deprecated: Use github.com/containernetworking/cni/pkg/skel.PluginMainFuncs instead.
func PluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) {
if e := PluginMainWithError(cmdAdd, cmdCheck, cmdDel, versionInfo, about); e != nil {
if err := e.Print(); err != nil {

View File

@ -26,9 +26,10 @@ import (
convert "github.com/containernetworking/cni/pkg/types/internal"
)
const ImplementedSpecVersion string = "1.0.0"
// The types did not change between v1.0 and v1.1
const ImplementedSpecVersion string = "1.1.0"
var supportedVersions = []string{ImplementedSpecVersion}
var supportedVersions = []string{"1.0.0", "1.1.0"}
// Register converters for all versions less than the implemented spec version
func init() {
@ -38,10 +39,14 @@ func init() {
convert.RegisterConverter("0.3.0", supportedVersions, convertFrom04x)
convert.RegisterConverter("0.3.1", supportedVersions, convertFrom04x)
convert.RegisterConverter("0.4.0", supportedVersions, convertFrom04x)
convert.RegisterConverter("1.0.0", []string{"1.1.0"}, convertFrom100)
// Down-converters
convert.RegisterConverter("1.0.0", []string{"0.3.0", "0.3.1", "0.4.0"}, convertTo04x)
convert.RegisterConverter("1.0.0", []string{"0.1.0", "0.2.0"}, convertTo02x)
convert.RegisterConverter("1.1.0", []string{"0.3.0", "0.3.1", "0.4.0"}, convertTo04x)
convert.RegisterConverter("1.1.0", []string{"0.1.0", "0.2.0"}, convertTo02x)
convert.RegisterConverter("1.1.0", []string{"1.0.0"}, convertFrom100)
// Creator
convert.RegisterCreator(supportedVersions, NewResult)
@ -90,12 +95,49 @@ type Result struct {
DNS types.DNS `json:"dns,omitempty"`
}
// Note: DNS should be omit if DNS is empty but default Marshal function
// will output empty structure hence need to write a Marshal function
func (r *Result) MarshalJSON() ([]byte, error) {
// use type alias to escape recursion for json.Marshal() to MarshalJSON()
type fixObjType = Result
bytes, err := json.Marshal(fixObjType(*r)) //nolint:all
if err != nil {
return nil, err
}
fixupObj := make(map[string]interface{})
if err := json.Unmarshal(bytes, &fixupObj); err != nil {
return nil, err
}
if r.DNS.IsEmpty() {
delete(fixupObj, "dns")
}
return json.Marshal(fixupObj)
}
// convertFrom100 does nothing except set the version; the types are the same
func convertFrom100(from types.Result, toVersion string) (types.Result, error) {
fromResult := from.(*Result)
result := &Result{
CNIVersion: toVersion,
Interfaces: fromResult.Interfaces,
IPs: fromResult.IPs,
Routes: fromResult.Routes,
DNS: fromResult.DNS,
}
return result, nil
}
func convertFrom02x(from types.Result, toVersion string) (types.Result, error) {
result040, err := convert.Convert(from, "0.4.0")
if err != nil {
return nil, err
}
result100, err := convertFrom04x(result040, ImplementedSpecVersion)
result100, err := convertFrom04x(result040, toVersion)
if err != nil {
return nil, err
}
@ -226,9 +268,12 @@ func (r *Result) PrintTo(writer io.Writer) error {
// Interface contains values about the created interfaces
type Interface struct {
Name string `json:"name"`
Mac string `json:"mac,omitempty"`
Sandbox string `json:"sandbox,omitempty"`
Name string `json:"name"`
Mac string `json:"mac,omitempty"`
Mtu int `json:"mtu,omitempty"`
Sandbox string `json:"sandbox,omitempty"`
SocketPath string `json:"socketPath,omitempty"`
PciID string `json:"pciID,omitempty"`
}
func (i *Interface) String() string {

View File

@ -26,8 +26,8 @@ import (
type UnmarshallableBool bool
// UnmarshalText implements the encoding.TextUnmarshaler interface.
// Returns boolean true if the string is "1" or "[Tt]rue"
// Returns boolean false if the string is "0" or "[Ff]alse"
// Returns boolean true if the string is "1" or "true" or "True"
// Returns boolean false if the string is "0" or "false" or "False”
func (b *UnmarshallableBool) UnmarshalText(data []byte) error {
s := strings.ToLower(string(data))
switch s {

View File

@ -19,6 +19,9 @@ import (
"fmt"
"github.com/containernetworking/cni/pkg/types"
_ "github.com/containernetworking/cni/pkg/types/020"
_ "github.com/containernetworking/cni/pkg/types/040"
_ "github.com/containernetworking/cni/pkg/types/100"
convert "github.com/containernetworking/cni/pkg/types/internal"
)

View File

@ -56,30 +56,73 @@ func (n *IPNet) UnmarshalJSON(data []byte) error {
return nil
}
// NetConf describes a network.
type NetConf struct {
// NetConfType describes a network.
type NetConfType struct {
CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Capabilities map[string]bool `json:"capabilities,omitempty"`
IPAM IPAM `json:"ipam,omitempty"`
DNS DNS `json:"dns"`
DNS DNS `json:"dns,omitempty"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult Result `json:"-"`
// ValidAttachments is only supplied when executing a GC operation
ValidAttachments []GCAttachment `json:"cni.dev/valid-attachments,omitempty"`
}
// NetConf is defined as different type as custom MarshalJSON() and issue #1096
type NetConf NetConfType
// GCAttachment is the parameters to a GC call -- namely,
// the container ID and ifname pair that represents a
// still-valid attachment.
type GCAttachment struct {
ContainerID string `json:"containerID"`
IfName string `json:"ifname"`
}
// Note: DNS should be omit if DNS is empty but default Marshal function
// will output empty structure hence need to write a Marshal function
func (n *NetConfType) MarshalJSON() ([]byte, error) {
// use type alias to escape recursion for json.Marshal() to MarshalJSON()
type fixObjType = NetConf
bytes, err := json.Marshal(fixObjType(*n))
if err != nil {
return nil, err
}
fixupObj := make(map[string]interface{})
if err := json.Unmarshal(bytes, &fixupObj); err != nil {
return nil, err
}
if n.DNS.IsEmpty() {
delete(fixupObj, "dns")
}
return json.Marshal(fixupObj)
}
type IPAM struct {
Type string `json:"type,omitempty"`
}
// IsEmpty returns true if IPAM structure has no value, otherwise return false
func (i *IPAM) IsEmpty() bool {
return i.Type == ""
}
// NetConfList describes an ordered list of networks.
type NetConfList struct {
CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"`
DisableCheck bool `json:"disableCheck,omitempty"`
DisableGC bool `json:"disableGC,omitempty"`
Plugins []*NetConf `json:"plugins,omitempty"`
}
@ -116,31 +159,48 @@ type DNS struct {
Options []string `json:"options,omitempty"`
}
// IsEmpty returns true if DNS structure has no value, otherwise return false
func (d *DNS) IsEmpty() bool {
if len(d.Nameservers) == 0 && d.Domain == "" && len(d.Search) == 0 && len(d.Options) == 0 {
return true
}
return false
}
func (d *DNS) Copy() *DNS {
if d == nil {
return nil
}
to := &DNS{Domain: d.Domain}
for _, ns := range d.Nameservers {
to.Nameservers = append(to.Nameservers, ns)
}
for _, s := range d.Search {
to.Search = append(to.Search, s)
}
for _, o := range d.Options {
to.Options = append(to.Options, o)
}
to.Nameservers = append(to.Nameservers, d.Nameservers...)
to.Search = append(to.Search, d.Search...)
to.Options = append(to.Options, d.Options...)
return to
}
type Route struct {
Dst net.IPNet
GW net.IP
Dst net.IPNet
GW net.IP
MTU int
AdvMSS int
Priority int
Table *int
Scope *int
}
func (r *Route) String() string {
return fmt.Sprintf("%+v", *r)
table := "<nil>"
if r.Table != nil {
table = fmt.Sprintf("%d", *r.Table)
}
scope := "<nil>"
if r.Scope != nil {
scope = fmt.Sprintf("%d", *r.Scope)
}
return fmt.Sprintf("{Dst:%+v GW:%v MTU:%d AdvMSS:%d Priority:%d Table:%s Scope:%s}", r.Dst, r.GW, r.MTU, r.AdvMSS, r.Priority, table, scope)
}
func (r *Route) Copy() *Route {
@ -148,14 +208,30 @@ func (r *Route) Copy() *Route {
return nil
}
return &Route{
Dst: r.Dst,
GW: r.GW,
route := &Route{
Dst: r.Dst,
GW: r.GW,
MTU: r.MTU,
AdvMSS: r.AdvMSS,
Priority: r.Priority,
Scope: r.Scope,
}
if r.Table != nil {
table := *r.Table
route.Table = &table
}
if r.Scope != nil {
scope := *r.Scope
route.Scope = &scope
}
return route
}
// 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/main/SPEC.md#well-known-error-codes
const (
ErrUnknown uint = iota // 0
ErrIncompatibleCNIVersion // 1
@ -165,6 +241,7 @@ const (
ErrIOFailure // 5
ErrDecodingFailure // 6
ErrInvalidNetworkConfig // 7
ErrInvalidNetNS // 8
ErrTryAgainLater uint = 11
ErrInternal uint = 999
)
@ -200,8 +277,13 @@ func (e *Error) Print() error {
// JSON (un)marshallable types
type route struct {
Dst IPNet `json:"dst"`
GW net.IP `json:"gw,omitempty"`
Dst IPNet `json:"dst"`
GW net.IP `json:"gw,omitempty"`
MTU int `json:"mtu,omitempty"`
AdvMSS int `json:"advmss,omitempty"`
Priority int `json:"priority,omitempty"`
Table *int `json:"table,omitempty"`
Scope *int `json:"scope,omitempty"`
}
func (r *Route) UnmarshalJSON(data []byte) error {
@ -212,13 +294,24 @@ func (r *Route) UnmarshalJSON(data []byte) error {
r.Dst = net.IPNet(rt.Dst)
r.GW = rt.GW
r.MTU = rt.MTU
r.AdvMSS = rt.AdvMSS
r.Priority = rt.Priority
r.Table = rt.Table
r.Scope = rt.Scope
return nil
}
func (r Route) MarshalJSON() ([]byte, error) {
rt := route{
Dst: IPNet(r.Dst),
GW: r.GW,
Dst: IPNet(r.Dst),
GW: r.GW,
MTU: r.MTU,
AdvMSS: r.AdvMSS,
Priority: r.Priority,
Table: r.Table,
Scope: r.Scope,
}
return json.Marshal(rt)

View File

@ -36,7 +36,6 @@ 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", "")
}
@ -48,7 +47,6 @@ func ValidateContainerID(containerID string) *types.Error {
// 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:", "")
}
@ -58,11 +56,11 @@ func ValidateNetworkName(networkName string) *types.Error {
return nil
}
// ValidateInterfaceName will validate the interface name based on the three rules below
// ValidateInterfaceName will validate the interface name based on the four 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
// 4. 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 {

View File

@ -142,3 +142,27 @@ func GreaterThanOrEqualTo(version, otherVersion string) (bool, error) {
}
return false, nil
}
// GreaterThan returns true if the first version is greater than the second
func GreaterThan(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
}

View File

@ -19,13 +19,12 @@ import (
"fmt"
"github.com/containernetworking/cni/pkg/types"
types100 "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/types/create"
)
// Current reports the version of the CNI spec implemented by this library
func Current() string {
return types100.ImplementedSpecVersion
return "1.1.0"
}
// Legacy PluginInfo describes a plugin that is backwards compatible with the
@ -35,8 +34,10 @@ 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", "0.4.0", "1.0.0")
var (
Legacy = PluginSupports("0.1.0", "0.2.0")
All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0", "1.1.0")
)
// VersionsFrom returns a list of versions starting from min, inclusive
func VersionsStartingFrom(min string) PluginInfo {