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