raw_exec.go 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  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. package invoke
  15. import (
  16. "bytes"
  17. "context"
  18. "encoding/json"
  19. "fmt"
  20. "io"
  21. "os/exec"
  22. "strings"
  23. "time"
  24. "github.com/containernetworking/cni/pkg/types"
  25. )
  26. type RawExec struct {
  27. Stderr io.Writer
  28. }
  29. func (e *RawExec) ExecPlugin(ctx context.Context, pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
  30. stdout := &bytes.Buffer{}
  31. stderr := &bytes.Buffer{}
  32. c := exec.CommandContext(ctx, pluginPath)
  33. c.Env = environ
  34. c.Stdin = bytes.NewBuffer(stdinData)
  35. c.Stdout = stdout
  36. c.Stderr = stderr
  37. // Retry the command on "text file busy" errors
  38. for i := 0; i <= 5; i++ {
  39. err := c.Run()
  40. // Command succeeded
  41. if err == nil {
  42. break
  43. }
  44. // If the plugin is currently about to be written, then we wait a
  45. // second and try it again
  46. if strings.Contains(err.Error(), "text file busy") {
  47. time.Sleep(time.Second)
  48. continue
  49. }
  50. // All other errors except than the busy text file
  51. return nil, e.pluginErr(err, stdout.Bytes(), stderr.Bytes())
  52. }
  53. // Copy stderr to caller's buffer in case plugin printed to both
  54. // stdout and stderr for some reason. Ignore failures as stderr is
  55. // only informational.
  56. if e.Stderr != nil && stderr.Len() > 0 {
  57. _, _ = stderr.WriteTo(e.Stderr)
  58. }
  59. return stdout.Bytes(), nil
  60. }
  61. func (e *RawExec) pluginErr(err error, stdout, stderr []byte) error {
  62. emsg := types.Error{}
  63. if len(stdout) == 0 {
  64. if len(stderr) == 0 {
  65. emsg.Msg = fmt.Sprintf("netplugin failed with no error message: %v", err)
  66. } else {
  67. emsg.Msg = fmt.Sprintf("netplugin failed: %q", string(stderr))
  68. }
  69. } else if perr := json.Unmarshal(stdout, &emsg); perr != nil {
  70. emsg.Msg = fmt.Sprintf("netplugin failed but error parsing its diagnostic message %q: %v", string(stdout), perr)
  71. }
  72. return &emsg
  73. }
  74. func (e *RawExec) FindInPath(plugin string, paths []string) (string, error) {
  75. return FindInPath(plugin, paths)
  76. }