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  }