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  }