| // 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 { |
| if ip.Interface == nil { |
| continue |
| } |
| 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) |
| } |
| } |
| if len(containerIPs) == 0 { |
| return fmt.Errorf("got no container IPs") |
| } |
| |
| // 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())) |
| } |