Build and install from GOPATH

* Rename 'vendor/src' -> 'vendor'
  * Ignore vendor/ instead of vendor/src/ for lint
* Rename 'cmd/client' -> 'cmd/ocic' to make it 'go install'able
* Rename 'cmd/server' -> 'cmd/ocid' to make it 'go install'able
* Update Makefile to build and install from GOPATH
* Update tests to locate ocid/ocic in GOPATH/bin
* Search for binaries in GOPATH/bin instead of PATH
* Install tools using `go get -u`, so they are updated on each run

Signed-off-by: Jonathan Yu <jawnsy@redhat.com>
This commit is contained in:
Jonathan Yu 2017-01-17 11:57:35 -08:00
parent 9da2882d49
commit 6c9628cdb1
No known key found for this signature in database
GPG key ID: F3C2F688A21CB648
1111 changed files with 70 additions and 61 deletions

View file

@ -0,0 +1,76 @@
// Copyright 2015 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package invoke
import (
"os"
"strings"
)
type CNIArgs interface {
// For use with os/exec; i.e., return nil to inherit the
// environment from this process
AsEnv() []string
}
type inherited struct{}
var inheritArgsFromEnv inherited
func (_ *inherited) AsEnv() []string {
return nil
}
func ArgsFromEnv() CNIArgs {
return &inheritArgsFromEnv
}
type Args struct {
Command string
ContainerID string
NetNS string
PluginArgs [][2]string
PluginArgsStr string
IfName string
Path string
}
func (args *Args) AsEnv() []string {
env := os.Environ()
pluginArgsStr := args.PluginArgsStr
if pluginArgsStr == "" {
pluginArgsStr = stringify(args.PluginArgs)
}
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 env
}
// taken from rkt/networking/net_plugin.go
func stringify(pluginArgs [][2]string) string {
entries := make([]string, len(pluginArgs))
for i, kv := range pluginArgs {
entries[i] = strings.Join(kv[:], "=")
}
return strings.Join(entries, ";")
}

View file

@ -0,0 +1,53 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package invoke
import (
"fmt"
"os"
"strings"
"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")
}
paths := strings.Split(os.Getenv("CNI_PATH"), ":")
pluginPath, err := FindInPath(delegatePlugin, paths)
if err != nil {
return nil, err
}
return ExecPluginWithResult(pluginPath, netconf, ArgsFromEnv())
}
func DelegateDel(delegatePlugin string, netconf []byte) error {
if os.Getenv("CNI_COMMAND") != "DEL" {
return fmt.Errorf("CNI_COMMAND is not DEL")
}
paths := strings.Split(os.Getenv("CNI_PATH"), ":")
pluginPath, err := FindInPath(delegatePlugin, paths)
if err != nil {
return err
}
return ExecPluginWithoutResult(pluginPath, netconf, ArgsFromEnv())
}

View file

@ -0,0 +1,91 @@
// Copyright 2015 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package invoke
import (
"encoding/json"
"fmt"
"os"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version"
)
func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (*types.Result, error) {
return defaultPluginExec.WithResult(pluginPath, netconf, args)
}
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{},
}
type PluginExec struct {
RawExec interface {
ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error)
}
VersionDecoder interface {
Decode(jsonBytes []byte) (version.PluginInfo, error)
}
}
func (e *PluginExec) WithResult(pluginPath string, netconf []byte, args CNIArgs) (*types.Result, error) {
stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv())
if err != nil {
return nil, err
}
res := &types.Result{}
err = json.Unmarshal(stdoutBytes, res)
return res, err
}
func (e *PluginExec) WithoutResult(pluginPath string, netconf []byte, args CNIArgs) error {
_, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv())
return err
}
// GetVersionInfo returns the version information available about the plugin.
// For recent-enough plugins, it uses the information returned by the VERSION
// command. For older plugins which do not recognize that command, it reports
// version 0.1.0
func (e *PluginExec) GetVersionInfo(pluginPath string) (version.PluginInfo, error) {
args := &Args{
Command: "VERSION",
// set fake values required by plugins built against an older version of skel
NetNS: "dummy",
IfName: "dummy",
Path: "dummy",
}
stdin := []byte(fmt.Sprintf(`{"cniVersion":%q}`, version.Current()))
stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, stdin, args.AsEnv())
if err != nil {
if err.Error() == "unknown CNI_COMMAND: VERSION" {
return version.PluginSupports("0.1.0"), nil
}
return nil, err
}
return e.VersionDecoder.Decode(stdoutBytes)
}

View file

@ -0,0 +1,47 @@
// Copyright 2015 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package invoke
import (
"fmt"
"os"
"path/filepath"
)
// FindInPath returns the full path of the plugin by searching in the provided path
func FindInPath(plugin string, paths []string) (string, error) {
if plugin == "" {
return "", fmt.Errorf("no plugin name provided")
}
if len(paths) == 0 {
return "", fmt.Errorf("no paths provided")
}
var fullpath string
for _, path := range paths {
full := filepath.Join(path, plugin)
if fi, err := os.Stat(full); err == nil && fi.Mode().IsRegular() {
fullpath = full
break
}
}
if fullpath == "" {
return "", fmt.Errorf("failed to find plugin %q in path %s", plugin, paths)
}
return fullpath, nil
}

View file

@ -0,0 +1,63 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package invoke
import (
"bytes"
"encoding/json"
"fmt"
"io"
"os/exec"
"github.com/containernetworking/cni/pkg/types"
)
type RawExec struct {
Stderr io.Writer
}
func (e *RawExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
stdout := &bytes.Buffer{}
c := exec.Cmd{
Env: environ,
Path: pluginPath,
Args: []string{pluginPath},
Stdin: bytes.NewBuffer(stdinData),
Stdout: stdout,
Stderr: e.Stderr,
}
if err := c.Run(); err != nil {
return nil, pluginErr(err, stdout.Bytes())
}
return stdout.Bytes(), nil
}
func pluginErr(err error, output []byte) error {
if _, ok := err.(*exec.ExitError); ok {
emsg := types.Error{}
if perr := json.Unmarshal(output, &emsg); perr != nil {
return fmt.Errorf("netplugin failed but error parsing its diagnostic message %q: %v", string(output), perr)
}
details := ""
if emsg.Details != "" {
details = fmt.Sprintf("; %v", emsg.Details)
}
return fmt.Errorf("%v%v", emsg.Msg, details)
}
return err
}

View file

@ -0,0 +1,34 @@
### Namespaces, Threads, and Go
On Linux each OS thread can have a different network namespace. Go's thread scheduling model switches goroutines between OS threads based on OS thread load and whether the goroutine would block other goroutines. This can result in a goroutine switching network namespaces without notice and lead to errors in your code.
### Namespace Switching
Switching namespaces with the `ns.Set()` method is not recommended without additional strategies to prevent unexpected namespace changes when your goroutines switch OS threads.
Go provides the `runtime.LockOSThread()` function to ensure a specific goroutine executes on its current OS thread and prevents any other goroutine from running in that thread until the locked one exits. Careful usage of `LockOSThread()` and goroutines can provide good control over which network namespace a given goroutine executes in.
For example, you cannot rely on the `ns.Set()` namespace being the current namespace after the `Set()` call unless you do two things. First, the goroutine calling `Set()` must have previously called `LockOSThread()`. Second, you must ensure `runtime.UnlockOSThread()` is not called somewhere in-between. You also cannot rely on the initial network namespace remaining the current network namespace if any other code in your program switches namespaces, unless you have already called `LockOSThread()` in that goroutine. Note that `LockOSThread()` prevents the Go scheduler from optimally scheduling goroutines for best performance, so `LockOSThread()` should only be used in small, isolated goroutines that release the lock quickly.
### Do() The Recommended Thing
The `ns.Do()` method provides control over network namespaces for you by implementing these strategies. All code dependent on a particular network namespace (including the root namespace) should be wrapped in the `ns.Do()` method to ensure the correct namespace is selected for the duration of your code. For example:
```go
targetNs, err := ns.NewNS()
if err != nil {
return err
}
err = targetNs.Do(func(hostNs ns.NetNS) error {
dummy := &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: "dummy0",
},
}
return netlink.LinkAdd(dummy)
})
```
Note this requirement to wrap every network call is very onerous - any libraries you call might call out to network services such as DNS, and all such calls need to be protected after you call `ns.Do()`. The CNI plugins all exit very soon after calling `ns.Do()` which helps to minimize the problem.
### Further Reading
- https://github.com/golang/go/wiki/LockOSThread
- http://morsmachine.dk/go-scheduler
- https://github.com/containernetworking/cni/issues/262

302
vendor/github.com/containernetworking/cni/pkg/ns/ns.go generated vendored Normal file
View file

@ -0,0 +1,302 @@
// Copyright 2015 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 (
"crypto/rand"
"fmt"
"os"
"path"
"runtime"
"sync"
"syscall"
"golang.org/x/sys/unix"
)
type NetNS interface {
// Executes the passed closure in this object's network namespace,
// attempting to restore the original namespace before returning.
// However, since each OS thread can have a different network namespace,
// and Go's thread scheduling is highly variable, callers cannot
// guarantee any specific namespace is set unless operations that
// require that namespace are wrapped with Do(). Also, no code called
// from Do() should call runtime.UnlockOSThread(), or the risk
// of executing code in an incorrect namespace will be greater. See
// https://github.com/golang/go/wiki/LockOSThread for further details.
Do(toRun func(NetNS) error) error
// Sets the current network namespace to this object's network namespace.
// Note that since Go's thread scheduling is highly variable, callers
// cannot guarantee the requested namespace will be the current namespace
// after this function is called; to ensure this wrap operations that
// require the namespace with Do() instead.
Set() error
// Returns the filesystem path representing this object's network namespace
Path() string
// Returns a file descriptor representing this object's network namespace
Fd() uintptr
// Cleans up this instance of the network namespace; if this instance
// is the last user the namespace will be destroyed
Close() error
}
type netNS struct {
file *os.File
mounted bool
closed bool
}
func getCurrentThreadNetNSPath() string {
// /proc/self/ns/net returns the namespace of the main thread, not
// of whatever thread this goroutine is running on. Make sure we
// use the thread's net namespace since the thread is switching around
return fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid())
}
// Returns an object representing the current OS thread's network namespace
func GetCurrentNS() (NetNS, error) {
return GetNS(getCurrentThreadNetNSPath())
}
const (
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/magic.h
NSFS_MAGIC = 0x6e736673
PROCFS_MAGIC = 0x9fa0
)
type NSPathNotExistErr struct{ msg string }
func (e NSPathNotExistErr) Error() string { return e.msg }
type NSPathNotNSErr struct{ msg string }
func (e NSPathNotNSErr) Error() string { return e.msg }
func IsNSorErr(nspath string) error {
stat := syscall.Statfs_t{}
if err := syscall.Statfs(nspath, &stat); err != nil {
if os.IsNotExist(err) {
err = NSPathNotExistErr{msg: fmt.Sprintf("failed to Statfs %q: %v", nspath, err)}
} else {
err = fmt.Errorf("failed to Statfs %q: %v", nspath, err)
}
return err
}
switch stat.Type {
case PROCFS_MAGIC, NSFS_MAGIC:
return nil
default:
return NSPathNotNSErr{msg: fmt.Sprintf("unknown FS magic on %q: %x", nspath, stat.Type)}
}
}
// Returns an object representing the namespace referred to by @path
func GetNS(nspath string) (NetNS, error) {
err := IsNSorErr(nspath)
if err != nil {
return nil, err
}
fd, err := os.Open(nspath)
if err != nil {
return nil, err
}
return &netNS{file: fd}, nil
}
// Creates a new persistent network namespace and returns an object
// representing that namespace, without switching to it
func NewNS() (NetNS, error) {
const nsRunDir = "/var/run/netns"
b := make([]byte, 16)
_, err := rand.Reader.Read(b)
if err != nil {
return nil, fmt.Errorf("failed to generate random netns name: %v", err)
}
err = os.MkdirAll(nsRunDir, 0755)
if err != nil {
return nil, err
}
// create an empty file at the mount point
nsName := fmt.Sprintf("cni-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
nsPath := path.Join(nsRunDir, nsName)
mountPointFd, err := os.Create(nsPath)
if err != nil {
return nil, err
}
mountPointFd.Close()
// Ensure the mount point is cleaned up on errors; if the namespace
// was successfully mounted this will have no effect because the file
// is in-use
defer os.RemoveAll(nsPath)
var wg sync.WaitGroup
wg.Add(1)
// do namespace work in a dedicated goroutine, so that we can safely
// Lock/Unlock OSThread without upsetting the lock/unlock state of
// the caller of this function
var fd *os.File
go (func() {
defer wg.Done()
runtime.LockOSThread()
var origNS NetNS
origNS, err = GetNS(getCurrentThreadNetNSPath())
if err != nil {
return
}
defer origNS.Close()
// create a new netns on the current thread
err = unix.Unshare(unix.CLONE_NEWNET)
if err != nil {
return
}
defer origNS.Set()
// bind mount the new netns from the current thread onto the mount point
err = unix.Mount(getCurrentThreadNetNSPath(), nsPath, "none", unix.MS_BIND, "")
if err != nil {
return
}
fd, err = os.Open(nsPath)
if err != nil {
return
}
})()
wg.Wait()
if err != nil {
unix.Unmount(nsPath, unix.MNT_DETACH)
return nil, fmt.Errorf("failed to create namespace: %v", err)
}
return &netNS{file: fd, mounted: true}, nil
}
func (ns *netNS) Path() string {
return ns.file.Name()
}
func (ns *netNS) Fd() uintptr {
return ns.file.Fd()
}
func (ns *netNS) errorIfClosed() error {
if ns.closed {
return fmt.Errorf("%q has already been closed", ns.file.Name())
}
return nil
}
func (ns *netNS) Close() error {
if err := ns.errorIfClosed(); err != nil {
return err
}
if err := ns.file.Close(); err != nil {
return fmt.Errorf("Failed to close %q: %v", ns.file.Name(), err)
}
ns.closed = true
if ns.mounted {
if err := unix.Unmount(ns.file.Name(), unix.MNT_DETACH); err != nil {
return fmt.Errorf("Failed to unmount namespace %s: %v", ns.file.Name(), err)
}
if err := os.RemoveAll(ns.file.Name()); err != nil {
return fmt.Errorf("Failed to clean up namespace %s: %v", ns.file.Name(), err)
}
ns.mounted = false
}
return nil
}
func (ns *netNS) Do(toRun func(NetNS) error) error {
if err := ns.errorIfClosed(); err != nil {
return err
}
containedCall := func(hostNS NetNS) error {
threadNS, err := GetNS(getCurrentThreadNetNSPath())
if err != nil {
return fmt.Errorf("failed to open current netns: %v", err)
}
defer threadNS.Close()
// switch to target namespace
if err = ns.Set(); err != nil {
return fmt.Errorf("error switching to ns %v: %v", ns.file.Name(), err)
}
defer threadNS.Set() // switch back
return toRun(hostNS)
}
// save a handle to current network namespace
hostNS, err := GetNS(getCurrentThreadNetNSPath())
if err != nil {
return fmt.Errorf("Failed to open current namespace: %v", err)
}
defer hostNS.Close()
var wg sync.WaitGroup
wg.Add(1)
var innerError error
go func() {
defer wg.Done()
runtime.LockOSThread()
innerError = containedCall(hostNS)
}()
wg.Wait()
return innerError
}
func (ns *netNS) Set() error {
if err := ns.errorIfClosed(); err != nil {
return err
}
if _, _, err := unix.Syscall(unix.SYS_SETNS, ns.Fd(), uintptr(unix.CLONE_NEWNET), 0); err != 0 {
return fmt.Errorf("Error switching to ns %v: %v", ns.file.Name(), err)
}
return nil
}
// WithNetNSPath executes the passed closure under the given network
// namespace, restoring the original namespace afterwards.
func WithNetNSPath(nspath string, toRun func(NetNS) error) error {
ns, err := GetNS(nspath)
if err != nil {
return err
}
defer ns.Close()
return ns.Do(toRun)
}

View file

@ -0,0 +1,101 @@
// Copyright 2015 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 types
import (
"encoding"
"fmt"
"reflect"
"strings"
)
// UnmarshallableBool typedef for builtin bool
// because builtin type's methods can't be declared
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"
func (b *UnmarshallableBool) UnmarshalText(data []byte) error {
s := strings.ToLower(string(data))
switch s {
case "1", "true":
*b = true
case "0", "false":
*b = false
default:
return fmt.Errorf("Boolean unmarshal error: invalid input %s", s)
}
return nil
}
// UnmarshallableString typedef for builtin string
type UnmarshallableString string
// UnmarshalText implements the encoding.TextUnmarshaler interface.
// Returns the string
func (s *UnmarshallableString) UnmarshalText(data []byte) error {
*s = UnmarshallableString(data)
return nil
}
// CommonArgs contains the IgnoreUnknown argument
// and must be embedded by all Arg structs
type CommonArgs struct {
IgnoreUnknown UnmarshallableBool `json:"ignoreunknown,omitempty"`
}
// GetKeyField is a helper function to receive Values
// Values that represent a pointer to a struct
func GetKeyField(keyString string, v reflect.Value) reflect.Value {
return v.Elem().FieldByName(keyString)
}
// LoadArgs parses args from a string in the form "K=V;K2=V2;..."
func LoadArgs(args string, container interface{}) error {
if args == "" {
return nil
}
containerValue := reflect.ValueOf(container)
pairs := strings.Split(args, ";")
unknownArgs := []string{}
for _, pair := range pairs {
kv := strings.Split(pair, "=")
if len(kv) != 2 {
return fmt.Errorf("ARGS: invalid pair %q", pair)
}
keyString := kv[0]
valueString := kv[1]
keyField := GetKeyField(keyString, containerValue)
if !keyField.IsValid() {
unknownArgs = append(unknownArgs, pair)
continue
}
u := keyField.Addr().Interface().(encoding.TextUnmarshaler)
err := u.UnmarshalText([]byte(valueString))
if err != nil {
return fmt.Errorf("ARGS: error parsing value of pair %q: %v)", pair, err)
}
}
isIgnoreUnknown := GetKeyField("IgnoreUnknown", containerValue).Bool()
if len(unknownArgs) > 0 && !isIgnoreUnknown {
return fmt.Errorf("ARGS: unknown args %q", unknownArgs)
}
return nil
}

View file

@ -0,0 +1,201 @@
// Copyright 2015 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 types
import (
"encoding/json"
"fmt"
"net"
"os"
)
// like net.IPNet but adds JSON marshalling and unmarshalling
type IPNet net.IPNet
// ParseCIDR takes a string like "10.2.3.1/24" and
// return IPNet with "10.2.3.1" and /24 mask
func ParseCIDR(s string) (*net.IPNet, error) {
ip, ipn, err := net.ParseCIDR(s)
if err != nil {
return nil, err
}
ipn.IP = ip
return ipn, nil
}
func (n IPNet) MarshalJSON() ([]byte, error) {
return json.Marshal((*net.IPNet)(&n).String())
}
func (n *IPNet) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
tmp, err := ParseCIDR(s)
if err != nil {
return err
}
*n = IPNet(*tmp)
return nil
}
// NetConf describes a network.
type NetConf struct {
CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
IPAM struct {
Type string `json:"type,omitempty"`
} `json:"ipam,omitempty"`
DNS DNS `json:"dns"`
}
// Result is what gets returned from the plugin (via stdout) to the caller
type Result struct {
IP4 *IPConfig `json:"ip4,omitempty"`
IP6 *IPConfig `json:"ip6,omitempty"`
DNS DNS `json:"dns,omitempty"`
}
func (r *Result) Print() error {
return prettyPrint(r)
}
// String returns a formatted string in the form of "[IP4: $1,][ IP6: $2,] DNS: $3" where
// $1 represents the receiver's IPv4, $2 represents the receiver's IPv6 and $3 the
// receiver's DNS. If $1 or $2 are nil, they won't be present in the returned string.
func (r *Result) String() string {
var str string
if r.IP4 != nil {
str = fmt.Sprintf("IP4:%+v, ", *r.IP4)
}
if r.IP6 != nil {
str += fmt.Sprintf("IP6:%+v, ", *r.IP6)
}
return fmt.Sprintf("%sDNS:%+v", str, r.DNS)
}
// IPConfig contains values necessary to configure an interface
type IPConfig struct {
IP net.IPNet
Gateway net.IP
Routes []Route
}
// DNS contains values interesting for DNS resolvers
type DNS struct {
Nameservers []string `json:"nameservers,omitempty"`
Domain string `json:"domain,omitempty"`
Search []string `json:"search,omitempty"`
Options []string `json:"options,omitempty"`
}
type Route struct {
Dst net.IPNet
GW net.IP
}
// 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
)
type Error struct {
Code uint `json:"code"`
Msg string `json:"msg"`
Details string `json:"details,omitempty"`
}
func (e *Error) Error() string {
return e.Msg
}
func (e *Error) Print() error {
return prettyPrint(e)
}
// net.IPNet is not JSON (un)marshallable so this duality is needed
// for our custom IPNet type
// JSON (un)marshallable types
type ipConfig struct {
IP IPNet `json:"ip"`
Gateway net.IP `json:"gateway,omitempty"`
Routes []Route `json:"routes,omitempty"`
}
type route struct {
Dst IPNet `json:"dst"`
GW net.IP `json:"gw,omitempty"`
}
func (c *IPConfig) MarshalJSON() ([]byte, error) {
ipc := ipConfig{
IP: IPNet(c.IP),
Gateway: c.Gateway,
Routes: c.Routes,
}
return json.Marshal(ipc)
}
func (c *IPConfig) UnmarshalJSON(data []byte) error {
ipc := ipConfig{}
if err := json.Unmarshal(data, &ipc); err != nil {
return err
}
c.IP = net.IPNet(ipc.IP)
c.Gateway = ipc.Gateway
c.Routes = ipc.Routes
return nil
}
func (r *Route) UnmarshalJSON(data []byte) error {
rt := route{}
if err := json.Unmarshal(data, &rt); err != nil {
return err
}
r.Dst = net.IPNet(rt.Dst)
r.GW = rt.GW
return nil
}
func (r *Route) MarshalJSON() ([]byte, error) {
rt := route{
Dst: IPNet(r.Dst),
GW: r.GW,
}
return json.Marshal(rt)
}
func prettyPrint(obj interface{}) error {
data, err := json.MarshalIndent(obj, "", " ")
if err != nil {
return err
}
_, err = os.Stdout.Write(data)
return err
}

View file

@ -0,0 +1,37 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package version
import (
"encoding/json"
"fmt"
)
// ConfigDecoder can decode the CNI version available in network config data
type ConfigDecoder struct{}
func (*ConfigDecoder) Decode(jsonBytes []byte) (string, error) {
var conf struct {
CNIVersion string `json:"cniVersion"`
}
err := json.Unmarshal(jsonBytes, &conf)
if err != nil {
return "", fmt.Errorf("decoding version from network config: %s", err)
}
if conf.CNIVersion == "" {
return "0.1.0", nil
}
return conf.CNIVersion, nil
}

View file

@ -0,0 +1,78 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package version
import (
"encoding/json"
"fmt"
"io"
)
// PluginInfo reports information about CNI versioning
type PluginInfo interface {
// SupportedVersions returns one or more CNI spec versions that the plugin
// supports. If input is provided in one of these versions, then the plugin
// promises to use the same CNI version in its response
SupportedVersions() []string
// Encode writes this CNI version information as JSON to the given Writer
Encode(io.Writer) error
}
type pluginInfo struct {
CNIVersion_ string `json:"cniVersion"`
SupportedVersions_ []string `json:"supportedVersions,omitempty"`
}
func (p *pluginInfo) Encode(w io.Writer) error {
return json.NewEncoder(w).Encode(p)
}
func (p *pluginInfo) SupportedVersions() []string {
return p.SupportedVersions_
}
// PluginSupports returns a new PluginInfo that will report the given versions
// as supported
func PluginSupports(supportedVersions ...string) PluginInfo {
if len(supportedVersions) < 1 {
panic("programmer error: you must support at least one version")
}
return &pluginInfo{
CNIVersion_: Current(),
SupportedVersions_: supportedVersions,
}
}
// PluginDecoder can decode the response returned by a plugin's VERSION command
type PluginDecoder struct{}
func (*PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) {
var info pluginInfo
err := json.Unmarshal(jsonBytes, &info)
if err != nil {
return nil, fmt.Errorf("decoding version info: %s", err)
}
if info.CNIVersion_ == "" {
return nil, fmt.Errorf("decoding version info: missing field cniVersion")
}
if len(info.SupportedVersions_) == 0 {
if info.CNIVersion_ == "0.2.0" {
return PluginSupports("0.1.0", "0.2.0"), nil
}
return nil, fmt.Errorf("decoding version info: missing field supportedVersions")
}
return &info, nil
}

View file

@ -0,0 +1,47 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package version
import "fmt"
type ErrorIncompatible struct {
Config string
Plugin []string
}
func (e *ErrorIncompatible) Details() string {
return fmt.Sprintf("config is %q, plugin supports %q", e.Config, e.Plugin)
}
func (e *ErrorIncompatible) Error() string {
return fmt.Sprintf("incompatible CNI versions: %s", e.Details())
}
type Reconciler struct{}
func (*Reconciler) Check(configVersion string, pluginInfo PluginInfo) *ErrorIncompatible {
pluginVersions := pluginInfo.SupportedVersions()
for _, pluginVersion := range pluginVersions {
if configVersion == pluginVersion {
return nil
}
}
return &ErrorIncompatible{
Config: configVersion,
Plugin: pluginVersions,
}
}

View file

@ -0,0 +1,29 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package version
// Current reports the version of the CNI spec implemented by this library
func Current() string {
return "0.2.0"
}
// Legacy PluginInfo describes a plugin that is backwards compatible with the
// CNI spec version 0.1.0. In particular, a runtime compiled against the 0.1.0
// library ought to work correctly with a plugin that reports support for
// Legacy versions.
//
// Any future CNI spec versions which meet this definition should be added to
// this list.
var Legacy = PluginSupports("0.1.0", "0.2.0")