github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/cmd/juju/action/common.go (about)

     1  // Copyright 2014-2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package action
     5  
     6  import (
     7  	"github.com/juju/cmd"
     8  	"github.com/juju/errors"
     9  	"github.com/juju/loggo"
    10  	"github.com/juju/names"
    11  	"gopkg.in/yaml.v1"
    12  
    13  	"github.com/juju/juju/apiserver/params"
    14  )
    15  
    16  var logger = loggo.GetLogger("juju.cmd.juju.action")
    17  
    18  // conform ensures all keys of any nested maps are strings.  This is
    19  // necessary because YAML unmarshals map[interface{}]interface{} in nested
    20  // maps, which cannot be serialized by bson. Also, handle []interface{}.
    21  // cf. gopkg.in/juju/charm.v4/actions.go cleanse
    22  func conform(input interface{}) (interface{}, error) {
    23  	switch typedInput := input.(type) {
    24  
    25  	case map[string]interface{}:
    26  		newMap := make(map[string]interface{})
    27  		for key, value := range typedInput {
    28  			newValue, err := conform(value)
    29  			if err != nil {
    30  				return nil, err
    31  			}
    32  			newMap[key] = newValue
    33  		}
    34  		return newMap, nil
    35  
    36  	case map[interface{}]interface{}:
    37  		newMap := make(map[string]interface{})
    38  		for key, value := range typedInput {
    39  			typedKey, ok := key.(string)
    40  			if !ok {
    41  				return nil, errors.New("map keyed with non-string value")
    42  			}
    43  			newMap[typedKey] = value
    44  		}
    45  		return conform(newMap)
    46  
    47  	case []interface{}:
    48  		newSlice := make([]interface{}, len(typedInput))
    49  		for i, sliceValue := range typedInput {
    50  			newSliceValue, err := conform(sliceValue)
    51  			if err != nil {
    52  				return nil, errors.New("map keyed with non-string value")
    53  			}
    54  			newSlice[i] = newSliceValue
    55  		}
    56  		return newSlice, nil
    57  
    58  	default:
    59  		return input, nil
    60  	}
    61  }
    62  
    63  // displayActionResult returns any error from an ActionResult and displays
    64  // its response values otherwise.
    65  func displayActionResult(result params.ActionResult, ctx *cmd.Context, out cmd.Output) error {
    66  	if result.Error != nil {
    67  		return result.Error
    68  	}
    69  
    70  	if result.Action == nil {
    71  		return errors.New("action for result was nil")
    72  	}
    73  
    74  	output, err := yaml.Marshal(result.Output)
    75  	if err != nil {
    76  		return err
    77  	}
    78  
    79  	response := struct {
    80  		Action  string
    81  		Target  string
    82  		Status  string
    83  		Message string
    84  		Results string
    85  	}{
    86  		Action:  result.Action.Name,
    87  		Target:  result.Action.Receiver,
    88  		Status:  result.Status,
    89  		Message: result.Message,
    90  		Results: string(output),
    91  	}
    92  
    93  	err = out.Write(ctx, response)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	return nil
    99  }
   100  
   101  // getActionTagByPrefix uses the APIClient to get all ActionTags matching a prefix.
   102  func getActionTagsByPrefix(api APIClient, prefix string) ([]names.ActionTag, error) {
   103  	results := []names.ActionTag{}
   104  
   105  	tags, err := api.FindActionTagsByPrefix(params.FindTags{Prefixes: []string{prefix}})
   106  	if err != nil {
   107  		return results, err
   108  	}
   109  
   110  	matches, ok := tags.Matches[prefix]
   111  	if !ok || len(matches) < 1 {
   112  		return results, nil
   113  	}
   114  
   115  	results, rejects := getActionTags(matches)
   116  	if len(rejects) > 0 {
   117  		logger.Errorf("FindActionTagsByPrefix for prefix %q found invalid tags %v", prefix, rejects)
   118  	}
   119  	return results, nil
   120  }
   121  
   122  // getActionTagByPrefix uses the APIClient to get an ActionTag from a prefix.
   123  func getActionTagByPrefix(api APIClient, prefix string) (names.ActionTag, error) {
   124  	tag := names.ActionTag{}
   125  	actiontags, err := getActionTagsByPrefix(api, prefix)
   126  	if err != nil {
   127  		return tag, err
   128  	}
   129  
   130  	if len(actiontags) < 1 {
   131  		return tag, errors.Errorf("actions for identifier %q not found", prefix)
   132  	}
   133  
   134  	if len(actiontags) > 1 {
   135  		return tag, errors.Errorf("identifier %q matched multiple actions %v", prefix, actiontags)
   136  	}
   137  
   138  	return actiontags[0], nil
   139  }
   140  
   141  // getActionTags converts a slice of params.Entity to a slice of names.ActionTag, and
   142  // also populates a slice of strings for the params.Entity.Tag that are not a valid
   143  // names.ActionTag.
   144  func getActionTags(entities []params.Entity) (good []names.ActionTag, bad []string) {
   145  	for _, entity := range entities {
   146  		if tag, err := entityToActionTag(entity); err != nil {
   147  			bad = append(bad, entity.Tag)
   148  		} else {
   149  			good = append(good, tag)
   150  		}
   151  	}
   152  	return
   153  }
   154  
   155  // entityToActionTag converts the params.Entity type to a names.ActionTag
   156  func entityToActionTag(entity params.Entity) (names.ActionTag, error) {
   157  	return names.ParseActionTag(entity.Tag)
   158  }
   159  
   160  // addValueToMap adds the given value to the map on which the method is run.
   161  // This allows us to merge maps such as {foo: {bar: baz}} and {foo: {baz: faz}}
   162  // into {foo: {bar: baz, baz: faz}}.
   163  func addValueToMap(keys []string, value interface{}, target map[string]interface{}) {
   164  	next := target
   165  
   166  	for i := range keys {
   167  		// If we are on last key set or overwrite the val.
   168  		if i == len(keys)-1 {
   169  			next[keys[i]] = value
   170  			break
   171  		}
   172  
   173  		if iface, ok := next[keys[i]]; ok {
   174  			switch typed := iface.(type) {
   175  			case map[string]interface{}:
   176  				// If we already had a map inside, keep
   177  				// stepping through.
   178  				next = typed
   179  			default:
   180  				// If we didn't, then overwrite value
   181  				// with a map and iterate with that.
   182  				m := map[string]interface{}{}
   183  				next[keys[i]] = m
   184  				next = m
   185  			}
   186  			continue
   187  		}
   188  
   189  		// Otherwise, it wasn't present, so make it and step
   190  		// into.
   191  		m := map[string]interface{}{}
   192  		next[keys[i]] = m
   193  		next = m
   194  	}
   195  }