github.com/john-lin/cni@v0.6.0-rc1.0.20170712150331-b69e640cc0e2/pkg/skel/skel.go (about) 1 // Copyright 2014-2016 CNI authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package skel provides skeleton code for a CNI plugin. 16 // In particular, it implements argument parsing and validation. 17 package skel 18 19 import ( 20 "fmt" 21 "io" 22 "io/ioutil" 23 "log" 24 "os" 25 26 "github.com/containernetworking/cni/pkg/types" 27 "github.com/containernetworking/cni/pkg/version" 28 ) 29 30 // CmdArgs captures all the arguments passed in to the plugin 31 // via both env vars and stdin 32 type CmdArgs struct { 33 ContainerID string 34 Netns string 35 IfName string 36 Args string 37 Path string 38 StdinData []byte 39 } 40 41 type dispatcher struct { 42 Getenv func(string) string 43 Stdin io.Reader 44 Stdout io.Writer 45 Stderr io.Writer 46 47 ConfVersionDecoder version.ConfigDecoder 48 VersionReconciler version.Reconciler 49 } 50 51 type reqForCmdEntry map[string]bool 52 53 func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) { 54 var cmd, contID, netns, ifName, args, path string 55 56 vars := []struct { 57 name string 58 val *string 59 reqForCmd reqForCmdEntry 60 }{ 61 { 62 "CNI_COMMAND", 63 &cmd, 64 reqForCmdEntry{ 65 "ADD": true, 66 "DEL": true, 67 }, 68 }, 69 { 70 "CNI_CONTAINERID", 71 &contID, 72 reqForCmdEntry{ 73 "ADD": false, 74 "DEL": false, 75 }, 76 }, 77 { 78 "CNI_NETNS", 79 &netns, 80 reqForCmdEntry{ 81 "ADD": true, 82 "DEL": false, 83 }, 84 }, 85 { 86 "CNI_IFNAME", 87 &ifName, 88 reqForCmdEntry{ 89 "ADD": true, 90 "DEL": true, 91 }, 92 }, 93 { 94 "CNI_ARGS", 95 &args, 96 reqForCmdEntry{ 97 "ADD": false, 98 "DEL": false, 99 }, 100 }, 101 { 102 "CNI_PATH", 103 &path, 104 reqForCmdEntry{ 105 "ADD": true, 106 "DEL": true, 107 }, 108 }, 109 } 110 111 argsMissing := false 112 for _, v := range vars { 113 *v.val = t.Getenv(v.name) 114 if *v.val == "" { 115 if v.reqForCmd[cmd] || v.name == "CNI_COMMAND" { 116 fmt.Fprintf(t.Stderr, "%v env variable missing\n", v.name) 117 argsMissing = true 118 } 119 } 120 } 121 122 if argsMissing { 123 return "", nil, fmt.Errorf("required env variables missing") 124 } 125 126 stdinData, err := ioutil.ReadAll(t.Stdin) 127 if err != nil { 128 return "", nil, fmt.Errorf("error reading from stdin: %v", err) 129 } 130 131 cmdArgs := &CmdArgs{ 132 ContainerID: contID, 133 Netns: netns, 134 IfName: ifName, 135 Args: args, 136 Path: path, 137 StdinData: stdinData, 138 } 139 return cmd, cmdArgs, nil 140 } 141 142 func createTypedError(f string, args ...interface{}) *types.Error { 143 return &types.Error{ 144 Code: 100, 145 Msg: fmt.Sprintf(f, args...), 146 } 147 } 148 149 func (t *dispatcher) checkVersionAndCall(cmdArgs *CmdArgs, pluginVersionInfo version.PluginInfo, toCall func(*CmdArgs) error) error { 150 configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData) 151 if err != nil { 152 return err 153 } 154 verErr := t.VersionReconciler.Check(configVersion, pluginVersionInfo) 155 if verErr != nil { 156 return &types.Error{ 157 Code: types.ErrIncompatibleCNIVersion, 158 Msg: "incompatible CNI versions", 159 Details: verErr.Details(), 160 } 161 } 162 return toCall(cmdArgs) 163 } 164 165 func (t *dispatcher) pluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) *types.Error { 166 cmd, cmdArgs, err := t.getCmdArgsFromEnv() 167 if err != nil { 168 return createTypedError(err.Error()) 169 } 170 171 switch cmd { 172 case "ADD": 173 err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdAdd) 174 case "DEL": 175 err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdDel) 176 case "VERSION": 177 err = versionInfo.Encode(t.Stdout) 178 default: 179 return createTypedError("unknown CNI_COMMAND: %v", cmd) 180 } 181 182 if err != nil { 183 if e, ok := err.(*types.Error); ok { 184 // don't wrap Error in Error 185 return e 186 } 187 return createTypedError(err.Error()) 188 } 189 return nil 190 } 191 192 // PluginMainWithError is the core "main" for a plugin. It accepts 193 // callback functions for add and del CNI commands and returns an error. 194 // 195 // The caller must also specify what CNI spec versions the plugin supports. 196 // 197 // It is the responsibility of the caller to check for non-nil error return. 198 // 199 // For a plugin to comply with the CNI spec, it must print any error to stdout 200 // as JSON and then exit with nonzero status code. 201 // 202 // To let this package automatically handle errors and call os.Exit(1) for you, 203 // use PluginMain() instead. 204 func PluginMainWithError(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) *types.Error { 205 return (&dispatcher{ 206 Getenv: os.Getenv, 207 Stdin: os.Stdin, 208 Stdout: os.Stdout, 209 Stderr: os.Stderr, 210 }).pluginMain(cmdAdd, cmdDel, versionInfo) 211 } 212 213 // PluginMain is the core "main" for a plugin which includes automatic error handling. 214 // 215 // The caller must also specify what CNI spec versions the plugin supports. 216 // 217 // When an error occurs in either cmdAdd or cmdDel, PluginMain will print the error 218 // as JSON to stdout and call os.Exit(1). 219 // 220 // To have more control over error handling, use PluginMainWithError() instead. 221 func PluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) { 222 if e := PluginMainWithError(cmdAdd, cmdDel, versionInfo); e != nil { 223 if err := e.Print(); err != nil { 224 log.Print("Error writing error JSON to stdout: ", err) 225 } 226 os.Exit(1) 227 } 228 }