diff --git a/plugins/sample/README.md b/plugins/sample/README.md new file mode 100644 index 00000000..9bbd6e96 --- /dev/null +++ b/plugins/sample/README.md @@ -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 index 00000000..9f5b7d2a --- /dev/null +++ b/plugins/sample/main.go @@ -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())) +}