github.com/brandond/cni@v0.8.1/pkg/skel/skel_test.go (about)

     1  // Copyright 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
    16  
    17  import (
    18  	"bytes"
    19  	"errors"
    20  	"fmt"
    21  	"strings"
    22  
    23  	"github.com/containernetworking/cni/pkg/types"
    24  	"github.com/containernetworking/cni/pkg/types/current"
    25  	"github.com/containernetworking/cni/pkg/version"
    26  
    27  	. "github.com/onsi/ginkgo"
    28  	. "github.com/onsi/ginkgo/extensions/table"
    29  	. "github.com/onsi/gomega"
    30  )
    31  
    32  type fakeCmd struct {
    33  	CallCount int
    34  	Returns   struct {
    35  		Error error
    36  	}
    37  	Received struct {
    38  		CmdArgs *CmdArgs
    39  	}
    40  }
    41  
    42  func (c *fakeCmd) Func(args *CmdArgs) error {
    43  	c.CallCount++
    44  	c.Received.CmdArgs = args
    45  	return c.Returns.Error
    46  }
    47  
    48  var _ = Describe("dispatching to the correct callback", func() {
    49  	var (
    50  		environment              map[string]string
    51  		stdinData                string
    52  		stdout, stderr           *bytes.Buffer
    53  		cmdAdd, cmdCheck, cmdDel *fakeCmd
    54  		dispatch                 *dispatcher
    55  		expectedCmdArgs          *CmdArgs
    56  		versionInfo              version.PluginInfo
    57  	)
    58  
    59  	BeforeEach(func() {
    60  		environment = map[string]string{
    61  			"CNI_COMMAND":     "ADD",
    62  			"CNI_CONTAINERID": "some-container-id",
    63  			"CNI_NETNS":       "/some/netns/path",
    64  			"CNI_IFNAME":      "eth0",
    65  			"CNI_ARGS":        "some;extra;args",
    66  			"CNI_PATH":        "/some/cni/path",
    67  		}
    68  
    69  		stdinData = `{ "name":"skel-test", "some": "config", "cniVersion": "9.8.7" }`
    70  		stdout = &bytes.Buffer{}
    71  		stderr = &bytes.Buffer{}
    72  		versionInfo = version.PluginSupports("9.8.7")
    73  		dispatch = &dispatcher{
    74  			Getenv: func(key string) string { return environment[key] },
    75  			Stdin:  strings.NewReader(stdinData),
    76  			Stdout: stdout,
    77  			Stderr: stderr,
    78  		}
    79  		cmdAdd = &fakeCmd{}
    80  		cmdCheck = &fakeCmd{}
    81  		cmdDel = &fakeCmd{}
    82  		expectedCmdArgs = &CmdArgs{
    83  			ContainerID: "some-container-id",
    84  			Netns:       "/some/netns/path",
    85  			IfName:      "eth0",
    86  			Args:        "some;extra;args",
    87  			Path:        "/some/cni/path",
    88  			StdinData:   []byte(stdinData),
    89  		}
    90  	})
    91  
    92  	var envVarChecker = func(envVar string, isRequired bool) {
    93  		delete(environment, envVar)
    94  
    95  		err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
    96  		if isRequired {
    97  			Expect(err).To(Equal(&types.Error{
    98  				Code: types.ErrInvalidEnvironmentVariables,
    99  				Msg:  "required env variables [" + envVar + "] missing",
   100  			}))
   101  		} else {
   102  			Expect(err).NotTo(HaveOccurred())
   103  		}
   104  	}
   105  
   106  	Context("when the CNI_COMMAND is ADD", func() {
   107  		It("extracts env vars and stdin data and calls cmdAdd", func() {
   108  			err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   109  
   110  			Expect(err).NotTo(HaveOccurred())
   111  			Expect(cmdAdd.CallCount).To(Equal(1))
   112  			Expect(cmdCheck.CallCount).To(Equal(0))
   113  			Expect(cmdDel.CallCount).To(Equal(0))
   114  			Expect(cmdAdd.Received.CmdArgs).To(Equal(expectedCmdArgs))
   115  		})
   116  
   117  		It("returns an error when containerID has invalid characters", func() {
   118  			environment["CNI_CONTAINERID"] = "some-%%container-id"
   119  			err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   120  			Expect(err).To(HaveOccurred())
   121  			Expect(err).To(Equal(&types.Error{
   122  				Code:    types.ErrInvalidEnvironmentVariables,
   123  				Msg:     "invalid characters in containerID",
   124  				Details: "some-%%container-id",
   125  			}))
   126  		})
   127  
   128  		Context("return errors when interface name is invalid", func() {
   129  			It("interface name is too long", func() {
   130  				environment["CNI_IFNAME"] = "1234567890123456"
   131  
   132  				err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   133  				Expect(err).To(HaveOccurred())
   134  				Expect(err).To(Equal(&types.Error{
   135  					Code:    types.ErrInvalidEnvironmentVariables,
   136  					Msg:     "interface name is too long",
   137  					Details: "interface name should be less than 16 characters",
   138  				}))
   139  			})
   140  
   141  			It("interface name is .", func() {
   142  				environment["CNI_IFNAME"] = "."
   143  
   144  				err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   145  				Expect(err).To(HaveOccurred())
   146  				Expect(err).To(Equal(&types.Error{
   147  					Code:    types.ErrInvalidEnvironmentVariables,
   148  					Msg:     "interface name is . or ..",
   149  					Details: "",
   150  				}))
   151  			})
   152  
   153  			It("interface name is ..", func() {
   154  				environment["CNI_IFNAME"] = ".."
   155  
   156  				err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   157  				Expect(err).To(HaveOccurred())
   158  				Expect(err).To(Equal(&types.Error{
   159  					Code:    types.ErrInvalidEnvironmentVariables,
   160  					Msg:     "interface name is . or ..",
   161  					Details: "",
   162  				}))
   163  			})
   164  
   165  			It("interface name contains invalid characters /", func() {
   166  				environment["CNI_IFNAME"] = "test/test"
   167  
   168  				err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   169  				Expect(err).To(HaveOccurred())
   170  				Expect(err).To(Equal(&types.Error{
   171  					Code:    types.ErrInvalidEnvironmentVariables,
   172  					Msg:     "interface name contains / or : or whitespace characters",
   173  					Details: "",
   174  				}))
   175  			})
   176  
   177  			It("interface name contains invalid characters :", func() {
   178  				environment["CNI_IFNAME"] = "test:test"
   179  
   180  				err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   181  				Expect(err).To(HaveOccurred())
   182  				Expect(err).To(Equal(&types.Error{
   183  					Code:    types.ErrInvalidEnvironmentVariables,
   184  					Msg:     "interface name contains / or : or whitespace characters",
   185  					Details: "",
   186  				}))
   187  			})
   188  
   189  			It("interface name contains invalid characters whitespace", func() {
   190  				environment["CNI_IFNAME"] = "test test"
   191  
   192  				err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   193  				Expect(err).To(HaveOccurred())
   194  				Expect(err).To(Equal(&types.Error{
   195  					Code:    types.ErrInvalidEnvironmentVariables,
   196  					Msg:     "interface name contains / or : or whitespace characters",
   197  					Details: "",
   198  				}))
   199  			})
   200  		})
   201  
   202  		It("does not call cmdCheck or cmdDel", func() {
   203  			err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   204  
   205  			Expect(err).NotTo(HaveOccurred())
   206  			Expect(cmdCheck.CallCount).To(Equal(0))
   207  			Expect(cmdDel.CallCount).To(Equal(0))
   208  		})
   209  
   210  		DescribeTable("required / optional env vars", envVarChecker,
   211  			Entry("command", "CNI_COMMAND", true),
   212  			Entry("container id", "CNI_CONTAINERID", true),
   213  			Entry("net ns", "CNI_NETNS", true),
   214  			Entry("if name", "CNI_IFNAME", true),
   215  			Entry("args", "CNI_ARGS", false),
   216  			Entry("path", "CNI_PATH", true),
   217  		)
   218  
   219  		Context("when multiple required env vars are missing", func() {
   220  			BeforeEach(func() {
   221  				delete(environment, "CNI_NETNS")
   222  				delete(environment, "CNI_IFNAME")
   223  				delete(environment, "CNI_PATH")
   224  			})
   225  
   226  			It("reports that all of them are missing, not just the first", func() {
   227  				err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   228  				Expect(err).To(HaveOccurred())
   229  
   230  				Expect(err).To(Equal(&types.Error{
   231  					Code: types.ErrInvalidEnvironmentVariables,
   232  					Msg:  "required env variables [CNI_NETNS,CNI_IFNAME,CNI_PATH] missing",
   233  				}))
   234  			})
   235  		})
   236  
   237  		Context("when the stdin data is missing the required cniVersion config", func() {
   238  			BeforeEach(func() {
   239  				dispatch.Stdin = strings.NewReader(`{ "name": "skel-test", "some": "config" }`)
   240  			})
   241  
   242  			Context("when the plugin supports version 0.1.0", func() {
   243  				BeforeEach(func() {
   244  					versionInfo = version.PluginSupports("0.1.0")
   245  					expectedCmdArgs.StdinData = []byte(`{ "name": "skel-test", "some": "config" }`)
   246  				})
   247  
   248  				It("infers the config is 0.1.0 and calls the cmdAdd callback", func() {
   249  
   250  					err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   251  					Expect(err).NotTo(HaveOccurred())
   252  
   253  					Expect(cmdAdd.CallCount).To(Equal(1))
   254  					Expect(cmdAdd.Received.CmdArgs).To(Equal(expectedCmdArgs))
   255  				})
   256  			})
   257  
   258  			Context("when the plugin does not support 0.1.0", func() {
   259  				BeforeEach(func() {
   260  					versionInfo = version.PluginSupports("4.3.2")
   261  				})
   262  
   263  				It("immediately returns a useful error", func() {
   264  					err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   265  					Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
   266  					Expect(err.Msg).To(Equal("incompatible CNI versions"))
   267  					Expect(err.Details).To(Equal(`config is "0.1.0", plugin supports ["4.3.2"]`))
   268  				})
   269  
   270  				It("does not call either callback", func() {
   271  					dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   272  					Expect(cmdAdd.CallCount).To(Equal(0))
   273  					Expect(cmdCheck.CallCount).To(Equal(0))
   274  					Expect(cmdDel.CallCount).To(Equal(0))
   275  				})
   276  			})
   277  		})
   278  	})
   279  
   280  	Context("when the CNI_COMMAND is CHECK", func() {
   281  		BeforeEach(func() {
   282  			environment["CNI_COMMAND"] = "CHECK"
   283  		})
   284  
   285  		It("extracts env vars and stdin data and calls cmdCheck", func() {
   286  			err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   287  
   288  			Expect(err).NotTo(HaveOccurred())
   289  			Expect(cmdAdd.CallCount).To(Equal(0))
   290  			Expect(cmdCheck.CallCount).To(Equal(1))
   291  			Expect(cmdDel.CallCount).To(Equal(0))
   292  			Expect(cmdCheck.Received.CmdArgs).To(Equal(expectedCmdArgs))
   293  		})
   294  
   295  		It("does not call cmdAdd or cmdDel", func() {
   296  			err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   297  
   298  			Expect(err).NotTo(HaveOccurred())
   299  			Expect(cmdAdd.CallCount).To(Equal(0))
   300  			Expect(cmdDel.CallCount).To(Equal(0))
   301  		})
   302  
   303  		DescribeTable("required / optional env vars", envVarChecker,
   304  			Entry("command", "CNI_COMMAND", true),
   305  			Entry("container id", "CNI_CONTAINERID", true),
   306  			Entry("net ns", "CNI_NETNS", true),
   307  			Entry("if name", "CNI_IFNAME", true),
   308  			Entry("args", "CNI_ARGS", false),
   309  			Entry("path", "CNI_PATH", true),
   310  		)
   311  
   312  		Context("when multiple required env vars are missing", func() {
   313  			BeforeEach(func() {
   314  				delete(environment, "CNI_NETNS")
   315  				delete(environment, "CNI_IFNAME")
   316  				delete(environment, "CNI_PATH")
   317  			})
   318  
   319  			It("reports that all of them are missing, not just the first", func() {
   320  				err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   321  				Expect(err).To(HaveOccurred())
   322  
   323  				Expect(err).To(Equal(&types.Error{
   324  					Code: types.ErrInvalidEnvironmentVariables,
   325  					Msg:  "required env variables [CNI_NETNS,CNI_IFNAME,CNI_PATH] missing",
   326  				}))
   327  			})
   328  		})
   329  
   330  		Context("when cniVersion is less than 0.4.0", func() {
   331  			It("immediately returns a useful error", func() {
   332  				dispatch.Stdin = strings.NewReader(`{ "name": "skel-test", "cniVersion": "0.3.0", "some": "config" }`)
   333  				err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   334  				Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
   335  				Expect(err.Msg).To(Equal("config version does not allow CHECK"))
   336  				Expect(cmdAdd.CallCount).To(Equal(0))
   337  				Expect(cmdCheck.CallCount).To(Equal(0))
   338  				Expect(cmdDel.CallCount).To(Equal(0))
   339  			})
   340  		})
   341  
   342  		Context("when plugin does not support 0.4.0", func() {
   343  			It("immediately returns a useful error", func() {
   344  				dispatch.Stdin = strings.NewReader(`{ "name": "skel-test", "cniVersion": "0.4.0", "some": "config" }`)
   345  				versionInfo = version.PluginSupports("0.1.0", "0.2.0", "0.3.0")
   346  				err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   347  				Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
   348  				Expect(err.Msg).To(Equal("plugin version does not allow CHECK"))
   349  				Expect(cmdAdd.CallCount).To(Equal(0))
   350  				Expect(cmdCheck.CallCount).To(Equal(0))
   351  				Expect(cmdDel.CallCount).To(Equal(0))
   352  			})
   353  		})
   354  
   355  		Context("when the config has a bad version", func() {
   356  			It("immediately returns a useful error", func() {
   357  				dispatch.Stdin = strings.NewReader(`{ "cniVersion": "adsfsadf", "some": "config", "name": "test" }`)
   358  				versionInfo = version.PluginSupports("0.1.0", "0.2.0", "0.3.0")
   359  				err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   360  				Expect(err.Code).To(Equal(uint(types.ErrDecodingFailure)))
   361  				Expect(cmdAdd.CallCount).To(Equal(0))
   362  				Expect(cmdCheck.CallCount).To(Equal(0))
   363  				Expect(cmdDel.CallCount).To(Equal(0))
   364  			})
   365  		})
   366  
   367  		Context("when the config has a bad name", func() {
   368  			It("immediately returns invalid network config", func() {
   369  				dispatch.Stdin = strings.NewReader(`{ "cniVersion": "0.4.0", "some": "config", "name": "te%%st" }`)
   370  				versionInfo = version.PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.4.0")
   371  				err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   372  				Expect(err.Code).To(Equal(uint(types.ErrInvalidNetworkConfig)))
   373  				Expect(cmdAdd.CallCount).To(Equal(0))
   374  				Expect(cmdCheck.CallCount).To(Equal(0))
   375  				Expect(cmdDel.CallCount).To(Equal(0))
   376  			})
   377  		})
   378  
   379  		Context("when the plugin has a bad version", func() {
   380  			It("immediately returns a useful error", func() {
   381  				dispatch.Stdin = strings.NewReader(`{ "cniVersion": "0.4.0", "some": "config", "name": "test" }`)
   382  				versionInfo = version.PluginSupports("0.1.0", "0.2.0", "adsfasdf")
   383  				err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   384  				Expect(err.Code).To(Equal(uint(types.ErrDecodingFailure)))
   385  				Expect(cmdAdd.CallCount).To(Equal(0))
   386  				Expect(cmdCheck.CallCount).To(Equal(0))
   387  				Expect(cmdDel.CallCount).To(Equal(0))
   388  			})
   389  		})
   390  	})
   391  
   392  	Context("when the CNI_COMMAND is DEL", func() {
   393  		BeforeEach(func() {
   394  			environment["CNI_COMMAND"] = "DEL"
   395  		})
   396  
   397  		It("calls cmdDel with the env vars and stdin data", func() {
   398  			err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   399  
   400  			Expect(err).NotTo(HaveOccurred())
   401  			Expect(cmdDel.CallCount).To(Equal(1))
   402  			Expect(cmdDel.Received.CmdArgs).To(Equal(expectedCmdArgs))
   403  		})
   404  
   405  		It("does not call cmdAdd", func() {
   406  			err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   407  
   408  			Expect(err).NotTo(HaveOccurred())
   409  			Expect(cmdAdd.CallCount).To(Equal(0))
   410  		})
   411  
   412  		DescribeTable("required / optional env vars", envVarChecker,
   413  			Entry("command", "CNI_COMMAND", true),
   414  			Entry("container id", "CNI_CONTAINERID", true),
   415  			Entry("net ns", "CNI_NETNS", false),
   416  			Entry("if name", "CNI_IFNAME", true),
   417  			Entry("args", "CNI_ARGS", false),
   418  			Entry("path", "CNI_PATH", true),
   419  		)
   420  	})
   421  
   422  	Context("when the CNI_COMMAND is VERSION", func() {
   423  		BeforeEach(func() {
   424  			environment["CNI_COMMAND"] = "VERSION"
   425  		})
   426  
   427  		It("prints the version to stdout", func() {
   428  			err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   429  
   430  			Expect(err).NotTo(HaveOccurred())
   431  			Expect(stdout).To(MatchJSON(fmt.Sprintf(`{
   432  				"cniVersion": "%s",
   433  				"supportedVersions": ["9.8.7"]
   434  			}`, current.ImplementedSpecVersion)))
   435  		})
   436  
   437  		It("does not call cmdAdd or cmdDel", func() {
   438  			err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   439  
   440  			Expect(err).NotTo(HaveOccurred())
   441  			Expect(cmdAdd.CallCount).To(Equal(0))
   442  			Expect(cmdDel.CallCount).To(Equal(0))
   443  		})
   444  
   445  		DescribeTable("VERSION does not need the usual env vars", envVarChecker,
   446  			Entry("command", "CNI_COMMAND", true),
   447  			Entry("container id", "CNI_CONTAINERID", false),
   448  			Entry("net ns", "CNI_NETNS", false),
   449  			Entry("if name", "CNI_IFNAME", false),
   450  			Entry("args", "CNI_ARGS", false),
   451  			Entry("path", "CNI_PATH", false),
   452  		)
   453  
   454  		It("does not read from Stdin", func() {
   455  			r := &BadReader{}
   456  			dispatch.Stdin = r
   457  
   458  			err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   459  
   460  			Expect(err).NotTo(HaveOccurred())
   461  			Expect(r.ReadCount).To(Equal(0))
   462  			Expect(stdout).To(MatchJSON(fmt.Sprintf(`{
   463  				"cniVersion": "%s",
   464  				"supportedVersions": ["9.8.7"]
   465  			}`, current.ImplementedSpecVersion)))
   466  		})
   467  	})
   468  
   469  	Context("when the CNI_COMMAND is unrecognized", func() {
   470  		BeforeEach(func() {
   471  			environment["CNI_COMMAND"] = "NOPE"
   472  		})
   473  
   474  		It("does not call any cmd callback", func() {
   475  			dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   476  
   477  			Expect(cmdAdd.CallCount).To(Equal(0))
   478  			Expect(cmdDel.CallCount).To(Equal(0))
   479  		})
   480  
   481  		It("returns an error", func() {
   482  			err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   483  
   484  			Expect(err).To(Equal(&types.Error{
   485  				Code: types.ErrInvalidEnvironmentVariables,
   486  				Msg:  "unknown CNI_COMMAND: NOPE",
   487  			}))
   488  		})
   489  
   490  		It("prints the about string when the command is blank", func() {
   491  			environment["CNI_COMMAND"] = ""
   492  			dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "test framework v42")
   493  			Expect(stderr.String()).To(ContainSubstring("test framework v42"))
   494  		})
   495  	})
   496  
   497  	Context("when the CNI_COMMAND is missing", func() {
   498  		It("prints the about string to stderr", func() {
   499  			environment = map[string]string{}
   500  			err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "AWESOME PLUGIN")
   501  			Expect(err).NotTo(HaveOccurred())
   502  
   503  			Expect(cmdAdd.CallCount).To(Equal(0))
   504  			Expect(cmdDel.CallCount).To(Equal(0))
   505  			log := stderr.String()
   506  			Expect(log).To(Equal("AWESOME PLUGIN\n"))
   507  		})
   508  
   509  		It("fails if there is no about string", func() {
   510  			environment = map[string]string{}
   511  			err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   512  			Expect(err).To(HaveOccurred())
   513  
   514  			Expect(cmdAdd.CallCount).To(Equal(0))
   515  			Expect(cmdDel.CallCount).To(Equal(0))
   516  			Expect(err).To(Equal(&types.Error{
   517  				Code: types.ErrInvalidEnvironmentVariables,
   518  				Msg:  "required env variables [CNI_COMMAND] missing",
   519  			}))
   520  		})
   521  	})
   522  
   523  	Context("when stdin cannot be read", func() {
   524  		BeforeEach(func() {
   525  			dispatch.Stdin = &BadReader{}
   526  		})
   527  
   528  		It("does not call any cmd callback", func() {
   529  			dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   530  
   531  			Expect(cmdAdd.CallCount).To(Equal(0))
   532  			Expect(cmdDel.CallCount).To(Equal(0))
   533  		})
   534  
   535  		It("wraps and returns the error", func() {
   536  			err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   537  
   538  			Expect(err).To(Equal(&types.Error{
   539  				Code: types.ErrIOFailure,
   540  				Msg:  "error reading from stdin: banana",
   541  			}))
   542  		})
   543  	})
   544  
   545  	Context("when the callback returns an error", func() {
   546  		Context("when it is a typed Error", func() {
   547  			BeforeEach(func() {
   548  				cmdAdd.Returns.Error = &types.Error{
   549  					Code: 1234,
   550  					Msg:  "insufficient something",
   551  				}
   552  			})
   553  
   554  			It("returns the error as-is", func() {
   555  				err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   556  
   557  				Expect(err).To(Equal(&types.Error{
   558  					Code: 1234,
   559  					Msg:  "insufficient something",
   560  				}))
   561  			})
   562  		})
   563  
   564  		Context("when it is an unknown error", func() {
   565  			BeforeEach(func() {
   566  				cmdAdd.Returns.Error = errors.New("potato")
   567  			})
   568  
   569  			It("wraps and returns the error", func() {
   570  				err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
   571  
   572  				Expect(err).To(Equal(&types.Error{
   573  					Code: types.ErrInternal,
   574  					Msg:  "potato",
   575  				}))
   576  			})
   577  		})
   578  	})
   579  })
   580  
   581  // BadReader is an io.Reader which always errors
   582  type BadReader struct {
   583  	Error     error
   584  	ReadCount int
   585  }
   586  
   587  func (r *BadReader) Read(buffer []byte) (int, error) {
   588  	r.ReadCount++
   589  	if r.Error != nil {
   590  		return 0, r.Error
   591  	}
   592  	return 0, errors.New("banana")
   593  }
   594  
   595  func (r *BadReader) Close() error {
   596  	return nil
   597  }