| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053 |
- // Copyright 2019 Yunion
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package google
- import (
- "context"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "strings"
- "time"
- "unicode"
- "golang.org/x/oauth2"
- "golang.org/x/oauth2/google"
- "golang.org/x/oauth2/jwt"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/util/httputils"
- "yunion.io/x/pkg/util/stringutils"
- api "yunion.io/x/cloudmux/pkg/apis/compute"
- "yunion.io/x/cloudmux/pkg/cloudprovider"
- )
- const (
- CLOUD_PROVIDER_GOOGLE = api.CLOUD_PROVIDER_GOOGLE
- CLOUD_PROVIDER_GOOGLE_CN = "谷歌云"
- GOOGLE_DEFAULT_REGION = "asia-east1"
- GOOGLE_API_VERSION = "v1"
- GOOGLE_MANAGER_API_VERSION = "v1"
- GOOGLE_STORAGE_API_VERSION = "v1"
- GOOGLE_CLOUDBUILD_API_VERSION = "v1"
- GOOGLE_BILLING_API_VERSION = "v1"
- GOOGLE_MONITOR_API_VERSION = "v3"
- GOOGLE_DBINSTANCE_API_VERSION = "v1beta4"
- GOOGLE_IAM_API_VERSION = "v1"
- GOOGLE_BIGQUERY_API_VERSION = "v2"
- GOOGLE_MANAGER_DOMAIN = "https://cloudresourcemanager.googleapis.com"
- GOOGLE_COMPUTE_DOMAIN = "https://www.googleapis.com/compute"
- GOOGLE_STORAGE_DOMAIN = "https://storage.googleapis.com/storage"
- GOOGLE_CLOUDBUILD_DOMAIN = "https://cloudbuild.googleapis.com"
- GOOGLE_STORAGE_UPLOAD_DOMAIN = "https://www.googleapis.com/upload/storage"
- GOOGLE_BILLING_DOMAIN = "https://cloudbilling.googleapis.com"
- GOOGLE_MONITOR_DOMAIN = "https://monitoring.googleapis.com"
- GOOGLE_DBINSTANCE_DOMAIN = "https://www.googleapis.com/sql"
- GOOGLE_IAM_DOMAIN = "https://iam.googleapis.com"
- GOOGLE_BIGQUERY_DOMAIN = "https://bigquery.googleapis.com/bigquery"
- MAX_RETRY = 3
- )
- var (
- MultiRegions []string = []string{"us", "eu", "asia"}
- DualRegions []string = []string{"nam4", "eur4"}
- )
- type GoogleClientConfig struct {
- cpcfg cloudprovider.ProviderConfig
- projectId string
- clientEmail string
- privateKeyId string
- privateKey string
- debug bool
- }
- func NewGoogleClientConfig(projectId, clientEmail, privateKeyId, privateKey string) *GoogleClientConfig {
- privateKey = strings.Replace(privateKey, "\\n", "\n", -1)
- cfg := &GoogleClientConfig{
- projectId: projectId,
- clientEmail: clientEmail,
- privateKeyId: privateKeyId,
- privateKey: privateKey,
- }
- return cfg
- }
- func (cfg *GoogleClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *GoogleClientConfig {
- cfg.cpcfg = cpcfg
- return cfg
- }
- func (cfg *GoogleClientConfig) Debug(debug bool) *GoogleClientConfig {
- cfg.debug = debug
- return cfg
- }
- type SGoogleClient struct {
- *GoogleClientConfig
- iregions []cloudprovider.ICloudRegion
- images []SImage
- snapshots map[string][]SSnapshot
- globalnetworks []SGlobalNetwork
- resourcepolices []SResourcePolicy
- client *http.Client
- iBuckets []cloudprovider.ICloudBucket
- }
- func NewGoogleClient(cfg *GoogleClientConfig) (*SGoogleClient, error) {
- client := SGoogleClient{
- GoogleClientConfig: cfg,
- }
- conf := &jwt.Config{
- Email: cfg.clientEmail,
- PrivateKeyID: cfg.privateKeyId,
- PrivateKey: []byte(cfg.privateKey),
- Scopes: []string{
- "https://www.googleapis.com/auth/cloud-platform",
- "https://www.googleapis.com/auth/compute",
- "https://www.googleapis.com/auth/compute.readonly",
- "https://www.googleapis.com/auth/cloud-platform.read-only",
- "https://www.googleapis.com/auth/cloudplatformprojects",
- "https://www.googleapis.com/auth/cloudplatformprojects.readonly",
- "https://www.googleapis.com/auth/devstorage.full_control",
- "https://www.googleapis.com/auth/devstorage.read_write",
- },
- TokenURL: google.JWTTokenURL,
- }
- httpClient := cfg.cpcfg.AdaptiveTimeoutHttpClient()
- ts, _ := httpClient.Transport.(*http.Transport)
- httpClient.Transport = cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response) error, error) {
- service := strings.Split(req.URL.Host, ".")[0]
- if service == "www" {
- service = strings.Split(req.URL.Path, "/")[0]
- }
- method, path := req.Method, req.URL.Path
- respCheck := func(resp *http.Response) error {
- if resp.StatusCode == 403 {
- if cfg.cpcfg.UpdatePermission != nil {
- cfg.cpcfg.UpdatePermission(service, fmt.Sprintf("%s %s", method, path))
- }
- }
- return nil
- }
- if cfg.cpcfg.ReadOnly {
- if req.Method == "GET" {
- return respCheck, nil
- }
- return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path)
- }
- return respCheck, nil
- })
- ctx := context.Background()
- ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
- client.client = conf.Client(ctx)
- return &client, client.fetchRegions()
- }
- func (self *SGoogleClient) GetAccountId() string {
- return self.clientEmail
- }
- func (self *SGoogleClient) GetGlobalRegion() *SGlobalRegion {
- return &SGlobalRegion{
- client: self,
- Description: "global",
- Kind: "compute#region",
- Name: "global",
- Status: "UP",
- SelfLink: "",
- CreationTimestamp: time.Time{},
- }
- }
- func (self *SGoogleClient) fetchRegions() error {
- regions := []SRegion{}
- err := self.ecsListAll("regions", nil, ®ions)
- if err != nil {
- return err
- }
- self.iregions = []cloudprovider.ICloudRegion{}
- for i := 0; i < len(regions); i++ {
- regions[i].client = self
- self.iregions = append(self.iregions, ®ions[i])
- }
- // add global region
- self.iregions = append(self.iregions, self.GetGlobalRegion())
- objectstoreCapability := []string{
- cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE,
- }
- for _, region := range MultiRegions {
- _region := SRegion{
- Name: region,
- client: self,
- capabilities: objectstoreCapability,
- }
- self.iregions = append(self.iregions, &_region)
- }
- for _, region := range DualRegions {
- _region := SRegion{
- Name: region,
- client: self,
- capabilities: objectstoreCapability,
- }
- self.iregions = append(self.iregions, &_region)
- }
- return nil
- }
- func (self *SGoogleClient) fetchBuckets() error {
- buckets := []SBucket{}
- params := map[string]string{
- "project": self.projectId,
- }
- err := self.storageListAll("b", params, &buckets)
- if err != nil {
- return errors.Wrap(err, "storageList")
- }
- self.iBuckets = []cloudprovider.ICloudBucket{}
- for i := range buckets {
- region := self.GetRegion(buckets[i].GetLocation())
- if region == nil {
- log.Errorf("failed to found region for bucket %s", buckets[i].GetName())
- continue
- }
- buckets[i].region = region
- self.iBuckets = append(self.iBuckets, &buckets[i])
- }
- return nil
- }
- func (self *SGoogleClient) getIBuckets() ([]cloudprovider.ICloudBucket, error) {
- if self.iBuckets == nil {
- err := self.fetchBuckets()
- if err != nil {
- return nil, errors.Wrap(err, "fetchBuckets")
- }
- }
- return self.iBuckets, nil
- }
- func jsonRequest(client *http.Client, method httputils.THttpMethod, domain, apiVersion, resource string, params map[string]string, body jsonutils.JSONObject, debug bool) (jsonutils.JSONObject, error) {
- resource = strings.TrimPrefix(resource, fmt.Sprintf("%s/%s/", domain, apiVersion))
- if len(resource) == 0 {
- return nil, cloudprovider.ErrNotFound
- }
- _url := fmt.Sprintf("%s/%s/%s", domain, apiVersion, resource)
- values := url.Values{}
- for k, v := range params {
- values.Set(k, v)
- }
- if len(values) > 0 {
- _url = fmt.Sprintf("%s?%s", _url, values.Encode())
- }
- return _jsonRequest(client, method, _url, body, debug)
- }
- func (self *SGoogleClient) ecsGet(resourceType, id string, retval interface{}) error {
- params := map[string]string{
- "filter": fmt.Sprintf(`id="%s"`, id),
- }
- resource := fmt.Sprintf("aggregated/%s", resourceType)
- if strings.Contains(resourceType, "/") {
- resource = resourceType
- }
- resp, err := self.ecsList(resource, params)
- if err != nil {
- return errors.Wrapf(err, "ecsList")
- }
- if resp.Contains("items") {
- if strings.HasPrefix(resource, "aggregated/") {
- items, err := resp.GetMap("items")
- if err != nil {
- return errors.Wrapf(err, "resp.GetMap(items)")
- }
- for _, values := range items {
- if values.Contains(resourceType) {
- arr, err := values.GetArray(resourceType)
- if err != nil {
- return errors.Wrapf(err, "v.GetArray(%s)", resourceType)
- }
- for i := range arr {
- if _id, _ := arr[i].GetString("id"); _id == id {
- return arr[i].Unmarshal(retval)
- }
- }
- }
- }
- } else if strings.HasPrefix(resource, "global/") {
- items, err := resp.GetArray("items")
- if err != nil {
- return errors.Wrapf(err, "resp.GetMap(items)")
- }
- for i := range items {
- if _id, _ := items[i].GetString("id"); _id == id {
- return items[i].Unmarshal(retval)
- }
- }
- }
- }
- return errors.Wrapf(cloudprovider.ErrNotFound, "%s", id)
- }
- func (self *SGoogleClient) ecsList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
- return self._ecsList("GET", resource, params)
- }
- func (self *SGoogleClient) _ecsList(method, resource string, params map[string]string) (jsonutils.JSONObject, error) {
- if !strings.HasPrefix(resource, "projects/") {
- resource = fmt.Sprintf("projects/%s/%s", self.projectId, resource)
- }
- return jsonRequest(self.client, httputils.THttpMethod(method), GOOGLE_COMPUTE_DOMAIN, GOOGLE_API_VERSION, resource, params, nil, self.debug)
- }
- func (self *SGoogleClient) managerList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
- return jsonRequest(self.client, "GET", GOOGLE_MANAGER_DOMAIN, GOOGLE_MANAGER_API_VERSION, resource, params, nil, self.debug)
- }
- func (self *SGoogleClient) managerGet(resource string) (jsonutils.JSONObject, error) {
- return jsonRequest(self.client, "GET", GOOGLE_MANAGER_DOMAIN, GOOGLE_MANAGER_API_VERSION, resource, nil, nil, self.debug)
- }
- func (self *SGoogleClient) managerPost(resource string, params map[string]string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
- return jsonRequest(self.client, "POST", GOOGLE_MANAGER_DOMAIN, GOOGLE_MANAGER_API_VERSION, resource, params, body, self.debug)
- }
- func (self *SGoogleClient) iamPost(resource string, params map[string]string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
- return jsonRequest(self.client, "POST", GOOGLE_IAM_DOMAIN, GOOGLE_IAM_API_VERSION, resource, params, body, self.debug)
- }
- func (self *SGoogleClient) iamList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
- return jsonRequest(self.client, "GET", GOOGLE_IAM_DOMAIN, GOOGLE_IAM_API_VERSION, resource, params, nil, self.debug)
- }
- func (self *SGoogleClient) iamGet(resource string) (jsonutils.JSONObject, error) {
- return jsonRequest(self.client, "GET", GOOGLE_IAM_DOMAIN, GOOGLE_IAM_API_VERSION, resource, nil, nil, self.debug)
- }
- func (self *SGoogleClient) iamPatch(resource string, params map[string]string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
- return jsonRequest(self.client, "PATCH", GOOGLE_IAM_DOMAIN, GOOGLE_IAM_API_VERSION, resource, params, body, self.debug)
- }
- func (self *SGoogleClient) iamDelete(id string, retval interface{}) error {
- resp, err := jsonRequest(self.client, "DELETE", GOOGLE_IAM_DOMAIN, GOOGLE_IAM_API_VERSION, id, nil, nil, self.debug)
- if err != nil {
- return err
- }
- if retval != nil {
- return resp.Unmarshal(retval)
- }
- return nil
- }
- func (self *SGoogleClient) iamListAll(resource string, params map[string]string, retval interface{}) error {
- if params == nil {
- params = map[string]string{}
- }
- items := jsonutils.NewArray()
- nextPageToken := ""
- params["pageSize"] = "100"
- for {
- params["pageToken"] = nextPageToken
- resp, err := self.iamList(resource, params)
- if err != nil {
- return errors.Wrap(err, "iamList")
- }
- if resp.Contains("roles") {
- _items, err := resp.GetArray("roles")
- if err != nil {
- return errors.Wrap(err, "resp.GetArray")
- }
- items.Add(_items...)
- }
- nextPageToken, _ = resp.GetString("nextPageToken")
- if len(nextPageToken) == 0 {
- break
- }
- }
- return items.Unmarshal(retval)
- }
- func (self *SGoogleClient) ecsListAll(resource string, params map[string]string, retval interface{}) error {
- return self._ecsListAll("GET", resource, params, retval)
- }
- func (self *SGoogleClient) _ecsListAll(method string, resource string, params map[string]string, retval interface{}) error {
- if params == nil {
- params = map[string]string{}
- }
- items := jsonutils.NewArray()
- nextPageToken := ""
- params["maxResults"] = "500"
- for {
- params["pageToken"] = nextPageToken
- resp, err := self._ecsList(method, resource, params)
- if err != nil {
- return errors.Wrap(err, "ecsList")
- }
- if resp.Contains("items") {
- _items, err := resp.GetArray("items")
- if err != nil {
- return errors.Wrap(err, "resp.GetArray")
- }
- items.Add(_items...)
- }
- nextPageToken, _ = resp.GetString("nextPageToken")
- if len(nextPageToken) == 0 {
- break
- }
- }
- return items.Unmarshal(retval)
- }
- func (self *SGoogleClient) ecsDelete(id string, retval interface{}) error {
- resp, err := jsonRequest(self.client, "DELETE", GOOGLE_COMPUTE_DOMAIN, GOOGLE_API_VERSION, id, nil, nil, self.debug)
- if err != nil {
- return err
- }
- if retval != nil {
- return resp.Unmarshal(retval)
- }
- return nil
- }
- func (self *SGoogleClient) ecsPatch(resource string, action string, params map[string]string, body jsonutils.JSONObject) (string, error) {
- if len(action) > 0 {
- resource = fmt.Sprintf("%s/%s", resource, action)
- }
- resp, err := jsonRequest(self.client, "PATCH", GOOGLE_COMPUTE_DOMAIN, GOOGLE_API_VERSION, resource, params, body, self.debug)
- if err != nil {
- return "", err
- }
- selfLink, _ := resp.GetString("selfLink")
- return selfLink, nil
- }
- func (self *SGoogleClient) ecsPost(resource string, action string, params map[string]string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
- resource = fmt.Sprintf("%s/%s", resource, action)
- return jsonRequest(self.client, "POST", GOOGLE_COMPUTE_DOMAIN, GOOGLE_API_VERSION, resource, params, body, self.debug)
- }
- func (self *SGoogleClient) ecsDo(resource string, action string, params map[string]string, body jsonutils.JSONObject) (string, error) {
- resp, err := self.ecsPost(resource, action, params, body)
- if err != nil {
- return "", err
- }
- selfLink, _ := resp.GetString("selfLink")
- return selfLink, nil
- }
- func (self *SGoogleClient) rdsDelete(id string, retval interface{}) error {
- resp, err := jsonRequest(self.client, "DELETE", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, id, nil, nil, self.debug)
- if err != nil {
- return err
- }
- if retval != nil {
- return resp.Unmarshal(retval)
- }
- return nil
- }
- func (self *SGoogleClient) rdsDo(resource string, action string, params map[string]string, body jsonutils.JSONObject) (string, error) {
- resource = fmt.Sprintf("%s/%s", resource, action)
- resp, err := jsonRequest(self.client, "POST", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, params, body, self.debug)
- if err != nil {
- return "", err
- }
- selfLink, _ := resp.GetString("selfLink")
- return selfLink, nil
- }
- func (self *SGoogleClient) rdsPatch(resource string, body jsonutils.JSONObject) (string, error) {
- resp, err := jsonRequest(self.client, "PATCH", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, nil, body, self.debug)
- if err != nil {
- return "", err
- }
- selfLink, _ := resp.GetString("selfLink")
- return selfLink, nil
- }
- func (self *SGoogleClient) rdsUpdate(resource string, params map[string]string, body jsonutils.JSONObject) (string, error) {
- resource = fmt.Sprintf("projects/%s/%s", self.projectId, resource)
- resp, err := jsonRequest(self.client, "PUT", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, params, body, self.debug)
- if err != nil {
- return "", err
- }
- selfLink, _ := resp.GetString("selfLink")
- return selfLink, nil
- }
- func (self *SGoogleClient) checkAndSetName(body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
- name, _ := body.GetString("name")
- if len(name) > 0 {
- generateName := ""
- for _, s := range strings.ToLower(name) {
- if unicode.IsLetter(s) || unicode.IsDigit(s) || s == '-' {
- generateName = fmt.Sprintf("%s%c", generateName, s)
- }
- }
- if strings.HasSuffix(generateName, "-") {
- generateName += "1"
- }
- if name != generateName {
- err := jsonutils.Update(body, map[string]string{"name": generateName})
- if err != nil {
- return nil, fmt.Errorf("faild to generate google name from %s -> %s", name, generateName)
- }
- }
- }
- return body, nil
- }
- func (self *SGoogleClient) ecsInsert(resource string, body jsonutils.JSONObject, retval interface{}) error {
- resource = fmt.Sprintf("projects/%s/%s", self.projectId, resource)
- var err error
- body, err = self.checkAndSetName(body)
- if err != nil {
- return errors.Wrap(err, "checkAndSetName")
- }
- params := map[string]string{"requestId": stringutils.UUID4()}
- resp, err := jsonRequest(self.client, "POST", GOOGLE_COMPUTE_DOMAIN, GOOGLE_API_VERSION, resource, params, body, self.debug)
- if err != nil {
- return err
- }
- if retval != nil {
- return resp.Unmarshal(retval)
- }
- return nil
- }
- func (self *SGoogleClient) storageInsert(resource string, body jsonutils.JSONObject, retval interface{}) error {
- resp, err := jsonRequest(self.client, "POST", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, nil, body, self.debug)
- if err != nil {
- return err
- }
- if retval != nil {
- return resp.Unmarshal(retval)
- }
- return nil
- }
- func (self *SGoogleClient) storageUpload(resource string, header http.Header, body io.Reader) (*http.Response, error) {
- resp, err := rawRequest(self.client, "POST", GOOGLE_STORAGE_UPLOAD_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, header, body, self.debug)
- if err != nil {
- return nil, errors.Wrap(err, "rawRequest")
- }
- if resp.StatusCode >= 400 {
- msg, _ := io.ReadAll(resp.Body)
- defer resp.Body.Close()
- return nil, fmt.Errorf("StatusCode: %d %s", resp.StatusCode, string(msg))
- }
- return resp, nil
- }
- func (self *SGoogleClient) storageUploadPart(resource string, header http.Header, body io.Reader) (*http.Response, error) {
- resp, err := rawRequest(self.client, "PUT", GOOGLE_STORAGE_UPLOAD_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, header, body, self.debug)
- if err != nil {
- return nil, errors.Wrap(err, "rawRequest")
- }
- if resp.StatusCode >= 400 {
- msg, _ := io.ReadAll(resp.Body)
- defer resp.Body.Close()
- return nil, fmt.Errorf("StatusCode: %d %s", resp.StatusCode, string(msg))
- }
- return resp, nil
- }
- func (self *SGoogleClient) storageAbortUpload(resource string) error {
- resp, err := rawRequest(self.client, "DELETE", GOOGLE_STORAGE_UPLOAD_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, nil, nil, self.debug)
- if err != nil {
- return errors.Wrap(err, "rawRequest")
- }
- defer resp.Body.Close()
- if resp.StatusCode >= 400 {
- msg, _ := io.ReadAll(resp.Body)
- return fmt.Errorf("StatusCode: %d %s", resp.StatusCode, string(msg))
- }
- return nil
- }
- func (self *SGoogleClient) storageDownload(resource string, header http.Header) (io.ReadCloser, error) {
- resp, err := rawRequest(self.client, "GET", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, header, nil, self.debug)
- if err != nil {
- return nil, errors.Wrap(err, "rawRequest")
- }
- defer resp.Body.Close()
- if resp.StatusCode >= 400 {
- msg, _ := io.ReadAll(resp.Body)
- return nil, fmt.Errorf("StatusCode: %d %s", resp.StatusCode, string(msg))
- }
- return resp.Body, err
- }
- func (self *SGoogleClient) storageList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
- return jsonRequest(self.client, "GET", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, params, nil, self.debug)
- }
- func (self *SGoogleClient) storageListAll(resource string, params map[string]string, retval interface{}) error {
- if params == nil {
- params = map[string]string{}
- }
- items := jsonutils.NewArray()
- nextPageToken := ""
- params["maxResults"] = "500"
- for {
- params["pageToken"] = nextPageToken
- resp, err := self.storageList(resource, params)
- if err != nil {
- return errors.Wrap(err, "storageList")
- }
- if resp.Contains("items") {
- _items, err := resp.GetArray("items")
- if err != nil {
- return errors.Wrap(err, "resp.GetArray")
- }
- items.Add(_items...)
- }
- nextPageToken, _ = resp.GetString("nextPageToken")
- if len(nextPageToken) == 0 {
- break
- }
- }
- return items.Unmarshal(retval)
- }
- func (self *SGoogleClient) storageGet(resource string, retval interface{}) error {
- resp, err := jsonRequest(self.client, "GET", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, nil, nil, self.debug)
- if err != nil {
- return err
- }
- if retval != nil {
- err = resp.Unmarshal(retval)
- if err != nil {
- return errors.Wrap(err, "resp.Unmarshal")
- }
- }
- return nil
- }
- func (self *SGoogleClient) storagePut(resource string, body jsonutils.JSONObject, retval interface{}) error {
- resp, err := jsonRequest(self.client, "PUT", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, nil, body, self.debug)
- if err != nil {
- return err
- }
- if retval != nil {
- err = resp.Unmarshal(retval)
- if err != nil {
- return errors.Wrap(err, "resp.Unmarshal")
- }
- }
- return nil
- }
- func (self *SGoogleClient) storageDelete(id string, retval interface{}) error {
- resp, err := jsonRequest(self.client, "DELETE", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, id, nil, nil, self.debug)
- if err != nil {
- return err
- }
- if retval != nil {
- return resp.Unmarshal(retval)
- }
- return nil
- }
- func (self *SGoogleClient) storageDo(resource string, action string, params map[string]string, body jsonutils.JSONObject) (string, error) {
- resource = fmt.Sprintf("%s/%s", resource, action)
- resp, err := jsonRequest(self.client, "POST", GOOGLE_STORAGE_DOMAIN, GOOGLE_STORAGE_API_VERSION, resource, params, body, self.debug)
- if err != nil {
- return "", err
- }
- selfLink, _ := resp.GetString("selfLink")
- return selfLink, nil
- }
- func (self *SGoogleClient) cloudbuildGet(resource string, retval interface{}) error {
- resp, err := jsonRequest(self.client, "GET", GOOGLE_CLOUDBUILD_DOMAIN, GOOGLE_CLOUDBUILD_API_VERSION, resource, nil, nil, self.debug)
- if err != nil {
- return err
- }
- if retval != nil {
- err = resp.Unmarshal(retval)
- if err != nil {
- return errors.Wrap(err, "resp.Unmarshal")
- }
- }
- return nil
- }
- func (self *SGoogleClient) cloudbuildInsert(resource string, body jsonutils.JSONObject, retval interface{}) error {
- resp, err := jsonRequest(self.client, "POST", GOOGLE_CLOUDBUILD_DOMAIN, GOOGLE_CLOUDBUILD_API_VERSION, resource, nil, body, self.debug)
- if err != nil {
- return err
- }
- if retval != nil {
- return resp.Unmarshal(retval)
- }
- return nil
- }
- func (self *SGoogleClient) rdsInsert(resource string, body jsonutils.JSONObject, retval interface{}) error {
- resource = fmt.Sprintf("projects/%s/%s", self.projectId, resource)
- var err error
- body, err = self.checkAndSetName(body)
- if err != nil {
- return errors.Wrap(err, "checkAndSetName")
- }
- resp, err := jsonRequest(self.client, "POST", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, nil, body, self.debug)
- if err != nil {
- return err
- }
- if retval != nil {
- return resp.Unmarshal(retval)
- }
- return nil
- }
- func (self *SGoogleClient) rdsGet(resource string, retval interface{}) error {
- resp, err := jsonRequest(self.client, "GET", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, nil, nil, self.debug)
- if err != nil {
- return err
- }
- if retval != nil {
- err = resp.Unmarshal(retval)
- if err != nil {
- return errors.Wrap(err, "resp.Unmarshal")
- }
- }
- return nil
- }
- func (self *SGoogleClient) rdsList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
- resource = fmt.Sprintf("projects/%s/%s", self.projectId, resource)
- return jsonRequest(self.client, "GET", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, params, nil, self.debug)
- }
- func (self *SGoogleClient) rdsListAll(resource string, params map[string]string, retval interface{}) error {
- if params == nil {
- params = map[string]string{}
- }
- items := jsonutils.NewArray()
- nextPageToken := ""
- params["maxResults"] = "500"
- for {
- params["pageToken"] = nextPageToken
- resp, err := self.rdsList(resource, params)
- if err != nil {
- return errors.Wrap(err, "rdsList")
- }
- if resp.Contains("items") {
- _items, err := resp.GetArray("items")
- if err != nil {
- return errors.Wrap(err, "resp.GetArray")
- }
- items.Add(_items...)
- }
- nextPageToken, _ = resp.GetString("nextPageToken")
- if len(nextPageToken) == 0 {
- break
- }
- }
- return items.Unmarshal(retval)
- }
- func (self *SGoogleClient) billingList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
- return jsonRequest(self.client, "GET", GOOGLE_BILLING_DOMAIN, GOOGLE_BILLING_API_VERSION, resource, params, nil, self.debug)
- }
- func (self *SGoogleClient) billingListAll(resource string, params map[string]string, retval interface{}) error {
- if params == nil {
- params = map[string]string{}
- }
- items := jsonutils.NewArray()
- nextPageToken := ""
- params["pageSize"] = "5000"
- for {
- params["pageToken"] = nextPageToken
- resp, err := self.billingList(resource, params)
- if err != nil {
- return errors.Wrap(err, "billingList")
- }
- if resp.Contains("skus") {
- _items, err := resp.GetArray("skus")
- if err != nil {
- return errors.Wrap(err, "resp.GetArray")
- }
- items.Add(_items...)
- }
- nextPageToken, _ = resp.GetString("nextPageToken")
- if len(nextPageToken) == 0 {
- break
- }
- }
- return items.Unmarshal(retval)
- }
- func (self *SGoogleClient) monitorList(resource string, params map[string]string) (jsonutils.JSONObject, error) {
- return jsonRequest(self.client, "GET", GOOGLE_MONITOR_DOMAIN, GOOGLE_MONITOR_API_VERSION, resource, params, nil, self.debug)
- }
- func (self *SGoogleClient) monitorListAll(resource string, params map[string]string) (*jsonutils.JSONArray, error) {
- if params == nil {
- params = map[string]string{}
- }
- timeSeries := jsonutils.NewArray()
- nextPageToken := ""
- params["pageSize"] = "5000"
- for {
- params["pageToken"] = nextPageToken
- resp, err := self.monitorList(resource, params)
- if err != nil {
- return nil, errors.Wrap(err, "monitorList")
- }
- if resp.Contains("timeSeries") {
- _series, err := resp.GetArray("timeSeries")
- if err != nil {
- return nil, errors.Wrap(err, "resp.GetTimeSeries")
- }
- timeSeries.Add(_series...)
- }
- nextPageToken, _ = resp.GetString("nextPageToken")
- if len(nextPageToken) == 0 {
- break
- }
- }
- return timeSeries, nil
- }
- func rawRequest(client *http.Client, method httputils.THttpMethod, domain, apiVersion string, resource string, header http.Header, body io.Reader, debug bool) (*http.Response, error) {
- resource = strings.TrimPrefix(resource, fmt.Sprintf("%s/%s/", domain, apiVersion))
- resource = fmt.Sprintf("%s/%s/%s", domain, apiVersion, resource)
- return httputils.Request(client, context.Background(), method, resource, header, body, debug)
- }
- /*
- "error": {
- "code": 400,
- "message": "Request contains an invalid argument.",
- "status": "INVALID_ARGUMENT",
- "details": [
- {
- "@type": "type.googleapis.com/google.cloudresourcemanager.v1.ProjectIamPolicyError",
- "type": "SOLO_MUST_INVITE_OWNERS",
- "member": "user:test",
- "role": "roles/owner"
- }
- ]
- }
- */
- type gError struct {
- ErrorInfo struct {
- Code int
- Message string
- Status string
- Details jsonutils.JSONObject
- } `json:"error"`
- Class string
- }
- func (g *gError) Error() string {
- return jsonutils.Marshal(g).String()
- }
- func (g *gError) ParseErrorFromJsonResponse(statusCode int, status string, body jsonutils.JSONObject) error {
- if body != nil {
- body.Unmarshal(g)
- }
- if g.ErrorInfo.Code == 0 {
- g.ErrorInfo.Code = statusCode
- }
- if g.ErrorInfo.Details == nil {
- g.ErrorInfo.Details = body
- }
- if len(g.Class) == 0 {
- g.Class = http.StatusText(statusCode)
- }
- if statusCode == 404 {
- return errors.Wrap(cloudprovider.ErrNotFound, g.Error())
- }
- return g
- }
- func _jsonRequest(cli *http.Client, method httputils.THttpMethod, url string, body jsonutils.JSONObject, debug bool) (jsonutils.JSONObject, error) {
- client := httputils.NewJsonClient(cli)
- req := httputils.NewJsonRequest(method, url, body)
- var err error
- var data jsonutils.JSONObject
- for i := 0; i < MAX_RETRY; i++ {
- var ge gError
- _, data, err = client.Send(context.Background(), req, &ge, debug)
- if err == nil {
- return data, nil
- }
- if body != nil {
- log.Errorf("%s %s params: %s error: %v", method, url, body.PrettyString(), err)
- } else {
- log.Errorf("%s %s error: %v", method, url, err)
- }
- retry := false
- for _, msg := range []string{
- "EOF",
- "i/o timeout",
- "TLS handshake timeout",
- "connection reset by peer",
- } {
- if strings.Index(err.Error(), msg) >= 0 {
- retry = true
- break
- }
- }
- if !retry {
- return nil, err
- }
- time.Sleep(time.Second * 10)
- }
- return nil, err
- }
- func (self *SGoogleClient) GetRegion(regionId string) *SRegion {
- if len(regionId) == 0 {
- regionId = GOOGLE_DEFAULT_REGION
- }
- for i := 0; i < len(self.iregions); i++ {
- if self.iregions[i].GetId() == regionId {
- return self.iregions[i].(*SRegion)
- }
- }
- return nil
- }
- func (client *SGoogleClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
- projects, err := client.GetProjects()
- if err != nil {
- return nil, errors.Wrap(err, "GetProjects")
- }
- accounts := []cloudprovider.SSubAccount{}
- for _, project := range projects {
- subAccount := cloudprovider.SSubAccount{}
- subAccount.Name = project.Name
- subAccount.Account = fmt.Sprintf("%s/%s", project.ProjectId, client.clientEmail)
- subAccount.Id = project.ProjectId
- subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
- if project.LifecycleState != "ACTIVE" {
- continue
- }
- accounts = append(accounts, subAccount)
- }
- return accounts, nil
- }
- func (self *SGoogleClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) {
- for i := 0; i < len(self.iregions); i++ {
- if self.iregions[i].GetGlobalId() == id {
- return self.iregions[i], nil
- }
- }
- return nil, cloudprovider.ErrNotFound
- }
- func (self *SGoogleClient) GetIRegions() ([]cloudprovider.ICloudRegion, error) {
- return self.iregions, nil
- }
- func (self *SGoogleClient) fetchGlobalNetwork() ([]SGlobalNetwork, error) {
- if len(self.globalnetworks) > 0 {
- return self.globalnetworks, nil
- }
- globalnetworks, err := self.GetGlobalNetworks(0, "")
- if err != nil {
- return nil, err
- }
- self.globalnetworks = globalnetworks
- return globalnetworks, nil
- }
- func (self *SGoogleClient) GetRegions() []SRegion {
- regions := make([]SRegion, len(self.iregions))
- for i := 0; i < len(regions); i++ {
- region := self.iregions[i].(*SRegion)
- regions[i] = *region
- }
- return regions
- }
- func (self *SGoogleClient) GetIProjects() ([]cloudprovider.ICloudProject, error) {
- projects, err := self.GetProjects()
- if err != nil {
- return nil, err
- }
- iprojects := []cloudprovider.ICloudProject{}
- for i := range projects {
- iprojects = append(iprojects, &projects[i])
- }
- return iprojects, nil
- }
- func (self *SGoogleClient) GetCapabilities() []string {
- caps := []string{
- // cloudprovider.CLOUD_CAPABILITY_PROJECT,
- cloudprovider.CLOUD_CAPABILITY_COMPUTE,
- cloudprovider.CLOUD_CAPABILITY_NETWORK,
- cloudprovider.CLOUD_CAPABILITY_SECURITY_GROUP,
- cloudprovider.CLOUD_CAPABILITY_EIP,
- cloudprovider.CLOUD_CAPABILITY_LOADBALANCER,
- cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE,
- cloudprovider.CLOUD_CAPABILITY_RDS,
- // cloudprovider.CLOUD_CAPABILITY_CACHE,
- // cloudprovider.CLOUD_CAPABILITY_EVENT,
- cloudprovider.CLOUD_CAPABILITY_CLOUDID,
- cloudprovider.CLOUD_CAPABILITY_QUOTA + cloudprovider.READ_ONLY_SUFFIX,
- cloudprovider.CLOUD_CAPABILITY_SNAPSHOT_POLICY + cloudprovider.READ_ONLY_SUFFIX,
- }
- return caps
- }
- func (self *SGoogleClient) GetSamlSpInitiatedLoginUrl(idpName string) string {
- // GOOGLE只支持一个IDP, 可以将organization名字存储在idpName里,避免因为权限不足,无法获取organization名称
- if len(idpName) == 0 {
- orgs, _ := self.ListOrganizations()
- if len(orgs) != 1 {
- log.Warningf("Organization count %d != 1, require assign the service account to ONE organization with organization viewer/admin role", len(orgs))
- } else {
- idpName = orgs[0].DisplayName
- }
- }
- if len(idpName) == 0 {
- log.Errorf("no valid organization name for this GCP account")
- return ""
- }
- return fmt.Sprintf("https://www.google.com/a/%s/ServiceLogin?continue=https://console.cloud.google.com", idpName)
- }
|