github.com/rkt/rkt@v1.30.1-0.20200224141603-171c416fac02/tests/cniproxy/main.go (about) 1 // Copyright 2016 The rkt 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 main 16 17 // cniproxy proxies commands through to the real container plugin 18 // and logs output for later inspection. 19 20 // For an example execution, see NewNetCNIDNSTest 21 22 // The following CNI arguments influence its behavior: 23 // 24 // X_REAL_PLUGIN=<<name>>: The name of the real plugin to execute 25 // X_ADD_DNS=1: populate a DNS response 26 // X_FAIL=exit / crash: "exit" = fail with error message, "crash" = fail without error message 27 // X_LOG=<<filename>>: write a logfile of what happens in the same directory as the binary 28 29 import ( 30 "bytes" 31 "encoding/json" 32 "fmt" 33 "io" 34 "os" 35 "os/exec" 36 "path/filepath" 37 "strings" 38 "syscall" 39 40 "github.com/containernetworking/cni/pkg/invoke" 41 ) 42 43 var cniArgs map[string]string 44 45 func main() { 46 parseCniArgs() 47 48 x_fail := cniArgs["X_FAIL"] 49 if x_fail == "exit" { 50 os.Stdout.WriteString(`{"cniVersion": "0.1.0", "code": 100, "msg": "this is a failure message"}`) 51 os.Exit(254) 52 } else if x_fail == "crash" { 53 os.Exit(254) 54 } 55 56 realPluginPath := findRealPlugin() 57 58 stdin, plugout, plugerr, exitCode := proxyPlugin(realPluginPath) 59 logResult(realPluginPath, stdin, plugout, plugerr, exitCode) 60 61 // Mutate the response 62 if exitCode == 0 && cniArgs["X_ADD_DNS"] == "1" { 63 addDns(plugout) 64 } 65 66 // Pass through the response to rkt 67 os.Stdout.Write(plugout.Bytes()) 68 os.Stderr.Write(plugerr.Bytes()) 69 70 os.Exit(exitCode) // pass through exit code 71 } 72 73 // mutate the response to add DNS info 74 func addDns(data *bytes.Buffer) { 75 var result interface{} 76 77 err := json.Unmarshal(data.Bytes(), &result) 78 if err != nil { 79 fail("could not parse json", err) 80 } 81 82 switch result := result.(type) { 83 case map[string]interface{}: 84 result["dns"] = map[string]interface{}{ 85 "nameservers": []string{"1.2.3.4", "4.3.2.1"}, 86 "domain": "dotawesome.awesome", 87 "search": []string{"cniproxy1, cniproxy2"}, 88 "options": []string{"option1", "option2"}, 89 } 90 newbytes, err := json.Marshal(result) 91 if err != nil { 92 fail("Failed to write json", err) 93 } 94 95 data.Reset() 96 data.Write(newbytes) 97 98 default: 99 fail("json not expected format") 100 } 101 } 102 103 func logResult(pluginPath string, stdin, stdout, stderr *bytes.Buffer, exitCode int) { 104 logfile, exists := cniArgs["X_LOG"] 105 if !exists { 106 return 107 } 108 109 logfile = filepath.Join(filepath.Dir(os.Args[0]), logfile) 110 fp, err := os.Create(logfile) 111 if err != nil { 112 fail("Could not open log file", logfile, err) 113 } 114 defer fp.Close() 115 116 result := map[string]interface{}{ 117 "pluginPath": pluginPath, 118 "stdin": stdin.String(), 119 "stdout": stdout.String(), 120 "stderr": stderr.String(), 121 "exitCode": exitCode, 122 "env": os.Environ(), 123 } 124 125 enc := json.NewEncoder(fp) 126 err = enc.Encode(result) 127 if err != nil { 128 fail("Could not write logfile", err) 129 } 130 } 131 132 // Proxy execution through the **real** plugin 133 func proxyPlugin(path string) (stdin, stdout, stderr *bytes.Buffer, exitCode int) { 134 135 // Filter all X_ arguments from the cni argument variable 136 filteredArgs := make([]string, 0, len(cniArgs)) 137 for k, v := range cniArgs { 138 if !strings.HasPrefix(k, "X_") { 139 filteredArgs = append(filteredArgs, fmt.Sprintf("%s=%s", k, v)) 140 } 141 } 142 os.Setenv("CNI_ARGS", strings.Join(filteredArgs, ";")) 143 144 stdin = bytes.NewBuffer([]byte{}) 145 stdout = bytes.NewBuffer([]byte{}) 146 stderr = bytes.NewBuffer([]byte{}) 147 148 teeIn := io.TeeReader(os.Stdin, stdin) 149 150 cmd := exec.Command(path) 151 cmd.Stdin = teeIn 152 cmd.Stdout = stdout 153 cmd.Stderr = stderr 154 155 exitStatus := cmd.Run() 156 157 if exitStatus == nil { 158 exitCode = 0 159 } else { 160 switch exitStatus := exitStatus.(type) { 161 case *exec.ExitError: 162 exitCode = exitStatus.Sys().(syscall.WaitStatus).ExitStatus() 163 default: 164 fail("exec", exitStatus) 165 } 166 } 167 return 168 } 169 170 // Find the proxied plugin from the CNI_PATH environment var 171 func findRealPlugin() string { 172 pluginName := getArgOrFail("X_REAL_PLUGIN") 173 paths := strings.Split(os.Getenv("CNI_PATH"), ":") 174 175 pluginPath, err := invoke.FindInPath(pluginName, paths) 176 177 if err != nil { 178 fail("Could not find plugin in CNI_PATH", err) 179 } 180 return pluginPath 181 } 182 183 func fail(msgs ...interface{}) { 184 fmt.Fprintln(os.Stderr, append([]interface{}{"CNIPROXY fail:"}, msgs...)...) 185 os.Exit(254) 186 } 187 188 func getArgOrFail(key string) string { 189 val, exists := cniArgs[key] 190 191 if !exists { 192 fail("Needed CNI_ARG", key) 193 } 194 return val 195 } 196 197 func parseCniArgs() { 198 cniArgs = make(map[string]string) 199 200 argStr := os.Getenv("CNI_ARGS") 201 202 for _, arg := range strings.Split(argStr, ";") { 203 argkv := strings.Split(arg, "=") 204 if len(argkv) != 2 { 205 fail("Invalid CNI arg", arg) 206 } 207 cniArgs[argkv[0]] = argkv[1] 208 } 209 }