329 lines
8.5 KiB
Go
329 lines
8.5 KiB
Go
|
package egoscale
|
||
|
|
||
|
import (
|
||
|
"encoding/base64"
|
||
|
"fmt"
|
||
|
"log"
|
||
|
"net"
|
||
|
"net/url"
|
||
|
"reflect"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
func csQuotePlus(s string) string {
|
||
|
s = strings.Replace(s, "+", "%20", -1)
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
func csEncode(s string) string {
|
||
|
return csQuotePlus(url.QueryEscape(s))
|
||
|
}
|
||
|
|
||
|
// info returns the meta info of a command
|
||
|
//
|
||
|
// command is not a Command so it's easier to Test
|
||
|
func info(command interface{}) (*CommandInfo, error) {
|
||
|
typeof := reflect.TypeOf(command)
|
||
|
|
||
|
// Going up the pointer chain to find the underlying struct
|
||
|
for typeof.Kind() == reflect.Ptr {
|
||
|
typeof = typeof.Elem()
|
||
|
}
|
||
|
|
||
|
field, ok := typeof.FieldByName("_")
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf(`missing meta ("_") field in %#v`, command)
|
||
|
}
|
||
|
|
||
|
name, nameOk := field.Tag.Lookup("name")
|
||
|
description, _ := field.Tag.Lookup("description")
|
||
|
|
||
|
if !nameOk {
|
||
|
return nil, fmt.Errorf(`missing "name" key in the tag string of %#v`, command)
|
||
|
}
|
||
|
|
||
|
info := &CommandInfo{
|
||
|
Name: name,
|
||
|
Description: description,
|
||
|
}
|
||
|
|
||
|
return info, nil
|
||
|
}
|
||
|
|
||
|
// prepareValues uses a command to build a POST request
|
||
|
//
|
||
|
// command is not a Command so it's easier to Test
|
||
|
func prepareValues(prefix string, params url.Values, command interface{}) error {
|
||
|
value := reflect.ValueOf(command)
|
||
|
typeof := reflect.TypeOf(command)
|
||
|
|
||
|
// Going up the pointer chain to find the underlying struct
|
||
|
for typeof.Kind() == reflect.Ptr {
|
||
|
typeof = typeof.Elem()
|
||
|
value = value.Elem()
|
||
|
}
|
||
|
|
||
|
// Checking for nil commands
|
||
|
if !value.IsValid() {
|
||
|
return fmt.Errorf("cannot serialize the invalid value %#v", command)
|
||
|
}
|
||
|
|
||
|
for i := 0; i < typeof.NumField(); i++ {
|
||
|
field := typeof.Field(i)
|
||
|
if field.Name == "_" {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
val := value.Field(i)
|
||
|
tag := field.Tag
|
||
|
if json, ok := tag.Lookup("json"); ok {
|
||
|
n, required := ExtractJSONTag(field.Name, json)
|
||
|
name := prefix + n
|
||
|
|
||
|
switch val.Kind() {
|
||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||
|
v := val.Int()
|
||
|
if v == 0 {
|
||
|
if required {
|
||
|
return fmt.Errorf("%s.%s (%v) is required, got 0", typeof.Name(), n, val.Kind())
|
||
|
}
|
||
|
} else {
|
||
|
params.Set(name, strconv.FormatInt(v, 10))
|
||
|
}
|
||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||
|
v := val.Uint()
|
||
|
if v == 0 {
|
||
|
if required {
|
||
|
return fmt.Errorf("%s.%s (%v) is required, got 0", typeof.Name(), n, val.Kind())
|
||
|
}
|
||
|
} else {
|
||
|
params.Set(name, strconv.FormatUint(v, 10))
|
||
|
}
|
||
|
case reflect.Float32, reflect.Float64:
|
||
|
v := val.Float()
|
||
|
if v == 0 {
|
||
|
if required {
|
||
|
return fmt.Errorf("%s.%s (%v) is required, got 0", typeof.Name(), n, val.Kind())
|
||
|
}
|
||
|
} else {
|
||
|
params.Set(name, strconv.FormatFloat(v, 'f', -1, 64))
|
||
|
}
|
||
|
case reflect.String:
|
||
|
v := val.String()
|
||
|
if v == "" {
|
||
|
if required {
|
||
|
return fmt.Errorf("%s.%s (%v) is required, got \"\"", typeof.Name(), n, val.Kind())
|
||
|
}
|
||
|
} else {
|
||
|
params.Set(name, v)
|
||
|
}
|
||
|
case reflect.Bool:
|
||
|
v := val.Bool()
|
||
|
if !v {
|
||
|
if required {
|
||
|
params.Set(name, "false")
|
||
|
}
|
||
|
} else {
|
||
|
params.Set(name, "true")
|
||
|
}
|
||
|
case reflect.Ptr:
|
||
|
if val.IsNil() {
|
||
|
if required {
|
||
|
return fmt.Errorf("%s.%s (%v) is required, got empty ptr", typeof.Name(), n, val.Kind())
|
||
|
}
|
||
|
} else {
|
||
|
switch field.Type.Elem().Kind() {
|
||
|
case reflect.Bool:
|
||
|
params.Set(name, strconv.FormatBool(val.Elem().Bool()))
|
||
|
case reflect.Struct:
|
||
|
i := val.Interface()
|
||
|
s, ok := i.(fmt.Stringer)
|
||
|
if !ok {
|
||
|
return fmt.Errorf("%s.%s (%v) is not a Stringer", typeof.Name(), field.Name, val.Kind())
|
||
|
}
|
||
|
if s != nil && s.String() == "" {
|
||
|
if required {
|
||
|
return fmt.Errorf("%s.%s (%v) is required, got empty value", typeof.Name(), field.Name, val.Kind())
|
||
|
}
|
||
|
} else {
|
||
|
params.Set(n, s.String())
|
||
|
}
|
||
|
default:
|
||
|
log.Printf("[SKIP] %s.%s (%v) not supported", typeof.Name(), n, field.Type.Elem().Kind())
|
||
|
}
|
||
|
}
|
||
|
case reflect.Slice:
|
||
|
switch field.Type.Elem().Kind() {
|
||
|
case reflect.Uint8:
|
||
|
switch field.Type {
|
||
|
case reflect.TypeOf(net.IPv4zero):
|
||
|
ip := (net.IP)(val.Bytes())
|
||
|
if ip == nil || ip.Equal(net.IPv4zero) {
|
||
|
if required {
|
||
|
return fmt.Errorf("%s.%s (%v) is required, got zero IPv4 address", typeof.Name(), n, val.Kind())
|
||
|
}
|
||
|
} else {
|
||
|
params.Set(name, ip.String())
|
||
|
}
|
||
|
case reflect.TypeOf(MAC48(0, 0, 0, 0, 0, 0)):
|
||
|
mac := val.Interface().(MACAddress)
|
||
|
s := mac.String()
|
||
|
if s == "" {
|
||
|
if required {
|
||
|
return fmt.Errorf("%s.%s (%v) is required, got empty MAC address", typeof.Name(), field.Name, val.Kind())
|
||
|
}
|
||
|
} else {
|
||
|
params.Set(name, s)
|
||
|
}
|
||
|
default:
|
||
|
if val.Len() == 0 {
|
||
|
if required {
|
||
|
return fmt.Errorf("%s.%s (%v) is required, got empty slice", typeof.Name(), n, val.Kind())
|
||
|
}
|
||
|
} else {
|
||
|
v := val.Bytes()
|
||
|
params.Set(name, base64.StdEncoding.EncodeToString(v))
|
||
|
}
|
||
|
}
|
||
|
case reflect.String:
|
||
|
if val.Len() == 0 {
|
||
|
if required {
|
||
|
return fmt.Errorf("%s.%s (%v) is required, got empty slice", typeof.Name(), n, val.Kind())
|
||
|
}
|
||
|
} else {
|
||
|
elems := make([]string, 0, val.Len())
|
||
|
for i := 0; i < val.Len(); i++ {
|
||
|
// XXX what if the value contains a comma? Double encode?
|
||
|
s := val.Index(i).String()
|
||
|
elems = append(elems, s)
|
||
|
}
|
||
|
params.Set(name, strings.Join(elems, ","))
|
||
|
}
|
||
|
default:
|
||
|
switch field.Type.Elem() {
|
||
|
case reflect.TypeOf(CIDR{}), reflect.TypeOf(UUID{}):
|
||
|
if val.Len() == 0 {
|
||
|
if required {
|
||
|
return fmt.Errorf("%s.%s (%v) is required, got empty slice", typeof.Name(), n, val.Kind())
|
||
|
}
|
||
|
} else {
|
||
|
value := reflect.ValueOf(val.Interface())
|
||
|
ss := make([]string, val.Len())
|
||
|
for i := 0; i < value.Len(); i++ {
|
||
|
v := value.Index(i).Interface()
|
||
|
s, ok := v.(fmt.Stringer)
|
||
|
if !ok {
|
||
|
return fmt.Errorf("not a String, %T", v)
|
||
|
}
|
||
|
ss[i] = s.String()
|
||
|
}
|
||
|
params.Set(name, strings.Join(ss, ","))
|
||
|
}
|
||
|
default:
|
||
|
if val.Len() == 0 {
|
||
|
if required {
|
||
|
return fmt.Errorf("%s.%s (%v) is required, got empty slice", typeof.Name(), n, val.Kind())
|
||
|
}
|
||
|
} else {
|
||
|
err := prepareList(name, params, val.Interface())
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
case reflect.Map:
|
||
|
if val.Len() == 0 {
|
||
|
if required {
|
||
|
return fmt.Errorf("%s.%s (%v) is required, got empty map", typeof.Name(), field.Name, val.Kind())
|
||
|
}
|
||
|
} else {
|
||
|
err := prepareMap(name, params, val.Interface())
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
case reflect.Struct:
|
||
|
i := val.Interface()
|
||
|
s, ok := i.(fmt.Stringer)
|
||
|
if !ok {
|
||
|
return fmt.Errorf("%s.%s (%v) is not a Stringer", typeof.Name(), field.Name, val.Kind())
|
||
|
}
|
||
|
if s != nil && s.String() == "" {
|
||
|
if required {
|
||
|
return fmt.Errorf("%s.%s (%v) is required, got empty value", typeof.Name(), field.Name, val.Kind())
|
||
|
}
|
||
|
} else {
|
||
|
params.Set(n, s.String())
|
||
|
}
|
||
|
default:
|
||
|
if required {
|
||
|
return fmt.Errorf("unsupported type %s.%s (%v)", typeof.Name(), n, val.Kind())
|
||
|
}
|
||
|
fmt.Printf("%s\n", val.Kind())
|
||
|
}
|
||
|
} else {
|
||
|
log.Printf("[SKIP] %s.%s no json label found", typeof.Name(), field.Name)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func prepareList(prefix string, params url.Values, slice interface{}) error {
|
||
|
value := reflect.ValueOf(slice)
|
||
|
|
||
|
for i := 0; i < value.Len(); i++ {
|
||
|
err := prepareValues(fmt.Sprintf("%s[%d].", prefix, i), params, value.Index(i).Interface())
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func prepareMap(prefix string, params url.Values, m interface{}) error {
|
||
|
value := reflect.ValueOf(m)
|
||
|
|
||
|
for i, key := range value.MapKeys() {
|
||
|
var keyName string
|
||
|
var keyValue string
|
||
|
|
||
|
switch key.Kind() {
|
||
|
case reflect.String:
|
||
|
keyName = key.String()
|
||
|
default:
|
||
|
return fmt.Errorf("only map[string]string are supported (XXX)")
|
||
|
}
|
||
|
|
||
|
val := value.MapIndex(key)
|
||
|
switch val.Kind() {
|
||
|
case reflect.String:
|
||
|
keyValue = val.String()
|
||
|
default:
|
||
|
return fmt.Errorf("only map[string]string are supported (XXX)")
|
||
|
}
|
||
|
params.Set(fmt.Sprintf("%s[%d].%s", prefix, i, keyName), keyValue)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// ExtractJSONTag returns the variable name or defaultName as well as if the field is required (!omitempty)
|
||
|
func ExtractJSONTag(defaultName, jsonTag string) (string, bool) {
|
||
|
tags := strings.Split(jsonTag, ",")
|
||
|
name := tags[0]
|
||
|
required := true
|
||
|
for _, tag := range tags {
|
||
|
if tag == "omitempty" {
|
||
|
required = false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if name == "" || name == "omitempty" {
|
||
|
name = defaultName
|
||
|
}
|
||
|
return name, required
|
||
|
}
|