bingo.go 9.7 KB

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