github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/machineactions/handleactions.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Copyright 2016 Cloudbase Solutions 3 // Licensed under the AGPLv3, see LICENCE file for details. 4 5 package machineactions 6 7 import ( 8 "encoding/base64" 9 "fmt" 10 "os" 11 "time" 12 "unicode/utf8" 13 14 "github.com/juju/clock" 15 "github.com/juju/errors" 16 "github.com/juju/utils/exec" 17 18 "github.com/juju/juju/core/actions" 19 ) 20 21 // RunAsUser is the user that the machine juju-run action is executed as. 22 var RunAsUser = "ubuntu" 23 24 // HandleAction receives a name and a map of parameters for a given machine action. 25 // It will handle that action in a specific way and return a results map suitable for ActionFinish. 26 func HandleAction(name string, params map[string]interface{}) (results map[string]interface{}, err error) { 27 spec, ok := actions.PredefinedActionsSpec[name] 28 if !ok { 29 return nil, errors.Errorf("unexpected action %s", name) 30 } 31 if err := spec.ValidateParams(params); err != nil { 32 return nil, errors.Errorf("invalid action parameters") 33 } 34 35 switch name { 36 case actions.JujuRunActionName: 37 return handleJujuRunAction(params) 38 default: 39 return nil, errors.Errorf("unexpected action %s", name) 40 } 41 } 42 43 func handleJujuRunAction(params map[string]interface{}) (results map[string]interface{}, err error) { 44 // The spec checks that the parameters are available so we don't need to check again here 45 command, _ := params["command"].(string) 46 logger.Tracef("juju run %q", command) 47 48 // The timeout is passed in in nanoseconds(which are represented in go as int64) 49 // But due to serialization it comes out as float64 50 timeout, _ := params["timeout"].(float64) 51 52 res, err := runCommandWithTimeout(command, time.Duration(timeout), clock.WallClock) 53 if err != nil { 54 return nil, errors.Trace(err) 55 } 56 57 actionResults := map[string]interface{}{} 58 actionResults["Code"] = fmt.Sprintf("%d", res.Code) 59 storeOutput(actionResults, "Stdout", res.Stdout) 60 storeOutput(actionResults, "Stderr", res.Stderr) 61 62 return actionResults, nil 63 } 64 65 func runCommandWithTimeout(command string, timeout time.Duration, clock clock.Clock) (*exec.ExecResponse, error) { 66 cmd := exec.RunParams{ 67 Commands: command, 68 Environment: os.Environ(), 69 Clock: clock, 70 User: RunAsUser, 71 } 72 73 err := cmd.Run() 74 if err != nil { 75 return nil, errors.Trace(err) 76 } 77 78 var cancel chan struct{} 79 if timeout != 0 { 80 cancel = make(chan struct{}) 81 go func() { 82 <-clock.After(timeout) 83 close(cancel) 84 }() 85 } 86 87 return cmd.WaitWithCancel(cancel) 88 } 89 90 func encodeBytes(input []byte) (value string, encoding string) { 91 if utf8.Valid(input) { 92 value = string(input) 93 encoding = "utf8" 94 } else { 95 value = base64.StdEncoding.EncodeToString(input) 96 encoding = "base64" 97 } 98 return value, encoding 99 } 100 101 func storeOutput(values map[string]interface{}, key string, input []byte) { 102 value, encoding := encodeBytes(input) 103 values[key] = value 104 if encoding != "utf8" { 105 values[key+"Encoding"] = encoding 106 } 107 }