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 }