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 }