github.com/mmatczuk/gohan@v0.0.0-20170206152520-30e45d9bdb69/cli/client/commands.go (about) 1 // Copyright (C) 2015 NTT Innovation Institute, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 // implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 package client 17 18 import ( 19 "bytes" 20 "encoding/json" 21 "fmt" 22 "net/http" 23 "regexp" 24 25 "github.com/rackspace/gophercloud" 26 27 "github.com/cloudwan/gohan/schema" 28 ) 29 30 var ( 31 noResponseError = "No response" 32 multipleResourcesFoundError = "Multiple %s with name '%s' found" 33 resourceNotFoundError = "Resource not found" 34 unexpectedResponse = "Unexpected response: %v" 35 ) 36 37 type gohanCommand struct { 38 Name string 39 Schema *schema.Schema 40 Action func(args []string) (string, error) 41 } 42 43 func (gohanClientCLI *GohanClientCLI) request(method, url string, opts *gophercloud.RequestOpts) (interface{}, error) { 44 if opts == nil { 45 opts = &gophercloud.RequestOpts{ 46 JSONBody: map[string]interface{}{}, 47 } 48 } 49 gohanClientCLI.logRequest(method, url, gohanClientCLI.provider.TokenID, opts.JSONBody.(map[string]interface{})) 50 return gohanClientCLI.handleResponse(gohanClientCLI.provider.Request(method, url, *opts)) 51 } 52 53 func (gohanClientCLI *GohanClientCLI) getCommands() []gohanCommand { 54 commands := []gohanCommand{} 55 for _, s := range gohanClientCLI.schemas { 56 commands = append(commands, 57 gohanClientCLI.getListCommand(s), 58 gohanClientCLI.getGetCommand(s), 59 gohanClientCLI.getPostCommand(s), 60 gohanClientCLI.getPutCommand(s), 61 gohanClientCLI.getDeleteCommand(s), 62 ) 63 commands = append(commands, gohanClientCLI.getCustomCommands(s)...) 64 } 65 return commands 66 } 67 68 func (gohanClientCLI *GohanClientCLI) getListCommand(s *schema.Schema) gohanCommand { 69 return gohanCommand{ 70 Name: fmt.Sprintf("%s list", s.ID), 71 Schema: s, 72 Action: func(args []string) (string, error) { 73 _, err := gohanClientCLI.handleArguments(args, s) 74 if err != nil { 75 return "", err 76 } 77 url := fmt.Sprintf("%s%s", gohanClientCLI.opts.gohanEndpointURL, s.URL) 78 result, err := gohanClientCLI.request("GET", url, nil) 79 return gohanClientCLI.formatOutput(s, result), err 80 }, 81 } 82 } 83 84 func (gohanClientCLI *GohanClientCLI) getGetCommand(s *schema.Schema) gohanCommand { 85 return gohanCommand{ 86 Name: fmt.Sprintf("%s show", s.ID), 87 Schema: s, 88 Action: func(args []string) (string, error) { 89 if len(args) < 1 { 90 return "", fmt.Errorf("Wrong number of arguments") 91 } 92 _, err := gohanClientCLI.handleArguments(args[:len(args)-1], s) 93 if err != nil { 94 return "", err 95 } 96 id, err := gohanClientCLI.getResourceID(s, args[len(args)-1]) 97 if err != nil { 98 return "", err 99 } 100 url := fmt.Sprintf("%s%s/%s", gohanClientCLI.opts.gohanEndpointURL, s.URL, id) 101 result, err := gohanClientCLI.request("GET", url, nil) 102 if err != nil { 103 return "", err 104 } 105 buffer := bytes.NewBufferString("") 106 buffer.WriteString(gohanClientCLI.formatOutput(s, result)) 107 for _, childSchema := range gohanClientCLI.schemas { 108 if s.ID == childSchema.Parent { 109 buffer.WriteString("\n") 110 buffer.WriteString(childSchema.Title) 111 buffer.WriteString("\n") 112 parentSchemaPropertyID := childSchema.ParentSchemaPropertyID() 113 url := fmt.Sprintf("%s%s?%s=%s", gohanClientCLI.opts.gohanEndpointURL, childSchema.URL, parentSchemaPropertyID, id) 114 result, err := gohanClientCLI.request("GET", url, nil) 115 if err != nil { 116 return "", err 117 } 118 buffer.WriteString(gohanClientCLI.formatOutput(childSchema, result)) 119 } 120 } 121 return buffer.String(), nil 122 }, 123 } 124 } 125 126 func (gohanClientCLI *GohanClientCLI) getPostCommand(s *schema.Schema) gohanCommand { 127 return gohanCommand{ 128 Name: fmt.Sprintf("%s create", s.ID), 129 Schema: s, 130 Action: func(args []string) (string, error) { 131 argsMap, err := gohanClientCLI.handleArguments(args, s) 132 if err != nil { 133 return "", err 134 } 135 parsedArgs, err := gohanClientCLI.handleRelationArguments(s, argsMap) 136 if err != nil { 137 return "", err 138 } 139 opts := gophercloud.RequestOpts{ 140 JSONBody: parsedArgs, 141 OkCodes: []int{201, 202, 400}, 142 } 143 url := fmt.Sprintf("%s%s", gohanClientCLI.opts.gohanEndpointURL, s.URL) 144 result, err := gohanClientCLI.request("POST", url, &opts) 145 return gohanClientCLI.formatOutput(s, result), err 146 }, 147 } 148 } 149 150 func (gohanClientCLI *GohanClientCLI) getPutCommand(s *schema.Schema) gohanCommand { 151 return gohanCommand{ 152 Name: fmt.Sprintf("%s set", s.ID), 153 Schema: s, 154 Action: func(args []string) (string, error) { 155 if len(args) < 1 { 156 return "", fmt.Errorf("Wrong number of arguments") 157 } 158 argsMap, err := gohanClientCLI.handleArguments(args[:len(args)-1], s) 159 if err != nil { 160 return "", err 161 } 162 parsedArgs, err := gohanClientCLI.handleRelationArguments(s, argsMap) 163 if err != nil { 164 return "", err 165 } 166 opts := gophercloud.RequestOpts{ 167 JSONBody: parsedArgs, 168 OkCodes: []int{200, 201, 202, 400}, 169 } 170 id, err := gohanClientCLI.getResourceID(s, args[len(args)-1]) 171 if err != nil { 172 return "", err 173 } 174 url := fmt.Sprintf("%s%s/%s", gohanClientCLI.opts.gohanEndpointURL, s.URL, id) 175 result, err := gohanClientCLI.request("PUT", url, &opts) 176 return gohanClientCLI.formatOutput(s, result), err 177 }, 178 } 179 } 180 181 func (gohanClientCLI *GohanClientCLI) getDeleteCommand(s *schema.Schema) gohanCommand { 182 return gohanCommand{ 183 Name: fmt.Sprintf("%s delete", s.ID), 184 Schema: s, 185 Action: func(args []string) (string, error) { 186 if len(args) < 1 { 187 return "", fmt.Errorf("Wrong number of arguments") 188 } 189 _, err := gohanClientCLI.handleArguments(args[:len(args)-1], s) 190 if err != nil { 191 return "", err 192 } 193 id, err := gohanClientCLI.getResourceID(s, args[len(args)-1]) 194 if err != nil { 195 return "", err 196 } 197 url := fmt.Sprintf("%s%s/%s", gohanClientCLI.opts.gohanEndpointURL, s.URL, id) 198 result, err := gohanClientCLI.request("DELETE", url, nil) 199 return gohanClientCLI.formatOutput(s, result), err 200 }, 201 } 202 } 203 204 // Assumes gohan client is called as follows: 205 // gohan client [common_params...] [action_input] [resource_id] 206 // where common_params are in form '--name value' and 'name' exists in commonParams 207 // action_input conforms to action's InputSchema specification 208 // resource_id is ID of the resource this action acts upon 209 func (gohanClientCLI *GohanClientCLI) getCustomCommands(s *schema.Schema) []gohanCommand { 210 ret := make([]gohanCommand, 0, len(s.Actions)) 211 for _, act := range s.Actions { 212 ret = append(ret, gohanCommand{ 213 Name: s.ID + " " + act.ID, 214 Schema: s, 215 Action: gohanClientCLI.createActionFunc(act, s), 216 }) 217 } 218 return ret 219 } 220 221 func (gohanClientCLI *GohanClientCLI) createActionFunc( 222 act schema.Action, 223 s *schema.Schema, 224 ) func(args []string) (string, error) { 225 return func(args []string) (string, error) { 226 params, input, id, err := splitArgs(args, &act) 227 if err != nil { 228 return "", err 229 } 230 if len(id) > 0 { 231 id, err = gohanClientCLI.getResourceID(s, id) 232 if err != nil { 233 return "", err 234 } 235 } 236 argsMap, err := gohanClientCLI.getCustomArgsAsMap(params, input, act) 237 if err != nil { 238 return "", err 239 } 240 opts := gophercloud.RequestOpts{ 241 JSONBody: argsMap, 242 OkCodes: okCodes(act.Method), 243 } 244 url := gohanClientCLI.opts.gohanEndpointURL + s.URL + substituteID(act.Path, id) 245 result, err := gohanClientCLI.request(act.Method, url, &opts) 246 if err != nil { 247 return "", err 248 } 249 result = gohanClientCLI.formatCustomOutput(result) 250 return gohanClientCLI.formatOutput(s, result), err 251 } 252 } 253 254 func okCodes(method string) []int { 255 switch { 256 case method == "GET": 257 return []int{200} 258 case method == "POST": 259 return []int{201, 202, 400} 260 case method == "PUT": 261 return []int{200, 201, 202, 400} 262 case method == "PATCH": 263 return []int{200, 204} 264 case method == "DELETE": 265 return []int{202, 204} 266 } 267 268 return []int{} 269 } 270 271 func (gohanClientCLI *GohanClientCLI) formatCustomOutput(rawOutput interface{}) interface{} { 272 if rawOutput == nil { 273 return rawOutput 274 } 275 switch gohanClientCLI.opts.outputFormat { 276 case outputFormatTable: 277 return map[string]interface{}{ 278 "output": rawOutput, 279 } 280 default: 281 // outputFormatJSON 282 return rawOutput 283 } 284 } 285 286 // Splits command line arguments into id, action input and remaining parameters 287 func splitArgs( 288 args []string, 289 action *schema.Action, 290 ) (remainingArgs []string, input string, id string, err error) { 291 remainingArgs = args 292 re := regexp.MustCompile(`.*/:id(/.*)?$`) 293 match := re.FindString(action.Path) 294 argCount := 0 295 if len(match) != 0 { 296 argCount++ 297 } 298 if action.InputSchema != nil { 299 argCount++ 300 } 301 if len(args) < argCount { 302 err = fmt.Errorf("Wrong number of arguments") 303 return 304 } else if (len(args)-argCount)%2 != 0 { 305 err = fmt.Errorf("Parameters should be in [--param-name value]... format") 306 return 307 } 308 if len(match) != 0 { 309 id = remainingArgs[len(remainingArgs)-1] 310 remainingArgs = remainingArgs[:len(remainingArgs)-1] 311 } 312 if action.InputSchema != nil { 313 input = remainingArgs[len(remainingArgs)-1] 314 remainingArgs = remainingArgs[:len(remainingArgs)-1] 315 } 316 return 317 } 318 319 func substituteID(path, id string) string { 320 re := regexp.MustCompile(`(.*/)(:id)(/.*)?$`) 321 match := re.FindStringSubmatch(path) 322 if len(match) == 0 { 323 return path 324 } 325 return match[1] + id + match[3] 326 } 327 328 func (gohanClientCLI *GohanClientCLI) handleResponse(response *http.Response, err error) (interface{}, error) { 329 if response == nil { 330 return nil, fmt.Errorf(noResponseError) 331 } 332 defer response.Body.Close() 333 var result interface{} 334 json.NewDecoder(response.Body).Decode(&result) 335 336 gohanClientCLI.logResponse(response.Status, result) 337 338 if response.StatusCode == http.StatusNotFound { 339 return nil, fmt.Errorf(resourceNotFoundError) 340 } 341 if err != nil { 342 return nil, fmt.Errorf(unexpectedResponse, response.Status) 343 } 344 345 return result, err 346 }