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  }