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