google.go 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053
  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 google
  15. import (
  16. "context"
  17. "fmt"
  18. "io"
  19. "net/http"
  20. "net/url"
  21. "strings"
  22. "time"
  23. "unicode"
  24. "golang.org/x/oauth2"
  25. "golang.org/x/oauth2/google"
  26. "golang.org/x/oauth2/jwt"
  27. "yunion.io/x/jsonutils"
  28. "yunion.io/x/log"
  29. "yunion.io/x/pkg/errors"
  30. "yunion.io/x/pkg/util/httputils"
  31. "yunion.io/x/pkg/util/stringutils"
  32. api "yunion.io/x/cloudmux/pkg/apis/compute"
  33. "yunion.io/x/cloudmux/pkg/cloudprovider"
  34. )
  35. const (
  36. CLOUD_PROVIDER_GOOGLE = api.CLOUD_PROVIDER_GOOGLE
  37. CLOUD_PROVIDER_GOOGLE_CN = "谷歌云"
  38. GOOGLE_DEFAULT_REGION = "asia-east1"
  39. GOOGLE_API_VERSION = "v1"
  40. GOOGLE_MANAGER_API_VERSION = "v1"
  41. GOOGLE_STORAGE_API_VERSION = "v1"
  42. GOOGLE_CLOUDBUILD_API_VERSION = "v1"
  43. GOOGLE_BILLING_API_VERSION = "v1"
  44. GOOGLE_MONITOR_API_VERSION = "v3"
  45. GOOGLE_DBINSTANCE_API_VERSION = "v1beta4"
  46. GOOGLE_IAM_API_VERSION = "v1"
  47. GOOGLE_BIGQUERY_API_VERSION = "v2"
  48. GOOGLE_MANAGER_DOMAIN = "https://cloudresourcemanager.googleapis.com"
  49. GOOGLE_COMPUTE_DOMAIN = "https://www.googleapis.com/compute"
  50. GOOGLE_STORAGE_DOMAIN = "https://storage.googleapis.com/storage"
  51. GOOGLE_CLOUDBUILD_DOMAIN = "https://cloudbuild.googleapis.com"
  52. GOOGLE_STORAGE_UPLOAD_DOMAIN = "https://www.googleapis.com/upload/storage"
  53. GOOGLE_BILLING_DOMAIN = "https://cloudbilling.googleapis.com"
  54. GOOGLE_MONITOR_DOMAIN = "https://monitoring.googleapis.com"
  55. GOOGLE_DBINSTANCE_DOMAIN = "https://www.googleapis.com/sql"
  56. GOOGLE_IAM_DOMAIN = "https://iam.googleapis.com"
  57. GOOGLE_BIGQUERY_DOMAIN = "https://bigquery.googleapis.com/bigquery"
  58. MAX_RETRY = 3
  59. )
  60. var (
  61. MultiRegions []string = []string{"us", "eu", "asia"}
  62. DualRegions []string = []string{"nam4", "eur4"}
  63. )
  64. type GoogleClientConfig struct {
  65. cpcfg cloudprovider.ProviderConfig
  66. projectId string
  67. clientEmail string
  68. privateKeyId string
  69. privateKey string
  70. debug bool
  71. }
  72. func NewGoogleClientConfig(projectId, clientEmail, privateKeyId, privateKey string) *GoogleClientConfig {
  73. privateKey = strings.Replace(privateKey, "\\n", "\n", -1)
  74. cfg := &GoogleClientConfig{
  75. projectId: projectId,
  76. clientEmail: clientEmail,
  77. privateKeyId: privateKeyId,
  78. privateKey: privateKey,
  79. }
  80. return cfg
  81. }
  82. func (cfg *GoogleClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *GoogleClientConfig {
  83. cfg.cpcfg = cpcfg
  84. return cfg
  85. }
  86. func (cfg *GoogleClientConfig) Debug(debug bool) *GoogleClientConfig {
  87. cfg.debug = debug
  88. return cfg
  89. }
  90. type SGoogleClient struct {
  91. *GoogleClientConfig
  92. iregions []cloudprovider.ICloudRegion
  93. images []SImage
  94. snapshots map[string][]SSnapshot
  95. globalnetworks []SGlobalNetwork
  96. resourcepolices []SResourcePolicy
  97. client *http.Client
  98. iBuckets []cloudprovider.ICloudBucket
  99. }
  100. func NewGoogleClient(cfg *GoogleClientConfig) (*SGoogleClient, error) {
  101. client := SGoogleClient{
  102. GoogleClientConfig: cfg,
  103. }
  104. conf := &jwt.Config{
  105. Email: cfg.clientEmail,
  106. PrivateKeyID: cfg.privateKeyId,
  107. PrivateKey: []byte(cfg.privateKey),
  108. Scopes: []string{
  109. "https://www.googleapis.com/auth/cloud-platform",
  110. "https://www.googleapis.com/auth/compute",
  111. "https://www.googleapis.com/auth/compute.readonly",
  112. "https://www.googleapis.com/auth/cloud-platform.read-only",
  113. "https://www.googleapis.com/auth/cloudplatformprojects",
  114. "https://www.googleapis.com/auth/cloudplatformprojects.readonly",
  115. "https://www.googleapis.com/auth/devstorage.full_control",
  116. "https://www.googleapis.com/auth/devstorage.read_write",
  117. },
  118. TokenURL: google.JWTTokenURL,
  119. }
  120. httpClient := cfg.cpcfg.AdaptiveTimeoutHttpClient()
  121. ts, _ := httpClient.Transport.(*http.Transport)
  122. httpClient.Transport = cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response) error, error) {
  123. service := strings.Split(req.URL.Host, ".")[0]
  124. if service == "www" {
  125. service = strings.Split(req.URL.Path, "/")[0]
  126. }
  127. method, path := req.Method, req.URL.Path
  128. respCheck := func(resp *http.Response) error {
  129. if resp.StatusCode == 403 {
  130. if cfg.cpcfg.UpdatePermission != nil {
  131. cfg.cpcfg.UpdatePermission(service, fmt.Sprintf("%s %s", method, path))
  132. }
  133. }
  134. return nil
  135. }
  136. if cfg.cpcfg.ReadOnly {
  137. if req.Method == "GET" {
  138. return respCheck, nil
  139. }
  140. return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path)
  141. }
  142. return respCheck, nil
  143. })
  144. ctx := context.Background()
  145. ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
  146. client.client = conf.Client(ctx)
  147. return &client, client.fetchRegions()
  148. }
  149. func (self *SGoogleClient) GetAccountId() string {
  150. return self.clientEmail
  151. }
  152. func (self *SGoogleClient) GetGlobalRegion() *SGlobalRegion {
  153. return &SGlobalRegion{
  154. client: self,
  155. Description: "global",
  156. Kind: "compute#region",
  157. Name: "global",
  158. Status: "UP",
  159. SelfLink: "",
  160. CreationTimestamp: time.Time{},
  161. }
  162. }
  163. func (self *SGoogleClient) fetchRegions() error {
  164. regions := []SRegion{}
  165. err := self.ecsListAll("regions", nil, &regions)
  166. if err != nil {
  167. return err
  168. }
  169. self.iregions = []cloudprovider.ICloudRegion{}
  170. for i := 0; i < len(regions); i++ {
  171. regions[i].client = self
  172. self.iregions = append(self.iregions, &regions[i])
  173. }
  174. // add global region
  175. self.iregions = append(self.iregions, self.GetGlobalRegion())
  176. objectstoreCapability := []string{
  177. cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE,
  178. }
  179. for _, region := range MultiRegions {
  180. _region := SRegion{
  181. Name: region,
  182. client: self,
  183. capabilities: objectstoreCapability,
  184. }
  185. self.iregions = append(self.iregions, &_region)
  186. }
  187. for _, region := range DualRegions {
  188. _region := SRegion{
  189. Name: region,
  190. client: self,
  191. capabilities: objectstoreCapability,
  192. }
  193. self.iregions = append(self.iregions, &_region)
  194. }
  195. return nil
  196. }
  197. func (self *SGoogleClient) fetchBuckets() error {
  198. buckets := []SBucket{}
  199. params := map[string]string{
  200. "project": self.projectId,
  201. }
  202. err := self.storageListAll("b", params, &buckets)
  203. if err != nil {
  204. return errors.Wrap(err, "storageList")
  205. }
  206. self.iBuckets = []cloudprovider.ICloudBucket{}
  207. for i := range buckets {
  208. region := self.GetRegion(buckets[i].GetLocation())
  209. if region == nil {
  210. log.Errorf("failed to found region for bucket %s", buckets[i].GetName())
  211. continue
  212. }
  213. buckets[i].region = region
  214. self.iBuckets = append(self.iBuckets, &buckets[i])
  215. }
  216. return nil
  217. }
  218. func (self *SGoogleClient) getIBuckets() ([]cloudprovider.ICloudBucket, error) {
  219. if self.iBuckets == nil {
  220. err := self.fetchBuckets()
  221. if err != nil {
  222. return nil, errors.Wrap(err, "fetchBuckets")
  223. }
  224. }
  225. return self.iBuckets, nil
  226. }
  227. func jsonRequest(client *http.Client, method httputils.THttpMethod, domain, apiVersion, resource string, params map[string]string, body jsonutils.JSONObject, debug bool) (jsonutils.JSONObject, error) {
  228. resource = strings.TrimPrefix(resource, fmt.Sprintf("%s/%s/", domain, apiVersion))
  229. if len(resource) == 0 {
  230. return nil, cloudprovider.ErrNotFound
  231. }
  232. _url := fmt.Sprintf("%s/%s/%s", domain, apiVersion, resource)
  233. values := url.Values{}
  234. for k, v := range params {
  235. values.Set(k, v)
  236. }
  237. if len(values) > 0 {
  238. _url = fmt.Sprintf("%s?%s", _url, values.Encode())
  239. }
  240. return _jsonRequest(client, method, _url, body, debug)
  241. }
  242. func (self *SGoogleClient) ecsGet(resourceType, id string, retval interface{}) error {
  243. params := map[string]string{
  244. "filter": fmt.Sprintf(`id="%s"`, id),
  245. }
  246. resource := fmt.Sprintf("aggregated/%s", resourceType)
  247. if strings.Contains(resourceType, "/") {
  248. resource = resourceType
  249. }
  250. resp, err := self.ecsList(resource, params)
  251. if err != nil {
  252. return errors.Wrapf(err, "ecsList")
  253. }
  254. if resp.Contains("items") {
  255. if strings.HasPrefix(resource, "aggregated/") {
  256. items, err := resp.GetMap("items")
  257. if err != nil {
  258. return errors.Wrapf(err, "resp.GetMap(items)")
  259. }
  260. for _, values := range items {
  261. if values.Contains(resourceType) {
  262. arr, err := values.GetArray(resourceType)
  263. if err != nil {
  264. return errors.Wrapf(err, "v.GetArray(%s)", resourceType)
  265. }
  266. for i := range arr {
  267. if _id, _ := arr[i].GetString("id"); _id == id {
  268. return arr[i].Unmarshal(retval)
  269. }
  270. }
  271. }
  272. }
  273. } else if strings.HasPrefix(resource, "global/") {
  274. items, err := resp.GetArray("items")
  275. if err != nil {
  276. return errors.Wrapf(err, "resp.GetMap(items)")
  277. }
  278. for i := range items {
  279. if _id, _ := items[i].GetString("id"); _id == id {
  280. return items[i].Unmarshal(retval)
  281. }
  282. }
  283. }
  284. }
  285. return errors.Wrapf(cloudprovider.ErrNotFound, "%s", id)
  286. }
  287. func (self *SGoogleClient) ecsList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
  288. return self._ecsList("GET", resource, params)
  289. }
  290. func (self *SGoogleClient) _ecsList(method, resource string, params map[string]string) (jsonutils.JSONObject, error) {
  291. if !strings.HasPrefix(resource, "projects/") {
  292. resource = fmt.Sprintf("projects/%s/%s", self.projectId, resource)
  293. }
  294. return jsonRequest(self.client, httputils.THttpMethod(method), GOOGLE_COMPUTE_DOMAIN, GOOGLE_API_VERSION, resource, params, nil, self.debug)
  295. }
  296. func (self *SGoogleClient) managerList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
  297. return jsonRequest(self.client, "GET", GOOGLE_MANAGER_DOMAIN, GOOGLE_MANAGER_API_VERSION, resource, params, nil, self.debug)
  298. }
  299. func (self *SGoogleClient) managerGet(resource string) (jsonutils.JSONObject, error) {
  300. return jsonRequest(self.client, "GET", GOOGLE_MANAGER_DOMAIN, GOOGLE_MANAGER_API_VERSION, resource, nil, nil, self.debug)
  301. }
  302. func (self *SGoogleClient) managerPost(resource string, params map[string]string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  303. return jsonRequest(self.client, "POST", GOOGLE_MANAGER_DOMAIN, GOOGLE_MANAGER_API_VERSION, resource, params, body, self.debug)
  304. }
  305. func (self *SGoogleClient) iamPost(resource string, params map[string]string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  306. return jsonRequest(self.client, "POST", GOOGLE_IAM_DOMAIN, GOOGLE_IAM_API_VERSION, resource, params, body, self.debug)
  307. }
  308. func (self *SGoogleClient) iamList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
  309. return jsonRequest(self.client, "GET", GOOGLE_IAM_DOMAIN, GOOGLE_IAM_API_VERSION, resource, params, nil, self.debug)
  310. }
  311. func (self *SGoogleClient) iamGet(resource string) (jsonutils.JSONObject, error) {
  312. return jsonRequest(self.client, "GET", GOOGLE_IAM_DOMAIN, GOOGLE_IAM_API_VERSION, resource, nil, nil, self.debug)
  313. }
  314. func (self *SGoogleClient) iamPatch(resource string, params map[string]string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  315. return jsonRequest(self.client, "PATCH", GOOGLE_IAM_DOMAIN, GOOGLE_IAM_API_VERSION, resource, params, body, self.debug)
  316. }
  317. func (self *SGoogleClient) iamDelete(id string, retval interface{}) error {
  318. resp, err := jsonRequest(self.client, "DELETE", GOOGLE_IAM_DOMAIN, GOOGLE_IAM_API_VERSION, id, nil, nil, self.debug)
  319. if err != nil {
  320. return err
  321. }
  322. if retval != nil {
  323. return resp.Unmarshal(retval)
  324. }
  325. return nil
  326. }
  327. func (self *SGoogleClient) iamListAll(resource string, params map[string]string, retval interface{}) error {
  328. if params == nil {
  329. params = map[string]string{}
  330. }
  331. items := jsonutils.NewArray()
  332. nextPageToken := ""
  333. params["pageSize"] = "100"
  334. for {
  335. params["pageToken"] = nextPageToken
  336. resp, err := self.iamList(resource, params)
  337. if err != nil {
  338. return errors.Wrap(err, "iamList")
  339. }
  340. if resp.Contains("roles") {
  341. _items, err := resp.GetArray("roles")
  342. if err != nil {
  343. return errors.Wrap(err, "resp.GetArray")
  344. }
  345. items.Add(_items...)
  346. }
  347. nextPageToken, _ = resp.GetString("nextPageToken")
  348. if len(nextPageToken) == 0 {
  349. break
  350. }
  351. }
  352. return items.Unmarshal(retval)
  353. }
  354. func (self *SGoogleClient) ecsListAll(resource string, params map[string]string, retval interface{}) error {
  355. return self._ecsListAll("GET", resource, params, retval)
  356. }
  357. func (self *SGoogleClient) _ecsListAll(method string, resource string, params map[string]string, retval interface{}) error {
  358. if params == nil {
  359. params = map[string]string{}
  360. }
  361. items := jsonutils.NewArray()
  362. nextPageToken := ""
  363. params["maxResults"] = "500"
  364. for {
  365. params["pageToken"] = nextPageToken
  366. resp, err := self._ecsList(method, resource, params)
  367. if err != nil {
  368. return errors.Wrap(err, "ecsList")
  369. }
  370. if resp.Contains("items") {
  371. _items, err := resp.GetArray("items")
  372. if err != nil {
  373. return errors.Wrap(err, "resp.GetArray")
  374. }
  375. items.Add(_items...)
  376. }
  377. nextPageToken, _ = resp.GetString("nextPageToken")
  378. if len(nextPageToken) == 0 {
  379. break
  380. }
  381. }
  382. return items.Unmarshal(retval)
  383. }
  384. func (self *SGoogleClient) ecsDelete(id string, retval interface{}) error {
  385. resp, err := jsonRequest(self.client, "DELETE", GOOGLE_COMPUTE_DOMAIN, GOOGLE_API_VERSION, id, nil, nil, self.debug)
  386. if err != nil {
  387. return err
  388. }
  389. if retval != nil {
  390. return resp.Unmarshal(retval)
  391. }
  392. return nil
  393. }
  394. func (self *SGoogleClient) ecsPatch(resource string, action string, params map[string]string, body jsonutils.JSONObject) (string, error) {
  395. if len(action) > 0 {
  396. resource = fmt.Sprintf("%s/%s", resource, action)
  397. }
  398. resp, err := jsonRequest(self.client, "PATCH", GOOGLE_COMPUTE_DOMAIN, GOOGLE_API_VERSION, resource, params, body, self.debug)
  399. if err != nil {
  400. return "", err
  401. }
  402. selfLink, _ := resp.GetString("selfLink")
  403. return selfLink, nil
  404. }
  405. func (self *SGoogleClient) ecsPost(resource string, action string, params map[string]string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  406. resource = fmt.Sprintf("%s/%s", resource, action)
  407. return jsonRequest(self.client, "POST", GOOGLE_COMPUTE_DOMAIN, GOOGLE_API_VERSION, resource, params, body, self.debug)
  408. }
  409. func (self *SGoogleClient) ecsDo(resource string, action string, params map[string]string, body jsonutils.JSONObject) (string, error) {
  410. resp, err := self.ecsPost(resource, action, params, body)
  411. if err != nil {
  412. return "", err
  413. }
  414. selfLink, _ := resp.GetString("selfLink")
  415. return selfLink, nil
  416. }
  417. func (self *SGoogleClient) rdsDelete(id string, retval interface{}) error {
  418. resp, err := jsonRequest(self.client, "DELETE", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, id, nil, nil, self.debug)
  419. if err != nil {
  420. return err
  421. }
  422. if retval != nil {
  423. return resp.Unmarshal(retval)
  424. }
  425. return nil
  426. }
  427. func (self *SGoogleClient) rdsDo(resource string, action string, params map[string]string, body jsonutils.JSONObject) (string, error) {
  428. resource = fmt.Sprintf("%s/%s", resource, action)
  429. resp, err := jsonRequest(self.client, "POST", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, params, body, self.debug)
  430. if err != nil {
  431. return "", err
  432. }
  433. selfLink, _ := resp.GetString("selfLink")
  434. return selfLink, nil
  435. }
  436. func (self *SGoogleClient) rdsPatch(resource string, body jsonutils.JSONObject) (string, error) {
  437. resp, err := jsonRequest(self.client, "PATCH", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, nil, body, self.debug)
  438. if err != nil {
  439. return "", err
  440. }
  441. selfLink, _ := resp.GetString("selfLink")
  442. return selfLink, nil
  443. }
  444. func (self *SGoogleClient) rdsUpdate(resource string, params map[string]string, body jsonutils.JSONObject) (string, error) {
  445. resource = fmt.Sprintf("projects/%s/%s", self.projectId, resource)
  446. resp, err := jsonRequest(self.client, "PUT", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, params, body, self.debug)
  447. if err != nil {
  448. return "", err
  449. }
  450. selfLink, _ := resp.GetString("selfLink")
  451. return selfLink, nil
  452. }
  453. func (self *SGoogleClient) checkAndSetName(body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  454. name, _ := body.GetString("name")
  455. if len(name) > 0 {
  456. generateName := ""
  457. for _, s := range strings.ToLower(name) {
  458. if unicode.IsLetter(s) || unicode.IsDigit(s) || s == '-' {
  459. generateName = fmt.Sprintf("%s%c", generateName, s)
  460. }
  461. }
  462. if strings.HasSuffix(generateName, "-") {
  463. generateName += "1"
  464. }
  465. if name != generateName {
  466. err := jsonutils.Update(body, map[string]string{"name": generateName})
  467. if err != nil {
  468. return nil, fmt.Errorf("faild to generate google name from %s -> %s", name, generateName)
  469. }
  470. }
  471. }
  472. return body, nil
  473. }
  474. func (self *SGoogleClient) ecsInsert(resource string, body jsonutils.JSONObject, retval interface{}) error {
  475. resource = fmt.Sprintf("projects/%s/%s", self.projectId, resource)
  476. var err error
  477. body, err = self.checkAndSetName(body)
  478. if err != nil {
  479. return errors.Wrap(err, "checkAndSetName")
  480. }
  481. params := map[string]string{"requestId": stringutils.UUID4()}
  482. resp, err := jsonRequest(self.client, "POST", GOOGLE_COMPUTE_DOMAIN, GOOGLE_API_VERSION, resource, params, body, self.debug)
  483. if err != nil {
  484. return err
  485. }
  486. if retval != nil {
  487. return resp.Unmarshal(retval)
  488. }
  489. return nil
  490. }
  491. func (self *SGoogleClient) storageInsert(resource string, body jsonutils.JSONObject, retval interface{}) error {
  492. resp, err := jsonRequest(self.client, "POST", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, nil, body, self.debug)
  493. if err != nil {
  494. return err
  495. }
  496. if retval != nil {
  497. return resp.Unmarshal(retval)
  498. }
  499. return nil
  500. }
  501. func (self *SGoogleClient) storageUpload(resource string, header http.Header, body io.Reader) (*http.Response, error) {
  502. resp, err := rawRequest(self.client, "POST", GOOGLE_STORAGE_UPLOAD_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, header, body, self.debug)
  503. if err != nil {
  504. return nil, errors.Wrap(err, "rawRequest")
  505. }
  506. if resp.StatusCode >= 400 {
  507. msg, _ := io.ReadAll(resp.Body)
  508. defer resp.Body.Close()
  509. return nil, fmt.Errorf("StatusCode: %d %s", resp.StatusCode, string(msg))
  510. }
  511. return resp, nil
  512. }
  513. func (self *SGoogleClient) storageUploadPart(resource string, header http.Header, body io.Reader) (*http.Response, error) {
  514. resp, err := rawRequest(self.client, "PUT", GOOGLE_STORAGE_UPLOAD_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, header, body, self.debug)
  515. if err != nil {
  516. return nil, errors.Wrap(err, "rawRequest")
  517. }
  518. if resp.StatusCode >= 400 {
  519. msg, _ := io.ReadAll(resp.Body)
  520. defer resp.Body.Close()
  521. return nil, fmt.Errorf("StatusCode: %d %s", resp.StatusCode, string(msg))
  522. }
  523. return resp, nil
  524. }
  525. func (self *SGoogleClient) storageAbortUpload(resource string) error {
  526. resp, err := rawRequest(self.client, "DELETE", GOOGLE_STORAGE_UPLOAD_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, nil, nil, self.debug)
  527. if err != nil {
  528. return errors.Wrap(err, "rawRequest")
  529. }
  530. defer resp.Body.Close()
  531. if resp.StatusCode >= 400 {
  532. msg, _ := io.ReadAll(resp.Body)
  533. return fmt.Errorf("StatusCode: %d %s", resp.StatusCode, string(msg))
  534. }
  535. return nil
  536. }
  537. func (self *SGoogleClient) storageDownload(resource string, header http.Header) (io.ReadCloser, error) {
  538. resp, err := rawRequest(self.client, "GET", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, header, nil, self.debug)
  539. if err != nil {
  540. return nil, errors.Wrap(err, "rawRequest")
  541. }
  542. defer resp.Body.Close()
  543. if resp.StatusCode >= 400 {
  544. msg, _ := io.ReadAll(resp.Body)
  545. return nil, fmt.Errorf("StatusCode: %d %s", resp.StatusCode, string(msg))
  546. }
  547. return resp.Body, err
  548. }
  549. func (self *SGoogleClient) storageList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
  550. return jsonRequest(self.client, "GET", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, params, nil, self.debug)
  551. }
  552. func (self *SGoogleClient) storageListAll(resource string, params map[string]string, retval interface{}) error {
  553. if params == nil {
  554. params = map[string]string{}
  555. }
  556. items := jsonutils.NewArray()
  557. nextPageToken := ""
  558. params["maxResults"] = "500"
  559. for {
  560. params["pageToken"] = nextPageToken
  561. resp, err := self.storageList(resource, params)
  562. if err != nil {
  563. return errors.Wrap(err, "storageList")
  564. }
  565. if resp.Contains("items") {
  566. _items, err := resp.GetArray("items")
  567. if err != nil {
  568. return errors.Wrap(err, "resp.GetArray")
  569. }
  570. items.Add(_items...)
  571. }
  572. nextPageToken, _ = resp.GetString("nextPageToken")
  573. if len(nextPageToken) == 0 {
  574. break
  575. }
  576. }
  577. return items.Unmarshal(retval)
  578. }
  579. func (self *SGoogleClient) storageGet(resource string, retval interface{}) error {
  580. resp, err := jsonRequest(self.client, "GET", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, nil, nil, self.debug)
  581. if err != nil {
  582. return err
  583. }
  584. if retval != nil {
  585. err = resp.Unmarshal(retval)
  586. if err != nil {
  587. return errors.Wrap(err, "resp.Unmarshal")
  588. }
  589. }
  590. return nil
  591. }
  592. func (self *SGoogleClient) storagePut(resource string, body jsonutils.JSONObject, retval interface{}) error {
  593. resp, err := jsonRequest(self.client, "PUT", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, nil, body, self.debug)
  594. if err != nil {
  595. return err
  596. }
  597. if retval != nil {
  598. err = resp.Unmarshal(retval)
  599. if err != nil {
  600. return errors.Wrap(err, "resp.Unmarshal")
  601. }
  602. }
  603. return nil
  604. }
  605. func (self *SGoogleClient) storageDelete(id string, retval interface{}) error {
  606. resp, err := jsonRequest(self.client, "DELETE", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, id, nil, nil, self.debug)
  607. if err != nil {
  608. return err
  609. }
  610. if retval != nil {
  611. return resp.Unmarshal(retval)
  612. }
  613. return nil
  614. }
  615. func (self *SGoogleClient) storageDo(resource string, action string, params map[string]string, body jsonutils.JSONObject) (string, error) {
  616. resource = fmt.Sprintf("%s/%s", resource, action)
  617. resp, err := jsonRequest(self.client, "POST", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, params, body, self.debug)
  618. if err != nil {
  619. return "", err
  620. }
  621. selfLink, _ := resp.GetString("selfLink")
  622. return selfLink, nil
  623. }
  624. func (self *SGoogleClient) cloudbuildGet(resource string, retval interface{}) error {
  625. resp, err := jsonRequest(self.client, "GET", GOOGLE_CLOUDBUILD_DOMAIN, GOOGLE_CLOUDBUILD_API_VERSION, resource, nil, nil, self.debug)
  626. if err != nil {
  627. return err
  628. }
  629. if retval != nil {
  630. err = resp.Unmarshal(retval)
  631. if err != nil {
  632. return errors.Wrap(err, "resp.Unmarshal")
  633. }
  634. }
  635. return nil
  636. }
  637. func (self *SGoogleClient) cloudbuildInsert(resource string, body jsonutils.JSONObject, retval interface{}) error {
  638. resp, err := jsonRequest(self.client, "POST", GOOGLE_CLOUDBUILD_DOMAIN, GOOGLE_CLOUDBUILD_API_VERSION, resource, nil, body, self.debug)
  639. if err != nil {
  640. return err
  641. }
  642. if retval != nil {
  643. return resp.Unmarshal(retval)
  644. }
  645. return nil
  646. }
  647. func (self *SGoogleClient) rdsInsert(resource string, body jsonutils.JSONObject, retval interface{}) error {
  648. resource = fmt.Sprintf("projects/%s/%s", self.projectId, resource)
  649. var err error
  650. body, err = self.checkAndSetName(body)
  651. if err != nil {
  652. return errors.Wrap(err, "checkAndSetName")
  653. }
  654. resp, err := jsonRequest(self.client, "POST", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, nil, body, self.debug)
  655. if err != nil {
  656. return err
  657. }
  658. if retval != nil {
  659. return resp.Unmarshal(retval)
  660. }
  661. return nil
  662. }
  663. func (self *SGoogleClient) rdsGet(resource string, retval interface{}) error {
  664. resp, err := jsonRequest(self.client, "GET", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, nil, nil, self.debug)
  665. if err != nil {
  666. return err
  667. }
  668. if retval != nil {
  669. err = resp.Unmarshal(retval)
  670. if err != nil {
  671. return errors.Wrap(err, "resp.Unmarshal")
  672. }
  673. }
  674. return nil
  675. }
  676. func (self *SGoogleClient) rdsList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
  677. resource = fmt.Sprintf("projects/%s/%s", self.projectId, resource)
  678. return jsonRequest(self.client, "GET", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, params, nil, self.debug)
  679. }
  680. func (self *SGoogleClient) rdsListAll(resource string, params map[string]string, retval interface{}) error {
  681. if params == nil {
  682. params = map[string]string{}
  683. }
  684. items := jsonutils.NewArray()
  685. nextPageToken := ""
  686. params["maxResults"] = "500"
  687. for {
  688. params["pageToken"] = nextPageToken
  689. resp, err := self.rdsList(resource, params)
  690. if err != nil {
  691. return errors.Wrap(err, "rdsList")
  692. }
  693. if resp.Contains("items") {
  694. _items, err := resp.GetArray("items")
  695. if err != nil {
  696. return errors.Wrap(err, "resp.GetArray")
  697. }
  698. items.Add(_items...)
  699. }
  700. nextPageToken, _ = resp.GetString("nextPageToken")
  701. if len(nextPageToken) == 0 {
  702. break
  703. }
  704. }
  705. return items.Unmarshal(retval)
  706. }
  707. func (self *SGoogleClient) billingList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
  708. return jsonRequest(self.client, "GET", GOOGLE_BILLING_DOMAIN, GOOGLE_BILLING_API_VERSION, resource, params, nil, self.debug)
  709. }
  710. func (self *SGoogleClient) billingListAll(resource string, params map[string]string, retval interface{}) error {
  711. if params == nil {
  712. params = map[string]string{}
  713. }
  714. items := jsonutils.NewArray()
  715. nextPageToken := ""
  716. params["pageSize"] = "5000"
  717. for {
  718. params["pageToken"] = nextPageToken
  719. resp, err := self.billingList(resource, params)
  720. if err != nil {
  721. return errors.Wrap(err, "billingList")
  722. }
  723. if resp.Contains("skus") {
  724. _items, err := resp.GetArray("skus")
  725. if err != nil {
  726. return errors.Wrap(err, "resp.GetArray")
  727. }
  728. items.Add(_items...)
  729. }
  730. nextPageToken, _ = resp.GetString("nextPageToken")
  731. if len(nextPageToken) == 0 {
  732. break
  733. }
  734. }
  735. return items.Unmarshal(retval)
  736. }
  737. func (self *SGoogleClient) monitorList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
  738. return jsonRequest(self.client, "GET", GOOGLE_MONITOR_DOMAIN, GOOGLE_MONITOR_API_VERSION, resource, params, nil, self.debug)
  739. }
  740. func (self *SGoogleClient) monitorListAll(resource string, params map[string]string) (*jsonutils.JSONArray, error) {
  741. if params == nil {
  742. params = map[string]string{}
  743. }
  744. timeSeries := jsonutils.NewArray()
  745. nextPageToken := ""
  746. params["pageSize"] = "5000"
  747. for {
  748. params["pageToken"] = nextPageToken
  749. resp, err := self.monitorList(resource, params)
  750. if err != nil {
  751. return nil, errors.Wrap(err, "monitorList")
  752. }
  753. if resp.Contains("timeSeries") {
  754. _series, err := resp.GetArray("timeSeries")
  755. if err != nil {
  756. return nil, errors.Wrap(err, "resp.GetTimeSeries")
  757. }
  758. timeSeries.Add(_series...)
  759. }
  760. nextPageToken, _ = resp.GetString("nextPageToken")
  761. if len(nextPageToken) == 0 {
  762. break
  763. }
  764. }
  765. return timeSeries, nil
  766. }
  767. func rawRequest(client *http.Client, method httputils.THttpMethod, domain, apiVersion string, resource string, header http.Header, body io.Reader, debug bool) (*http.Response, error) {
  768. resource = strings.TrimPrefix(resource, fmt.Sprintf("%s/%s/", domain, apiVersion))
  769. resource = fmt.Sprintf("%s/%s/%s", domain, apiVersion, resource)
  770. return httputils.Request(client, context.Background(), method, resource, header, body, debug)
  771. }
  772. /*
  773. "error": {
  774. "code": 400,
  775. "message": "Request contains an invalid argument.",
  776. "status": "INVALID_ARGUMENT",
  777. "details": [
  778. {
  779. "@type": "type.googleapis.com/google.cloudresourcemanager.v1.ProjectIamPolicyError",
  780. "type": "SOLO_MUST_INVITE_OWNERS",
  781. "member": "user:test",
  782. "role": "roles/owner"
  783. }
  784. ]
  785. }
  786. */
  787. type gError struct {
  788. ErrorInfo struct {
  789. Code int
  790. Message string
  791. Status string
  792. Details jsonutils.JSONObject
  793. } `json:"error"`
  794. Class string
  795. }
  796. func (g *gError) Error() string {
  797. return jsonutils.Marshal(g).String()
  798. }
  799. func (g *gError) ParseErrorFromJsonResponse(statusCode int, status string, body jsonutils.JSONObject) error {
  800. if body != nil {
  801. body.Unmarshal(g)
  802. }
  803. if g.ErrorInfo.Code == 0 {
  804. g.ErrorInfo.Code = statusCode
  805. }
  806. if g.ErrorInfo.Details == nil {
  807. g.ErrorInfo.Details = body
  808. }
  809. if len(g.Class) == 0 {
  810. g.Class = http.StatusText(statusCode)
  811. }
  812. if statusCode == 404 {
  813. return errors.Wrap(cloudprovider.ErrNotFound, g.Error())
  814. }
  815. return g
  816. }
  817. func _jsonRequest(cli *http.Client, method httputils.THttpMethod, url string, body jsonutils.JSONObject, debug bool) (jsonutils.JSONObject, error) {
  818. client := httputils.NewJsonClient(cli)
  819. req := httputils.NewJsonRequest(method, url, body)
  820. var err error
  821. var data jsonutils.JSONObject
  822. for i := 0; i < MAX_RETRY; i++ {
  823. var ge gError
  824. _, data, err = client.Send(context.Background(), req, &ge, debug)
  825. if err == nil {
  826. return data, nil
  827. }
  828. if body != nil {
  829. log.Errorf("%s %s params: %s error: %v", method, url, body.PrettyString(), err)
  830. } else {
  831. log.Errorf("%s %s error: %v", method, url, err)
  832. }
  833. retry := false
  834. for _, msg := range []string{
  835. "EOF",
  836. "i/o timeout",
  837. "TLS handshake timeout",
  838. "connection reset by peer",
  839. } {
  840. if strings.Index(err.Error(), msg) >= 0 {
  841. retry = true
  842. break
  843. }
  844. }
  845. if !retry {
  846. return nil, err
  847. }
  848. time.Sleep(time.Second * 10)
  849. }
  850. return nil, err
  851. }
  852. func (self *SGoogleClient) GetRegion(regionId string) *SRegion {
  853. if len(regionId) == 0 {
  854. regionId = GOOGLE_DEFAULT_REGION
  855. }
  856. for i := 0; i < len(self.iregions); i++ {
  857. if self.iregions[i].GetId() == regionId {
  858. return self.iregions[i].(*SRegion)
  859. }
  860. }
  861. return nil
  862. }
  863. func (client *SGoogleClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
  864. projects, err := client.GetProjects()
  865. if err != nil {
  866. return nil, errors.Wrap(err, "GetProjects")
  867. }
  868. accounts := []cloudprovider.SSubAccount{}
  869. for _, project := range projects {
  870. subAccount := cloudprovider.SSubAccount{}
  871. subAccount.Name = project.Name
  872. subAccount.Account = fmt.Sprintf("%s/%s", project.ProjectId, client.clientEmail)
  873. subAccount.Id = project.ProjectId
  874. subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
  875. if project.LifecycleState != "ACTIVE" {
  876. continue
  877. }
  878. accounts = append(accounts, subAccount)
  879. }
  880. return accounts, nil
  881. }
  882. func (self *SGoogleClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) {
  883. for i := 0; i < len(self.iregions); i++ {
  884. if self.iregions[i].GetGlobalId() == id {
  885. return self.iregions[i], nil
  886. }
  887. }
  888. return nil, cloudprovider.ErrNotFound
  889. }
  890. func (self *SGoogleClient) GetIRegions() ([]cloudprovider.ICloudRegion, error) {
  891. return self.iregions, nil
  892. }
  893. func (self *SGoogleClient) fetchGlobalNetwork() ([]SGlobalNetwork, error) {
  894. if len(self.globalnetworks) > 0 {
  895. return self.globalnetworks, nil
  896. }
  897. globalnetworks, err := self.GetGlobalNetworks(0, "")
  898. if err != nil {
  899. return nil, err
  900. }
  901. self.globalnetworks = globalnetworks
  902. return globalnetworks, nil
  903. }
  904. func (self *SGoogleClient) GetRegions() []SRegion {
  905. regions := make([]SRegion, len(self.iregions))
  906. for i := 0; i < len(regions); i++ {
  907. region := self.iregions[i].(*SRegion)
  908. regions[i] = *region
  909. }
  910. return regions
  911. }
  912. func (self *SGoogleClient) GetIProjects() ([]cloudprovider.ICloudProject, error) {
  913. projects, err := self.GetProjects()
  914. if err != nil {
  915. return nil, err
  916. }
  917. iprojects := []cloudprovider.ICloudProject{}
  918. for i := range projects {
  919. iprojects = append(iprojects, &projects[i])
  920. }
  921. return iprojects, nil
  922. }
  923. func (self *SGoogleClient) GetCapabilities() []string {
  924. caps := []string{
  925. // cloudprovider.CLOUD_CAPABILITY_PROJECT,
  926. cloudprovider.CLOUD_CAPABILITY_COMPUTE,
  927. cloudprovider.CLOUD_CAPABILITY_NETWORK,
  928. cloudprovider.CLOUD_CAPABILITY_SECURITY_GROUP,
  929. cloudprovider.CLOUD_CAPABILITY_EIP,
  930. cloudprovider.CLOUD_CAPABILITY_LOADBALANCER,
  931. cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE,
  932. cloudprovider.CLOUD_CAPABILITY_RDS,
  933. // cloudprovider.CLOUD_CAPABILITY_CACHE,
  934. // cloudprovider.CLOUD_CAPABILITY_EVENT,
  935. cloudprovider.CLOUD_CAPABILITY_CLOUDID,
  936. cloudprovider.CLOUD_CAPABILITY_QUOTA + cloudprovider.READ_ONLY_SUFFIX,
  937. cloudprovider.CLOUD_CAPABILITY_SNAPSHOT_POLICY + cloudprovider.READ_ONLY_SUFFIX,
  938. }
  939. return caps
  940. }
  941. func (self *SGoogleClient) GetSamlSpInitiatedLoginUrl(idpName string) string {
  942. // GOOGLE只支持一个IDP, 可以将organization名字存储在idpName里,避免因为权限不足,无法获取organization名称
  943. if len(idpName) == 0 {
  944. orgs, _ := self.ListOrganizations()
  945. if len(orgs) != 1 {
  946. log.Warningf("Organization count %d != 1, require assign the service account to ONE organization with organization viewer/admin role", len(orgs))
  947. } else {
  948. idpName = orgs[0].DisplayName
  949. }
  950. }
  951. if len(idpName) == 0 {
  952. log.Errorf("no valid organization name for this GCP account")
  953. return ""
  954. }
  955. return fmt.Sprintf("https://www.google.com/a/%s/ServiceLogin?continue=https://console.cloud.google.com", idpName)
  956. }