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