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 }