sample: create sample plugin
authorCasey Callendrello <casey.Callendrello@coreos.com>
Mon, 13 Mar 2017 13:31:58 +0000 (14:31 +0100)
committerCasey Callendrello <casey.Callendrello@coreos.com>
Mon, 13 Mar 2017 13:31:58 +0000 (14:31 +0100)
plugins/sample/README.md [new file with mode: 0644]
plugins/sample/main.go [new file with mode: 0644]

diff --git a/plugins/sample/README.md b/plugins/sample/README.md
new file mode 100644 (file)
index 0000000..9bbd6e9
--- /dev/null
@@ -0,0 +1,7 @@
+# Sample CNI plugin
+
+This is an example of a sample chained plugin. It includes solutions for some
+of the more subtle cases that can be experienced with multi-version chained
+plugins.
+
+To use it, just add your code to the cmdAdd and cmdDel plugins.
diff --git a/plugins/sample/main.go b/plugins/sample/main.go
new file mode 100644 (file)
index 0000000..9f5b7d2
--- /dev/null
@@ -0,0 +1,139 @@
+// Copyright 2017 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.
+
+// This is a sample chained plugin that supports multiple CNI versions. It
+// parses prevResult according to the cniVersion
+package main
+
+import (
+       "encoding/json"
+       "fmt"
+       "net"
+
+       "github.com/containernetworking/cni/pkg/skel"
+       "github.com/containernetworking/cni/pkg/types"
+       "github.com/containernetworking/cni/pkg/types/current"
+       "github.com/containernetworking/cni/pkg/version"
+)
+
+// PluginConf is whatever you expect your configuration json to be. This is whatever
+// is passed in on stdin. Your plugin may wish to expose its functionality via
+// runtime args, see CONVENTIONS.md in the CNI spec.
+type PluginConf struct {
+       types.NetConf // You may wish to not nest this type
+       RuntimeConfig *struct {
+               SampleConfig map[string]interface{} `json:"sample"`
+       } `json:"runtimeConfig"`
+
+       // This is the previous result, when called in the context of a chained
+       // plugin. Because this plugin supports multiple versions, we'll have to
+       // parse this in two passes. If your plugin is not chained, this can be
+       // removed (though you may wish to error if a non-chainable plugin is
+       // chained.
+       // If you need to modify the result before returning it, you will need
+       // to actually convert it to a concrete versioned struct.
+       RawPrevResult *map[string]interface{} `json:"prevResult"`
+       PrevResult    *current.Result         `json:"-"`
+
+       // Add plugin-specifc flags here
+       MyAwesomeFlag     bool   `json:"myAwesomeFlag"`
+       AnotherAwesomeArg string `json:"anotherAwesomeArg"`
+}
+
+// parseConfig parses the supplied configuration (and prevResult) from stdin.
+func parseConfig(stdin []byte) (*PluginConf, error) {
+       conf := PluginConf{}
+
+       if err := json.Unmarshal(stdin, &conf); err != nil {
+               return nil, fmt.Errorf("failed to parse network configuration: %v", err)
+       }
+
+       // Parse previous result. Remove this if your plugin is not chained.
+       if conf.RawPrevResult != nil {
+               resultBytes, err := json.Marshal(conf.RawPrevResult)
+               if err != nil {
+                       return nil, fmt.Errorf("could not serialize prevResult: %v", err)
+               }
+               res, err := version.NewResult(conf.CNIVersion, resultBytes)
+               if err != nil {
+                       return nil, fmt.Errorf("could not parse prevResult: %v", err)
+               }
+               conf.RawPrevResult = nil
+               conf.PrevResult, err = current.NewResultFromResult(res)
+               if err != nil {
+                       return nil, fmt.Errorf("could not convert result to current version: %v", err)
+               }
+       }
+       // End previous result parsing
+
+       // Do any validation here
+       if conf.AnotherAwesomeArg == "" {
+               return nil, fmt.Errorf("anotherAwesomeArg must be specified")
+       }
+
+       return &conf, nil
+}
+
+// cmdAdd is called for ADD requests
+func cmdAdd(args *skel.CmdArgs) error {
+       conf, err := parseConfig(args.StdinData)
+       if err != nil {
+               return err
+       }
+
+       if conf.PrevResult == nil {
+               return fmt.Errorf("must be called as chained plugin")
+       }
+
+       // This is some sample code to generate the list of container-side IPs.
+       // We're casting the prevResult to a 0.3.0 response, which can also include
+       // host-side IPs (but doesn't when converted from a 0.2.0 response).
+       containerIPs := make([]net.IP, 0, len(conf.PrevResult.IPs))
+       if conf.CNIVersion != "0.3.0" {
+               for _, ip := range conf.PrevResult.IPs {
+                       containerIPs = append(containerIPs, ip.Address.IP)
+               }
+       } else {
+               for _, ip := range conf.PrevResult.IPs {
+                       intIdx := ip.Interface
+                       // Every IP is indexed in to the interfaces array, with "-1" standing
+                       // for an unknown interface (which we'll assume to be Container-side
+                       // Skip all IPs we know belong to an interface with the wrong name.
+                       if intIdx >= 0 && intIdx < len(conf.PrevResult.Interfaces) && conf.PrevResult.Interfaces[intIdx].Name != args.IfName {
+                               continue
+                       }
+                       containerIPs = append(containerIPs, ip.Address.IP)
+               }
+       }
+
+       // Pass through the result for the next plugin
+       return types.PrintResult(conf.PrevResult, conf.CNIVersion)
+}
+
+// cmdDel is called for DELETE requests
+func cmdDel(args *skel.CmdArgs) error {
+       conf, err := parseConfig(args.StdinData)
+       if err != nil {
+               return err
+       }
+       _ = conf
+
+       // Do your delete here
+
+       return nil
+}
+
+func main() {
+       skel.PluginMain(cmdAdd, cmdDel, version.PluginSupports("", "0.1.0", "0.2.0", version.Current()))
+}