yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/bingocloud/bingo.go (about)

     1  // Copyright 2019 Yunion
     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 implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package bingocloud
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"crypto/hmac"
    21  	"crypto/sha256"
    22  	"encoding/base64"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"net/http"
    26  	"net/url"
    27  	"sort"
    28  	"strings"
    29  	"time"
    30  
    31  	xj "github.com/basgys/goxml2json"
    32  
    33  	"yunion.io/x/jsonutils"
    34  	"yunion.io/x/log"
    35  	"yunion.io/x/pkg/errors"
    36  
    37  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    38  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    39  	"yunion.io/x/onecloud/pkg/util/httputils"
    40  )
    41  
    42  const (
    43  	CLOUD_PROVIDER_BINGO_CLOUD = api.CLOUD_PROVIDER_BINGO_CLOUD
    44  
    45  	MAX_RESULT = 20
    46  )
    47  
    48  type BingoCloudConfig struct {
    49  	cpcfg     cloudprovider.ProviderConfig
    50  	endpoint  string
    51  	accessKey string
    52  	secretKey string
    53  
    54  	debug bool
    55  }
    56  
    57  func NewBingoCloudClientConfig(endpoint, accessKey, secretKey string) *BingoCloudConfig {
    58  	cfg := &BingoCloudConfig{
    59  		endpoint:  endpoint,
    60  		accessKey: accessKey,
    61  		secretKey: secretKey,
    62  	}
    63  	return cfg
    64  }
    65  
    66  func (cfg *BingoCloudConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *BingoCloudConfig {
    67  	cfg.cpcfg = cpcfg
    68  	return cfg
    69  }
    70  
    71  func (cfg *BingoCloudConfig) Debug(debug bool) *BingoCloudConfig {
    72  	cfg.debug = debug
    73  	return cfg
    74  }
    75  
    76  type SBingoCloudClient struct {
    77  	*BingoCloudConfig
    78  
    79  	regions []SRegion
    80  }
    81  
    82  func NewBingoCloudClient(cfg *BingoCloudConfig) (*SBingoCloudClient, error) {
    83  	client := &SBingoCloudClient{BingoCloudConfig: cfg}
    84  	var err error
    85  	client.regions, err = client.GetRegions()
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	for i := range client.regions {
    90  		client.regions[i].client = client
    91  	}
    92  	return client, nil
    93  }
    94  
    95  func (self *SBingoCloudClient) GetAccountId() string {
    96  	return self.endpoint
    97  }
    98  
    99  func (self *SBingoCloudClient) GetRegion(id string) (*SRegion, error) {
   100  	for i := range self.regions {
   101  		if self.regions[i].RegionId == id {
   102  			return &self.regions[i], nil
   103  		}
   104  	}
   105  	if len(id) == 0 {
   106  		return &self.regions[0], nil
   107  	}
   108  	return nil, cloudprovider.ErrNotFound
   109  }
   110  
   111  func (cli *SBingoCloudClient) getDefaultClient(timeout time.Duration) *http.Client {
   112  	client := httputils.GetDefaultClient()
   113  	if timeout > 0 {
   114  		client = httputils.GetTimeoutClient(timeout)
   115  	}
   116  	if cli.cpcfg.ProxyFunc != nil {
   117  		httputils.SetClientProxyFunc(client, cli.cpcfg.ProxyFunc)
   118  	}
   119  	return client
   120  }
   121  
   122  func (self *SBingoCloudClient) sign(query string) string {
   123  	uri, _ := url.Parse(self.endpoint)
   124  	items := strings.Split(query, "&")
   125  	sort.Slice(items, func(i, j int) bool {
   126  		x0, y0 := strings.Split(items[i], "=")[0], strings.Split(items[j], "=")[0]
   127  		return x0 < y0
   128  	})
   129  	path := "/"
   130  	if len(uri.Path) > 0 {
   131  		path = uri.Path
   132  	}
   133  	stringToSign := fmt.Sprintf("POST\n%s\n%s\n", uri.Host, path) + strings.Join(items, "&")
   134  	hmac := hmac.New(sha256.New, []byte(self.secretKey))
   135  	hmac.Write([]byte(stringToSign))
   136  	return base64.StdEncoding.EncodeToString(hmac.Sum(nil))
   137  }
   138  
   139  func setItemToArray(obj jsonutils.JSONObject) jsonutils.JSONObject {
   140  	objDict, ok := obj.(*jsonutils.JSONDict)
   141  	if ok {
   142  		for k, v := range objDict.Value() {
   143  			if v.String() == `""` {
   144  				objDict.Remove(k)
   145  				continue
   146  			}
   147  			vDict, ok := v.(*jsonutils.JSONDict)
   148  			if ok {
   149  				if vDict.Contains("item") {
   150  					item, _ := vDict.Get("item")
   151  					_, ok := item.(*jsonutils.JSONArray)
   152  					if !ok {
   153  						if k != "instancesSet" {
   154  							item = setItemToArray(item)
   155  							objDict.Set(k, jsonutils.NewArray(item))
   156  						} else {
   157  							objDict.Set(k, setItemToArray(item))
   158  						}
   159  					} else {
   160  						items, _ := item.GetArray()
   161  						for i := range items {
   162  							items[i] = setItemToArray(items[i])
   163  						}
   164  						objDict.Set(k, jsonutils.NewArray(items...))
   165  					}
   166  					for _, nk := range []string{"nextToken", "NextToken"} {
   167  						nextToken, _ := vDict.GetString(nk)
   168  						if len(nextToken) > 0 {
   169  							objDict.Set(nk, jsonutils.NewString(nextToken))
   170  						}
   171  					}
   172  				} else {
   173  					objDict.Set(k, setItemToArray(v))
   174  				}
   175  			} else if _, ok = v.(*jsonutils.JSONArray); ok {
   176  				if ok {
   177  					arr, _ := v.GetArray()
   178  					for i := range arr {
   179  						arr[i] = setItemToArray(arr[i])
   180  					}
   181  					objDict.Set(k, jsonutils.NewArray(arr...))
   182  				}
   183  			}
   184  		}
   185  	}
   186  	_, ok = obj.(*jsonutils.JSONArray)
   187  	if ok {
   188  		arr, _ := obj.GetArray()
   189  		for i := range arr {
   190  			arr[i] = setItemToArray(arr[i])
   191  		}
   192  		return jsonutils.NewArray(arr...)
   193  	}
   194  	return objDict
   195  }
   196  
   197  type sBingoError struct {
   198  	Response struct {
   199  		Errors struct {
   200  			Error struct {
   201  				Code    string
   202  				ErrorNo string
   203  				Message string
   204  			}
   205  		}
   206  	}
   207  }
   208  
   209  func (e sBingoError) Error() string {
   210  	return jsonutils.Marshal(e.Response.Errors.Error).String()
   211  }
   212  
   213  func (self *SBingoCloudClient) invoke(action string, params map[string]string) (jsonutils.JSONObject, error) {
   214  	if self.cpcfg.ReadOnly {
   215  		for _, prefix := range []string{"Get", "List", "Describe"} {
   216  			if strings.HasPrefix(action, prefix) {
   217  				return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, action)
   218  			}
   219  		}
   220  	}
   221  	var encode = func(k, v string) string {
   222  		d := url.Values{}
   223  		d.Set(k, v)
   224  		return d.Encode()
   225  	}
   226  	query := encode("Action", action)
   227  	for k, v := range params {
   228  		query += "&" + encode(k, v)
   229  	}
   230  	// 2022-02-11T03:57:37.000Z
   231  	sh, _ := time.LoadLocation("Asia/Shanghai")
   232  	timeStamp := time.Now().In(sh).Format("2006-01-02T15:04:05.000Z")
   233  	query += "&" + encode("Timestamp", timeStamp)
   234  	query += "&" + encode("AWSAccessKeyId", self.accessKey)
   235  	query += "&" + encode("Version", "2009-08-15")
   236  	query += "&" + encode("SignatureVersion", "2")
   237  	query += "&" + encode("SignatureMethod", "HmacSHA256")
   238  	query += "&" + encode("Signature", self.sign(query))
   239  	client := self.getDefaultClient(0)
   240  	resp, err := httputils.Request(client, context.Background(), httputils.POST, self.endpoint, nil, strings.NewReader(query), self.debug)
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  	defer resp.Body.Close()
   245  
   246  	data, err := ioutil.ReadAll(resp.Body)
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  
   251  	result, err := xj.Convert(bytes.NewReader(data))
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	obj, err := jsonutils.Parse([]byte(result.String()))
   257  	if err != nil {
   258  		return nil, errors.Wrapf(err, "jsonutils.Parse")
   259  	}
   260  
   261  	obj = setItemToArray(obj)
   262  
   263  	if self.debug {
   264  		log.Debugf("response: %s", obj.PrettyString())
   265  	}
   266  
   267  	be := &sBingoError{}
   268  	obj.Unmarshal(be)
   269  	if len(be.Response.Errors.Error.Code) > 0 {
   270  		return nil, be
   271  	}
   272  
   273  	respKey := action + "Response"
   274  	if obj.Contains(respKey) {
   275  		obj, err = obj.Get(respKey)
   276  		if err != nil {
   277  			return nil, err
   278  		}
   279  	}
   280  
   281  	return obj, nil
   282  }
   283  
   284  func (self *SBingoCloudClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
   285  	subAccount := cloudprovider.SSubAccount{
   286  		Account: self.accessKey,
   287  		Name:    self.cpcfg.Name,
   288  
   289  		HealthStatus: api.CLOUD_PROVIDER_HEALTH_NORMAL,
   290  	}
   291  	return []cloudprovider.SSubAccount{subAccount}, nil
   292  
   293  }
   294  
   295  func (self *SBingoCloudClient) GetIRegions() []cloudprovider.ICloudRegion {
   296  	ret := []cloudprovider.ICloudRegion{}
   297  	for i := range self.regions {
   298  		self.regions[i].client = self
   299  		ret = append(ret, &self.regions[i])
   300  	}
   301  	return ret
   302  }
   303  
   304  func (self *SBingoCloudClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) {
   305  	iregions := self.GetIRegions()
   306  	for i := range iregions {
   307  		if iregions[i].GetGlobalId() == id {
   308  			return iregions[i], nil
   309  		}
   310  	}
   311  	return nil, errors.Wrapf(cloudprovider.ErrNotFound, id)
   312  }
   313  
   314  func (self *SBingoCloudClient) GetCapabilities() []string {
   315  	return []string{
   316  		cloudprovider.CLOUD_CAPABILITY_COMPUTE + cloudprovider.READ_ONLY_SUFFIX,
   317  		cloudprovider.CLOUD_CAPABILITY_NETWORK + cloudprovider.READ_ONLY_SUFFIX,
   318  		cloudprovider.CLOUD_CAPABILITY_EIP + cloudprovider.READ_ONLY_SUFFIX,
   319  	}
   320  }