yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/azure/azure.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  // Copyright 2019 Yunion
    16  // Licensed under the Apache License, Version 2.0 (the "License");
    17  // you may not use this file except in compliance with the License.
    18  // You may obtain a copy of the License at
    19  //
    20  //     http://www.apache.org/licenses/LICENSE-2.0
    21  //
    22  // Unless required by applicable law or agreed to in writing, software
    23  // distributed under the License is distributed on an "AS IS" BASIS,
    24  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    25  // See the License for the specific language governing permissions and
    26  // limitations under the License.
    27  
    28  package azure
    29  
    30  import (
    31  	"context"
    32  	"fmt"
    33  	"net/http"
    34  	"net/url"
    35  	"strconv"
    36  	"strings"
    37  	"sync"
    38  	"time"
    39  
    40  	"github.com/Azure/go-autorest/autorest"
    41  	azureenv "github.com/Azure/go-autorest/autorest/azure"
    42  	"github.com/Azure/go-autorest/autorest/azure/auth"
    43  	"github.com/pkg/errors"
    44  	"golang.org/x/oauth2/clientcredentials"
    45  
    46  	"yunion.io/x/jsonutils"
    47  	"yunion.io/x/log"
    48  	"yunion.io/x/pkg/utils"
    49  
    50  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    51  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    52  	"yunion.io/x/onecloud/pkg/httperrors"
    53  	"yunion.io/x/onecloud/pkg/util/httputils"
    54  )
    55  
    56  const (
    57  	CLOUD_PROVIDER_AZURE    = api.CLOUD_PROVIDER_AZURE
    58  	CLOUD_PROVIDER_AZURE_CN = "微软"
    59  	CLOUD_PROVIDER_AZURE_EN = "Azure"
    60  
    61  	AZURE_API_VERSION = "2016-02-01"
    62  )
    63  
    64  type TAzureResource string
    65  
    66  var (
    67  	GraphResource        = TAzureResource("graph")
    68  	DefaultResource      = TAzureResource("default")
    69  	LoganalyticsResource = TAzureResource("loganalytics")
    70  )
    71  
    72  type azureAuthClient struct {
    73  	client *autorest.Client
    74  	domain string
    75  }
    76  
    77  type SAzureClient struct {
    78  	*AzureClientConfig
    79  
    80  	clientCache map[TAzureResource]*azureAuthClient
    81  	lock        sync.Mutex
    82  
    83  	ressourceGroups []SResourceGroup
    84  
    85  	regions  []SRegion
    86  	iBuckets []cloudprovider.ICloudBucket
    87  
    88  	subscriptions []SSubscription
    89  
    90  	debug bool
    91  
    92  	workspaces []SLoganalyticsWorkspace
    93  }
    94  
    95  type AzureClientConfig struct {
    96  	cpcfg cloudprovider.ProviderConfig
    97  
    98  	envName      string
    99  	tenantId     string
   100  	clientId     string
   101  	clientSecret string
   102  
   103  	subscriptionId string
   104  
   105  	debug bool
   106  }
   107  
   108  func NewAzureClientConfig(envName, tenantId, clientId, clientSecret string) *AzureClientConfig {
   109  	cfg := &AzureClientConfig{
   110  		envName:      envName,
   111  		tenantId:     tenantId,
   112  		clientId:     clientId,
   113  		clientSecret: clientSecret,
   114  	}
   115  	return cfg
   116  }
   117  
   118  func (cfg *AzureClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *AzureClientConfig {
   119  	cfg.cpcfg = cpcfg
   120  	return cfg
   121  }
   122  
   123  func (cfg *AzureClientConfig) SubscriptionId(id string) *AzureClientConfig {
   124  	cfg.subscriptionId = id
   125  	return cfg
   126  }
   127  
   128  func (cfg *AzureClientConfig) Debug(debug bool) *AzureClientConfig {
   129  	cfg.debug = debug
   130  	return cfg
   131  }
   132  
   133  func NewAzureClient(cfg *AzureClientConfig) (*SAzureClient, error) {
   134  	client := SAzureClient{
   135  		AzureClientConfig: cfg,
   136  		debug:             cfg.debug,
   137  		clientCache:       map[TAzureResource]*azureAuthClient{},
   138  	}
   139  	var err error
   140  	client.subscriptions, err = client.ListSubscriptions()
   141  	if err != nil {
   142  		return nil, errors.Wrap(err, "ListSubscriptions")
   143  	}
   144  	client.regions, err = client.ListRegions()
   145  	if err != nil {
   146  		return nil, errors.Wrapf(err, "ListRegions")
   147  	}
   148  	for i := range client.regions {
   149  		client.regions[i].client = &client
   150  	}
   151  	client.ressourceGroups, err = client.ListResourceGroups()
   152  	if err != nil {
   153  		return nil, errors.Wrapf(err, "ListResourceGroups")
   154  	}
   155  	return &client, nil
   156  }
   157  
   158  func (self *SAzureClient) getClient(resource TAzureResource) (*azureAuthClient, error) {
   159  	_client, ok := self.clientCache[resource]
   160  	if ok {
   161  		return _client, nil
   162  	}
   163  	ret := &azureAuthClient{}
   164  	client := autorest.NewClientWithUserAgent("Yunion API")
   165  	conf := auth.NewClientCredentialsConfig(self.clientId, self.clientSecret, self.tenantId)
   166  	env, err := azureenv.EnvironmentFromName(self.envName)
   167  	if err != nil {
   168  		return nil, errors.Wrapf(err, "azureenv.EnvironmentFromName(%s)", self.envName)
   169  	}
   170  
   171  	httpClient := self.cpcfg.AdaptiveTimeoutHttpClient()
   172  	transport, _ := httpClient.Transport.(*http.Transport)
   173  	httpClient.Transport = cloudprovider.GetCheckTransport(transport, func(req *http.Request) (func(resp *http.Response), error) {
   174  		if self.cpcfg.ReadOnly {
   175  			if req.Method == "GET" || (req.Method == "POST" && strings.HasSuffix(req.URL.Path, "oauth2/token")) {
   176  				return nil, nil
   177  			}
   178  			return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path)
   179  		}
   180  		return nil, nil
   181  	})
   182  	client.Sender = httpClient
   183  
   184  	switch resource {
   185  	case GraphResource:
   186  		ret.domain = env.GraphEndpoint
   187  		conf.Resource = env.GraphEndpoint
   188  	case LoganalyticsResource:
   189  		ret.domain = env.ResourceIdentifiers.OperationalInsights
   190  		conf.Resource = env.ResourceIdentifiers.OperationalInsights
   191  		if conf.Resource == "N/A" && self.envName == "AzureChinaCloud" {
   192  			ret.domain = "https://api.loganalytics.azure.cn"
   193  			conf.Resource = ret.domain
   194  		}
   195  	default:
   196  		ret.domain = env.ResourceManagerEndpoint
   197  		conf.Resource = env.ResourceManagerEndpoint
   198  	}
   199  	conf.AADEndpoint = env.ActiveDirectoryEndpoint
   200  	{
   201  		spt, err := conf.ServicePrincipalToken()
   202  		if err != nil {
   203  			return nil, errors.Wrapf(err, "ServicePrincipalToken")
   204  		}
   205  		spt.SetSender(httpClient)
   206  		client.Authorizer = autorest.NewBearerAuthorizer(spt)
   207  	}
   208  	if self.debug {
   209  		client.RequestInspector = LogRequest()
   210  	}
   211  	ret.client = &client
   212  	self.lock.Lock()
   213  	defer self.lock.Unlock()
   214  	self.clientCache[resource] = ret
   215  	return ret, nil
   216  }
   217  
   218  func (self *SAzureClient) getDefaultClient() (*azureAuthClient, error) {
   219  	return self.getClient(DefaultResource)
   220  }
   221  
   222  func (self *SAzureClient) getGraphClient() (*azureAuthClient, error) {
   223  	return self.getClient(GraphResource)
   224  }
   225  
   226  func (self *SAzureClient) getLoganalyticsClient() (*azureAuthClient, error) {
   227  	return self.getClient(LoganalyticsResource)
   228  }
   229  
   230  func (self *SAzureClient) jsonRequest(method, path string, body jsonutils.JSONObject, params url.Values, showErrorMsg bool) (jsonutils.JSONObject, error) {
   231  	cli, err := self.getDefaultClient()
   232  	if err != nil {
   233  		return nil, errors.Wrapf(err, "getDefaultClient")
   234  	}
   235  	defer func() {
   236  		if err != nil && showErrorMsg {
   237  			bj := ""
   238  			if body != nil {
   239  				bj = body.PrettyString()
   240  			}
   241  			log.Errorf("%s %s?%s \n%s error: %v", method, path, params.Encode(), bj, err)
   242  		}
   243  	}()
   244  	var resp jsonutils.JSONObject
   245  	for i := 0; i < 2; i++ {
   246  		resp, err = jsonRequest(cli.client, method, cli.domain, path, body, params, self.debug)
   247  		if err != nil {
   248  			if ae, ok := err.(*AzureResponseError); ok {
   249  				switch ae.AzureError.Code {
   250  				case "SubscriptionNotRegistered":
   251  					service := self.getService(path)
   252  					if len(service) == 0 {
   253  						return nil, err
   254  					}
   255  					re := self.ServiceRegister("Microsoft.Network")
   256  					if re != nil {
   257  						return nil, errors.Wrapf(re, "self.registerService(Microsoft.Network)")
   258  					}
   259  					continue
   260  				case "MissingSubscriptionRegistration":
   261  					for _, serviceType := range ae.AzureError.Details {
   262  						re := self.ServiceRegister(serviceType.Target)
   263  						if err != nil {
   264  							return nil, errors.Wrapf(re, "self.registerService(%s)", serviceType.Target)
   265  						}
   266  					}
   267  					continue
   268  				}
   269  			}
   270  			return resp, err
   271  		}
   272  		return resp, err
   273  	}
   274  	return resp, err
   275  }
   276  
   277  func (self *SAzureClient) ljsonRequest(method, path string, body jsonutils.JSONObject, params url.Values) (jsonutils.JSONObject, error) {
   278  	cli, err := self.getLoganalyticsClient()
   279  	if err != nil {
   280  		return nil, errors.Wrapf(err, "getLoganalyticsClient")
   281  	}
   282  	if params == nil {
   283  		params = url.Values{}
   284  	}
   285  	params.Set("api-version", "2021-12-01-preview")
   286  	return jsonRequest(cli.client, method, cli.domain, path, body, params, self.debug)
   287  }
   288  
   289  func (self *SAzureClient) gjsonRequest(method, path string, body jsonutils.JSONObject, params url.Values) (jsonutils.JSONObject, error) {
   290  	cli, err := self.getGraphClient()
   291  	if err != nil {
   292  		return nil, errors.Wrapf(err, "gjsonRequest")
   293  	}
   294  	if params == nil {
   295  		params = url.Values{}
   296  	}
   297  	params.Set("api-version", "1.6")
   298  	return jsonRequest(cli.client, method, cli.domain, path, body, params, self.debug)
   299  }
   300  
   301  func (self *SAzureClient) put(path string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
   302  	params := url.Values{}
   303  	params.Set("api-version", self._apiVersion(path, params))
   304  	return self.jsonRequest("PUT", path, body, params, true)
   305  }
   306  
   307  func (self *SAzureClient) post(path string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
   308  	params := url.Values{}
   309  	params.Set("api-version", self._apiVersion(path, params))
   310  	return self.jsonRequest("POST", path, body, params, true)
   311  }
   312  
   313  func (self *SAzureClient) patch(resource string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
   314  	params := url.Values{}
   315  	params.Set("api-version", self._apiVersion(resource, params))
   316  	return self.jsonRequest("PATCH", resource, body, params, true)
   317  }
   318  
   319  func (self *SAzureClient) _get(resourceId string, params url.Values, retVal interface{}, showErrorMsg bool) error {
   320  	if len(resourceId) == 0 {
   321  		return cloudprovider.ErrNotFound
   322  	}
   323  	if params == nil {
   324  		params = url.Values{}
   325  	}
   326  	params.Set("api-version", self._apiVersion(resourceId, params))
   327  	body, err := self.jsonRequest("GET", resourceId, nil, params, showErrorMsg)
   328  	if err != nil {
   329  		return err
   330  	}
   331  	err = body.Unmarshal(retVal)
   332  	if err != nil {
   333  		return err
   334  	}
   335  	return nil
   336  }
   337  
   338  func (self *SAzureClient) get(resourceId string, params url.Values, retVal interface{}) error {
   339  	return self._get(resourceId, params, retVal, true)
   340  }
   341  
   342  func (self *SAzureClient) gcreate(resource string, body jsonutils.JSONObject, retVal interface{}) error {
   343  	path := resource
   344  	result, err := self.msGraphRequest("POST", path, body)
   345  	if err != nil {
   346  		return errors.Wrapf(err, "msGraphRequest")
   347  	}
   348  	if retVal != nil {
   349  		return result.Unmarshal(retVal)
   350  	}
   351  	return nil
   352  }
   353  
   354  func (self *SAzureClient) gpatch(resource string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
   355  	return self.gjsonRequest("PATCH", resource, body, nil)
   356  }
   357  
   358  func (self *SAzureClient) glist(resource string, params url.Values, retVal interface{}) error {
   359  	if params == nil {
   360  		params = url.Values{}
   361  	}
   362  	err := self._glist(resource, params, retVal)
   363  	if err != nil {
   364  		return errors.Wrapf(err, "_glist(%s)", resource)
   365  	}
   366  	return nil
   367  }
   368  
   369  func (self *SAzureClient) _glist(resource string, params url.Values, retVal interface{}) error {
   370  	path := resource
   371  	if len(params) > 0 {
   372  		path = fmt.Sprintf("%s?%s", path, params.Encode())
   373  	}
   374  	body, err := self.msGraphRequest("GET", path, nil)
   375  	if err != nil {
   376  		return err
   377  	}
   378  	err = body.Unmarshal(retVal, "value")
   379  	if err != nil {
   380  		return errors.Wrapf(err, "body.Unmarshal")
   381  	}
   382  	return nil
   383  }
   384  
   385  func (self *SAzureClient) list(resource string, params url.Values, retVal interface{}) error {
   386  	if params == nil {
   387  		params = url.Values{}
   388  	}
   389  	result := []jsonutils.JSONObject{}
   390  	var key, skipToken string
   391  	for {
   392  		resp, err := self._list(resource, params)
   393  		if err != nil {
   394  			return errors.Wrapf(err, "_list(%s)", resource)
   395  		}
   396  		keys := []string{}
   397  		if resp.Contains("value") {
   398  			keys = []string{"value"}
   399  		}
   400  		part, err := resp.GetArray(keys...)
   401  		if err != nil {
   402  			return errors.Wrapf(err, "resp.GetArray(%s)", keys)
   403  		}
   404  		result = append(result, part...)
   405  		nextLink, _ := resp.GetString("nextLink")
   406  		if len(nextLink) == 0 {
   407  			break
   408  		}
   409  		link, err := url.Parse(nextLink)
   410  		if err != nil {
   411  			return errors.Wrapf(err, "url.Parse(%s)", nextLink)
   412  		}
   413  		prevSkipToken := params.Get(key)
   414  		key, skipToken = func() (string, string) {
   415  			for _, _key := range []string{"$skipToken", "$skiptoken"} {
   416  				tokens, ok := link.Query()[_key]
   417  				if ok {
   418  					for _, token := range tokens {
   419  						if len(token) > 0 && token != prevSkipToken {
   420  							return _key, token
   421  						}
   422  					}
   423  				}
   424  			}
   425  			return "", ""
   426  		}()
   427  		if len(skipToken) == 0 {
   428  			break
   429  		}
   430  		params.Del("$skipToken")
   431  		params.Del("$skiptoken")
   432  		params.Set(key, skipToken)
   433  	}
   434  	return jsonutils.Update(retVal, result)
   435  }
   436  
   437  func (self *SAzureClient) getService(path string) string {
   438  	for _, service := range []string{
   439  		"microsoft.compute",
   440  		"microsoft.classiccompute",
   441  		"microsoft.network",
   442  		"microsoft.classicnetwork",
   443  		"microsoft.storage",
   444  		"microsoft.classicstorage",
   445  		"microsoft.billing",
   446  		"microsoft.insights",
   447  		"microsoft.authorization",
   448  	} {
   449  		if strings.Contains(strings.ToLower(path), service) {
   450  			return service
   451  		}
   452  	}
   453  	return ""
   454  }
   455  
   456  func (self *SAzureClient) _apiVersion(resource string, params url.Values) string {
   457  	version := params.Get("api-version")
   458  	if len(version) > 0 {
   459  		return version
   460  	}
   461  	info := strings.Split(strings.ToLower(resource), "/")
   462  	if utils.IsInStringArray("microsoft.dbformariadb", info) {
   463  		return "2018-06-01-preview"
   464  	} else if utils.IsInStringArray("microsoft.dbformysql", info) {
   465  		if utils.IsInStringArray("flexibleservers", info) {
   466  			return "2020-07-01-privatepreview"
   467  		}
   468  		return "2017-12-01"
   469  	} else if utils.IsInStringArray("microsoft.dbforpostgresql", info) {
   470  		if utils.IsInStringArray("flexibleservers", info) {
   471  			return "2020-02-14-preview"
   472  		}
   473  		return "2017-12-01"
   474  	} else if utils.IsInStringArray("microsoft.sql", info) {
   475  		return "2020-08-01-preview"
   476  	} else if utils.IsInStringArray("microsoft.compute", info) {
   477  		if utils.IsInStringArray("tags", info) {
   478  			return "2020-06-01"
   479  		}
   480  		if utils.IsInStringArray("publishers", info) {
   481  			return "2020-06-01"
   482  		}
   483  		if utils.IsInStringArray("virtualmachines", info) {
   484  			return "2021-11-01"
   485  		}
   486  		if utils.IsInStringArray("skus", info) {
   487  			return "2019-04-01"
   488  		}
   489  		return "2018-06-01"
   490  	} else if utils.IsInStringArray("microsoft.classiccompute", info) {
   491  		return "2016-04-01"
   492  	} else if utils.IsInStringArray("microsoft.network", info) {
   493  		if utils.IsInStringArray("virtualnetworks", info) {
   494  			return "2018-08-01"
   495  		}
   496  		if utils.IsInStringArray("publicipaddresses", info) {
   497  			return "2018-03-01"
   498  		}
   499  		if utils.IsInStringArray("frontdoorwebapplicationfirewallmanagedrulesets", info) {
   500  			return "2020-11-01"
   501  		}
   502  		if utils.IsInStringArray("frontdoorwebapplicationfirewallpolicies", info) {
   503  			return "2020-11-01"
   504  		}
   505  		if utils.IsInStringArray("applicationgatewaywebapplicationfirewallpolicies", info) {
   506  			return "2021-01-01"
   507  		}
   508  		if utils.IsInStringArray("applicationgatewayavailablewafrulesets", info) {
   509  			return "2018-06-01"
   510  		}
   511  		return "2018-06-01"
   512  	} else if utils.IsInStringArray("microsoft.classicnetwork", info) {
   513  		return "2016-04-01"
   514  	} else if utils.IsInStringArray("microsoft.storage", info) {
   515  		if utils.IsInStringArray("storageaccounts", info) {
   516  			return "2016-12-01"
   517  		}
   518  		if utils.IsInStringArray("checknameavailability", info) {
   519  			return "2019-04-01"
   520  		}
   521  		if utils.IsInStringArray("skus", info) {
   522  			return "2019-04-01"
   523  		}
   524  		if utils.IsInStringArray("usages", info) {
   525  			return "2018-07-01"
   526  		}
   527  	} else if utils.IsInStringArray("microsoft.classicstorage", info) {
   528  		if utils.IsInStringArray("storageaccounts", info) {
   529  			return "2016-04-01"
   530  		}
   531  	} else if utils.IsInStringArray("microsoft.billing", info) {
   532  		return "2018-03-01-preview"
   533  	} else if utils.IsInStringArray("microsoft.insights", info) {
   534  		return "2017-03-01-preview"
   535  	} else if utils.IsInStringArray("microsoft.authorization", info) {
   536  		return "2018-01-01-preview"
   537  	} else if utils.IsInStringArray("microsoft.cache", info) {
   538  		if utils.IsInStringArray("redisenterprise", info) {
   539  			return "2021-03-01"
   540  		}
   541  		return "2020-06-01"
   542  	} else if utils.IsInStringArray("microsoft.containerservice", info) {
   543  		return "2021-05-01"
   544  	} else if utils.IsInStringArray("microsoft.operationalinsights", info) {
   545  		return "2021-12-01-preview"
   546  	}
   547  	return AZURE_API_VERSION
   548  }
   549  
   550  func (self *SAzureClient) _subscriptionId() string {
   551  	if len(self.subscriptionId) > 0 {
   552  		return self.subscriptionId
   553  	}
   554  	for _, sub := range self.subscriptions {
   555  		if sub.State == "Enabled" {
   556  			return sub.SubscriptionId
   557  		}
   558  	}
   559  	return ""
   560  }
   561  
   562  func (self *SAzureClient) _list(resource string, params url.Values) (jsonutils.JSONObject, error) {
   563  	subId := self._subscriptionId()
   564  	path := "subscriptions"
   565  	switch resource {
   566  	case "subscriptions", "providers/Microsoft.Billing/enrollmentAccounts":
   567  		path = resource
   568  	case "locations", "resourcegroups", "providers":
   569  		if len(subId) == 0 {
   570  			return nil, fmt.Errorf("no avaiable subscriptions")
   571  		}
   572  		path = fmt.Sprintf("subscriptions/%s/%s", subId, resource)
   573  	case "Microsoft.Network/frontdoorWebApplicationFirewallPolicies":
   574  		path = fmt.Sprintf("subscriptions/%s/resourceGroups/%s/providers/%s", subId, params.Get("resourceGroups"), resource)
   575  		params.Del("resourceGroups")
   576  	default:
   577  		if strings.HasPrefix(resource, "subscriptions/") || strings.HasPrefix(resource, "/subscriptions/") {
   578  			path = resource
   579  		} else {
   580  			if len(subId) == 0 {
   581  				return nil, fmt.Errorf("no avaiable subscriptions")
   582  			}
   583  			path = fmt.Sprintf("subscriptions/%s/providers/%s", subId, resource)
   584  		}
   585  	}
   586  	params.Set("api-version", self._apiVersion(resource, params))
   587  	return self.jsonRequest("GET", path, nil, params, true)
   588  }
   589  
   590  func (self *SAzureClient) del(resourceId string) error {
   591  	params := url.Values{}
   592  	params.Set("api-version", self._apiVersion(resourceId, params))
   593  	_, err := self.jsonRequest("DELETE", resourceId, nil, params, true)
   594  	return err
   595  }
   596  
   597  func (self *SAzureClient) GDelete(resourceId string) error {
   598  	return self.gdel(resourceId)
   599  }
   600  
   601  func (self *SAzureClient) gdel(resourceId string) error {
   602  	_, err := self.msGraphRequest("DELETE", resourceId, nil)
   603  	if err != nil {
   604  		return errors.Wrapf(err, "gdel(%s)", resourceId)
   605  	}
   606  	return nil
   607  }
   608  
   609  func (self *SAzureClient) perform(resourceId string, action string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
   610  	path := fmt.Sprintf("%s/%s", resourceId, action)
   611  	return self.post(path, body)
   612  }
   613  
   614  func (self *SAzureClient) CreateIProject(name string) (cloudprovider.ICloudProject, error) {
   615  	if len(self.regions) > 0 {
   616  		_, err := self.regions[0].CreateResourceGroup(name)
   617  		if err != nil {
   618  			return nil, errors.Wrapf(err, "CreateResourceGroup")
   619  		}
   620  		return self.regions[0].GetResourceGroupDetail(name)
   621  	}
   622  	return nil, fmt.Errorf("no region found ???")
   623  }
   624  
   625  func (self *SAzureClient) ListResourceGroups() ([]SResourceGroup, error) {
   626  	resourceGroups := []SResourceGroup{}
   627  	err := self.list("resourcegroups", url.Values{}, &resourceGroups)
   628  	if err != nil {
   629  		return nil, errors.Wrap(err, "list")
   630  	}
   631  	for i := range resourceGroups {
   632  		resourceGroups[i].client = self
   633  		resourceGroups[i].subId = self.subscriptionId
   634  	}
   635  	return resourceGroups, nil
   636  }
   637  
   638  type AzureErrorDetail struct {
   639  	Code    string `json:"code,omitempty"`
   640  	Message string `json:"message,omitempty"`
   641  	Target  string `json:"target,omitempty"`
   642  }
   643  
   644  type AzureError struct {
   645  	Code    string             `json:"code,omitempty"`
   646  	Details []AzureErrorDetail `json:"details,omitempty"`
   647  	Message string             `json:"message,omitempty"`
   648  }
   649  
   650  func (e *AzureError) Error() string {
   651  	return jsonutils.Marshal(e).String()
   652  }
   653  
   654  func (self *SAzureClient) getUniqName(resourceGroup, resourceType, name string) (string, error) {
   655  	prefix := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s/", self.subscriptionId, resourceGroup, resourceType)
   656  	newName := name
   657  	for i := 0; i < 20; i++ {
   658  		err := self._get(prefix+newName, nil, url.Values{}, false)
   659  		if errors.Cause(err) == cloudprovider.ErrNotFound {
   660  			return newName, nil
   661  		}
   662  		info := strings.Split(newName, "-")
   663  		num, err := strconv.Atoi(info[len(info)-1])
   664  		if err != nil {
   665  			info = append(info, "1")
   666  		} else {
   667  			info[len(info)-1] = fmt.Sprintf("%d", num+1)
   668  		}
   669  		newName = strings.Join(info, "-")
   670  	}
   671  	return "", fmt.Errorf("not find uniq name for %s[%s]", resourceType, name)
   672  }
   673  
   674  func (self *SAzureClient) create(resourceGroup, resourceType, name string, body jsonutils.JSONObject, retVal interface{}) error {
   675  	resource := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s/%s", self.subscriptionId, resourceGroup, resourceType, name)
   676  	params := url.Values{}
   677  	params.Set("api-version", self._apiVersion(resourceType, params))
   678  	resp, err := self.jsonRequest("PUT", resource, body, params, true)
   679  	if err != nil {
   680  		return errors.Wrapf(err, "jsonRequest")
   681  	}
   682  	if retVal != nil {
   683  		return resp.Unmarshal(retVal)
   684  	}
   685  	return nil
   686  }
   687  
   688  func (self *SAzureClient) CheckNameAvailability(resourceType, name string) (bool, error) {
   689  	path := fmt.Sprintf("/subscriptions/%s/providers/%s/checkNameAvailability", self.subscriptionId, strings.Split(resourceType, "/")[0])
   690  	body := map[string]string{
   691  		"Name": name,
   692  		"Type": resourceType,
   693  	}
   694  	resp, err := self.post(path, jsonutils.Marshal(body))
   695  	if err != nil {
   696  		return false, errors.Wrapf(err, "post(%s)", path)
   697  	}
   698  	output := sNameAvailableOutput{}
   699  	err = resp.Unmarshal(&output)
   700  	if err != nil {
   701  		return false, errors.Wrap(err, "resp.Unmarshal")
   702  	}
   703  	if output.NameAvailable {
   704  		return true, nil
   705  	}
   706  	if output.Reason == "AlreadyExists" {
   707  		return false, nil
   708  	}
   709  	return true, nil
   710  }
   711  
   712  func (self *SAzureClient) update(body jsonutils.JSONObject, retVal interface{}) error {
   713  	id := jsonutils.GetAnyString(body, []string{"Id", "id", "ID"})
   714  	if len(id) == 0 {
   715  		return fmt.Errorf("failed to found id for update operation")
   716  	}
   717  	params := url.Values{}
   718  	params.Set("api-version", self._apiVersion(id, params))
   719  	resp, err := self.jsonRequest("PUT", id, body, params, true)
   720  	if err != nil {
   721  		return err
   722  	}
   723  	if retVal != nil {
   724  		return resp.Unmarshal(retVal)
   725  	}
   726  	return nil
   727  }
   728  
   729  func jsonRequest(client *autorest.Client, method, domain, baseUrl string, body jsonutils.JSONObject, params url.Values, debug bool) (jsonutils.JSONObject, error) {
   730  	result, err := _jsonRequest(client, method, domain, baseUrl, body, params, debug)
   731  	if err != nil {
   732  		return nil, err
   733  	}
   734  	return result, nil
   735  }
   736  
   737  // {"odata.error":{"code":"Authorization_RequestDenied","message":{"lang":"en","value":"Insufficient privileges to complete the operation."},"requestId":"b776ba11-5cae-4fb9-b80d-29552e3caedd","date":"2020-10-29T09:05:23"}}
   738  type sMessage struct {
   739  	Lang  string
   740  	Value string
   741  }
   742  type sOdataError struct {
   743  	Code      string
   744  	Message   sMessage
   745  	RequestId string
   746  	Date      time.Time
   747  }
   748  type AzureResponseError struct {
   749  	OdataError sOdataError `json:"odata.error"`
   750  	AzureError AzureError  `json:"error"`
   751  	Code       string
   752  	Message    string
   753  }
   754  
   755  func (ae AzureResponseError) Error() string {
   756  	return jsonutils.Marshal(ae).String()
   757  }
   758  
   759  func (ae *AzureResponseError) ParseErrorFromJsonResponse(statusCode int, body jsonutils.JSONObject) error {
   760  	if body != nil {
   761  		body.Unmarshal(ae)
   762  	}
   763  	if statusCode == 404 {
   764  		msg := ""
   765  		if body != nil {
   766  			msg = body.String()
   767  		}
   768  		return errors.Wrap(cloudprovider.ErrNotFound, msg)
   769  	}
   770  	if len(ae.OdataError.Code) > 0 || len(ae.AzureError.Code) > 0 || (len(ae.Code) > 0 && len(ae.Message) > 0) {
   771  		return ae
   772  	}
   773  	return nil
   774  }
   775  
   776  func _jsonRequest(client *autorest.Client, method, domain, path string, body jsonutils.JSONObject, params url.Values, debug bool) (jsonutils.JSONObject, error) {
   777  	uri := fmt.Sprintf("%s/%s?%s", strings.TrimSuffix(domain, "/"), strings.TrimPrefix(path, "/"), params.Encode())
   778  	req := httputils.NewJsonRequest(httputils.THttpMethod(method), uri, body)
   779  	ae := AzureResponseError{}
   780  	cli := httputils.NewJsonClient(client)
   781  	header, body, err := cli.Send(context.TODO(), req, &ae, debug)
   782  	if err != nil {
   783  		if strings.Contains(err.Error(), "azure.BearerAuthorizer#WithAuthorization") {
   784  			return nil, errors.Wrapf(httperrors.ErrInvalidAccessKey, err.Error())
   785  		}
   786  		return nil, err
   787  	}
   788  	locationFunc := func(head http.Header) string {
   789  		for _, k := range []string{"Azure-Asyncoperation", "Location"} {
   790  			link := head.Get(k)
   791  			if len(link) > 0 {
   792  				return link
   793  			}
   794  		}
   795  		return ""
   796  	}
   797  	location := locationFunc(header)
   798  	if len(location) > 0 && (body == nil || body.IsZero() || !body.Contains("id")) {
   799  		err = cloudprovider.Wait(time.Second*10, time.Minute*30, func() (bool, error) {
   800  			locationUrl, err := url.Parse(location)
   801  			if err != nil {
   802  				return false, errors.Wrapf(err, "url.Parse(%s)", location)
   803  			}
   804  			if len(locationUrl.Query().Get("api-version")) == 0 {
   805  				q, _ := url.ParseQuery(locationUrl.RawQuery)
   806  				q.Set("api-version", params.Get("api-version"))
   807  				locationUrl.RawQuery = q.Encode()
   808  			}
   809  			req := httputils.NewJsonRequest(httputils.GET, locationUrl.String(), nil)
   810  			lae := AzureResponseError{}
   811  			_header, _body, _err := cli.Send(context.TODO(), req, &lae, debug)
   812  			if _err != nil {
   813  				if utils.IsInStringArray(lae.AzureError.Code, []string{"OSProvisioningTimedOut", "OSProvisioningClientError", "OSProvisioningInternalError"}) {
   814  					body = _body
   815  					return true, nil
   816  				}
   817  				return false, errors.Wrapf(_err, "cli.Send(%s)", location)
   818  			}
   819  			if retryAfter := _header.Get("Retry-After"); len(retryAfter) > 0 {
   820  				sleepTime, _ := strconv.Atoi(retryAfter)
   821  				time.Sleep(time.Second * time.Duration(sleepTime))
   822  				return false, nil
   823  			}
   824  			if _body != nil {
   825  				task := struct {
   826  					Status     string
   827  					Properties struct {
   828  						Output *jsonutils.JSONDict
   829  					}
   830  				}{}
   831  				_body.Unmarshal(&task)
   832  				if len(task.Status) == 0 {
   833  					body = _body
   834  					return true, nil
   835  				}
   836  				switch task.Status {
   837  				case "InProgress":
   838  					log.Debugf("process %s %s InProgress", method, path)
   839  					return false, nil
   840  				case "Succeeded":
   841  					log.Debugf("process %s %s Succeeded", method, path)
   842  					if task.Properties.Output != nil {
   843  						body = task.Properties.Output
   844  					}
   845  					return true, nil
   846  				case "Failed":
   847  					return false, fmt.Errorf("%s %s failed", method, path)
   848  				default:
   849  					return false, fmt.Errorf("Unknow status %s %s %s", task.Status, method, path)
   850  				}
   851  			}
   852  			return false, nil
   853  		})
   854  		if err != nil {
   855  			return nil, errors.Wrapf(err, "time out for waiting %s %s", method, uri)
   856  		}
   857  	}
   858  	return body, nil
   859  }
   860  
   861  func (self *SAzureClient) ListRegions() ([]SRegion, error) {
   862  	regions := []SRegion{}
   863  	err := self.list("locations", url.Values{}, &regions)
   864  	return regions, err
   865  }
   866  
   867  func (self *SAzureClient) GetRegions() []SRegion {
   868  	return self.regions
   869  }
   870  
   871  func (self *SAzureClient) GetSubAccounts() (subAccounts []cloudprovider.SSubAccount, err error) {
   872  	subAccounts = make([]cloudprovider.SSubAccount, len(self.subscriptions))
   873  	for i, subscription := range self.subscriptions {
   874  		subAccounts[i].Account = fmt.Sprintf("%s/%s", self.tenantId, subscription.SubscriptionId)
   875  		subAccounts[i].Name = subscription.DisplayName
   876  		subAccounts[i].HealthStatus = subscription.GetHealthStatus()
   877  	}
   878  	return subAccounts, nil
   879  }
   880  
   881  func (self *SAzureClient) GetAccountId() string {
   882  	return self.tenantId
   883  }
   884  
   885  func (self *SAzureClient) GetIamLoginUrl() string {
   886  	switch self.envName {
   887  	case "AzureChinaCloud":
   888  		return "http://portal.azure.cn"
   889  	default:
   890  		return "http://portal.azure.com"
   891  	}
   892  }
   893  
   894  func (self *SAzureClient) GetIRegions() []cloudprovider.ICloudRegion {
   895  	ret := []cloudprovider.ICloudRegion{}
   896  	for i := range self.regions {
   897  		ret = append(ret, &self.regions[i])
   898  	}
   899  	return ret
   900  }
   901  
   902  func (self *SAzureClient) getDefaultRegion() (cloudprovider.ICloudRegion, error) {
   903  	if len(self.regions) > 0 {
   904  		return &self.regions[0], nil
   905  	}
   906  	return nil, cloudprovider.ErrNotFound
   907  }
   908  
   909  func (self *SAzureClient) getIRegionByRegionId(id string) (cloudprovider.ICloudRegion, error) {
   910  	for i := 0; i < len(self.regions); i += 1 {
   911  		if self.regions[i].GetId() == id {
   912  			return &self.regions[i], nil
   913  		}
   914  	}
   915  	return nil, cloudprovider.ErrNotFound
   916  }
   917  
   918  func (self *SAzureClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) {
   919  	for i := 0; i < len(self.regions); i += 1 {
   920  		if self.regions[i].GetGlobalId() == id {
   921  			return &self.regions[i], nil
   922  		}
   923  	}
   924  	return nil, cloudprovider.ErrNotFound
   925  }
   926  
   927  func (self *SAzureClient) GetRegion(regionId string) *SRegion {
   928  	for i := 0; i < len(self.regions); i += 1 {
   929  		if self.regions[i].GetId() == regionId {
   930  			return &self.regions[i]
   931  		}
   932  	}
   933  	return nil
   934  }
   935  
   936  func (self *SAzureClient) GetIHostById(id string) (cloudprovider.ICloudHost, error) {
   937  	for i := 0; i < len(self.regions); i += 1 {
   938  		ihost, err := self.regions[i].GetIHostById(id)
   939  		if err == nil {
   940  			return ihost, nil
   941  		} else if err != cloudprovider.ErrNotFound {
   942  			return nil, err
   943  		}
   944  	}
   945  	return nil, cloudprovider.ErrNotFound
   946  }
   947  
   948  func (self *SAzureClient) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) {
   949  	for i := 0; i < len(self.regions); i += 1 {
   950  		ihost, err := self.regions[i].GetIVpcById(id)
   951  		if err == nil {
   952  			return ihost, nil
   953  		} else if err != cloudprovider.ErrNotFound {
   954  			return nil, err
   955  		}
   956  	}
   957  	return nil, cloudprovider.ErrNotFound
   958  }
   959  
   960  func (self *SAzureClient) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) {
   961  	for i := 0; i < len(self.regions); i += 1 {
   962  		ihost, err := self.regions[i].GetIStorageById(id)
   963  		if err == nil {
   964  			return ihost, nil
   965  		} else if err != cloudprovider.ErrNotFound {
   966  			return nil, err
   967  		}
   968  	}
   969  	return nil, cloudprovider.ErrNotFound
   970  }
   971  
   972  func getResourceGroup(id string) string {
   973  	info := strings.Split(strings.ToLower(id), "/")
   974  	idx := -1
   975  	for i := range info {
   976  		if info[i] == "resourcegroups" {
   977  			idx = i + 1
   978  			break
   979  		}
   980  	}
   981  	if idx > 0 && idx < len(info)-1 {
   982  		return fmt.Sprintf("%s/%s", info[1], info[idx])
   983  	}
   984  	return ""
   985  }
   986  
   987  func (self *SAzureClient) GetIProjects() ([]cloudprovider.ICloudProject, error) {
   988  	subscriptionId := self.subscriptionId
   989  	groups := []SResourceGroup{}
   990  	for _, sub := range self.subscriptions {
   991  		self.subscriptionId = sub.SubscriptionId
   992  		resourceGroups, err := self.ListResourceGroups()
   993  		if err != nil {
   994  			return nil, errors.Wrapf(err, "ListResourceGroups")
   995  		}
   996  		groups = append(groups, resourceGroups...)
   997  	}
   998  	self.subscriptionId = subscriptionId
   999  	iprojects := []cloudprovider.ICloudProject{}
  1000  	for i := range groups {
  1001  		groups[i].client = self
  1002  		iprojects = append(iprojects, &groups[i])
  1003  	}
  1004  	return iprojects, nil
  1005  }
  1006  
  1007  func (self *SAzureClient) GetStorageClasses(regionExtId string) ([]string, error) {
  1008  	var iRegion cloudprovider.ICloudRegion
  1009  	var err error
  1010  	if regionExtId == "" {
  1011  		iRegion, err = self.getDefaultRegion()
  1012  	} else {
  1013  		iRegion, err = self.GetIRegionById(regionExtId)
  1014  	}
  1015  	if err != nil {
  1016  		return nil, errors.Wrapf(err, "self.GetIRegionById %s", regionExtId)
  1017  	}
  1018  	skus, err := iRegion.(*SRegion).GetStorageAccountSkus()
  1019  	if err != nil {
  1020  		return nil, errors.Wrap(err, "GetStorageAccountSkus")
  1021  	}
  1022  	ret := make([]string, 0)
  1023  	for i := range skus {
  1024  		ret = append(ret, skus[i].Name)
  1025  	}
  1026  	return ret, nil
  1027  }
  1028  
  1029  func (self *SAzureClient) GetAccessEnv() string {
  1030  	env, _ := azureenv.EnvironmentFromName(self.envName)
  1031  	switch env.Name {
  1032  	case azureenv.PublicCloud.Name:
  1033  		return api.CLOUD_ACCESS_ENV_AZURE_GLOBAL
  1034  	case azureenv.ChinaCloud.Name:
  1035  		return api.CLOUD_ACCESS_ENV_AZURE_CHINA
  1036  	case azureenv.GermanCloud.Name:
  1037  		return api.CLOUD_ACCESS_ENV_AZURE_GERMAN
  1038  	case azureenv.USGovernmentCloud.Name:
  1039  		return api.CLOUD_ACCESS_ENV_AZURE_US_GOVERNMENT
  1040  	default:
  1041  		return api.CLOUD_ACCESS_ENV_AZURE_CHINA
  1042  	}
  1043  }
  1044  
  1045  func (self *SAzureClient) GetCapabilities() []string {
  1046  	caps := []string{
  1047  		cloudprovider.CLOUD_CAPABILITY_PROJECT,
  1048  		cloudprovider.CLOUD_CAPABILITY_COMPUTE,
  1049  		cloudprovider.CLOUD_CAPABILITY_NETWORK,
  1050  		cloudprovider.CLOUD_CAPABILITY_EIP,
  1051  		cloudprovider.CLOUD_CAPABILITY_LOADBALANCER + cloudprovider.READ_ONLY_SUFFIX,
  1052  		cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE,
  1053  		cloudprovider.CLOUD_CAPABILITY_RDS + cloudprovider.READ_ONLY_SUFFIX,
  1054  		cloudprovider.CLOUD_CAPABILITY_CACHE + cloudprovider.READ_ONLY_SUFFIX,
  1055  		cloudprovider.CLOUD_CAPABILITY_EVENT,
  1056  		cloudprovider.CLOUD_CAPABILITY_CLOUDID,
  1057  		cloudprovider.CLOUD_CAPABILITY_SAML_AUTH,
  1058  		cloudprovider.CLOUD_CAPABILITY_WAF,
  1059  		cloudprovider.CLOUD_CAPABILITY_QUOTA + cloudprovider.READ_ONLY_SUFFIX,
  1060  		cloudprovider.CLOUD_CAPABILITY_CACHE + cloudprovider.READ_ONLY_SUFFIX,
  1061  		cloudprovider.CLOUD_CAPABILITY_APP + cloudprovider.READ_ONLY_SUFFIX,
  1062  		cloudprovider.CLOUD_CAPABILITY_CONTAINER + cloudprovider.READ_ONLY_SUFFIX,
  1063  	}
  1064  	return caps
  1065  }
  1066  
  1067  type TagParams struct {
  1068  	Properties TagProperties `json:"properties"`
  1069  	Operation  string        `json:"operation"`
  1070  }
  1071  
  1072  type TagProperties struct {
  1073  	Tags map[string]string `json:"tags"`
  1074  }
  1075  
  1076  func (self *SAzureClient) GetTags(resourceId string) (map[string]string, error) {
  1077  	path := fmt.Sprintf("/%s/providers/Microsoft.Resources/tags/default", resourceId)
  1078  	tags := &TagParams{}
  1079  	err := self.get(path, nil, tags)
  1080  	if err != nil {
  1081  		return nil, errors.Wrap(err, "self.get(path, nil, tags)")
  1082  	}
  1083  	return tags.Properties.Tags, nil
  1084  }
  1085  
  1086  func (self *SAzureClient) SetTags(resourceId string, tags map[string]string) (jsonutils.JSONObject, error) {
  1087  	//reserved prefix 'microsoft', 'azure', 'windows'.
  1088  	for k := range tags {
  1089  		if strings.HasPrefix(k, "microsoft") || strings.HasPrefix(k, "azure") || strings.HasPrefix(k, "windows") {
  1090  			return nil, errors.Wrap(cloudprovider.ErrNotSupported, "reserved prefix microsoft, azure, windows")
  1091  		}
  1092  	}
  1093  	path := fmt.Sprintf("/%s/providers/Microsoft.Resources/tags/default", resourceId)
  1094  	input := TagParams{}
  1095  	input.Operation = "replace"
  1096  	input.Properties.Tags = tags
  1097  	if len(tags) == 0 {
  1098  		return nil, self.del(path)
  1099  	}
  1100  	return self.patch(path, jsonutils.Marshal(input))
  1101  }
  1102  
  1103  func (self *SAzureClient) msGraphClient() *http.Client {
  1104  	conf := clientcredentials.Config{
  1105  		ClientID:     self.clientId,
  1106  		ClientSecret: self.clientSecret,
  1107  
  1108  		TokenURL: fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", self.tenantId),
  1109  		Scopes:   []string{"https://graph.microsoft.com/.default"},
  1110  	}
  1111  	return conf.Client(context.TODO())
  1112  }
  1113  
  1114  func (self *SAzureClient) msGraphRequest(method string, resource string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  1115  	client := self.msGraphClient()
  1116  	url := fmt.Sprintf("https://graph.microsoft.com/v1.0/%s", resource)
  1117  	req := httputils.NewJsonRequest(httputils.THttpMethod(method), url, body)
  1118  	ae := AzureResponseError{}
  1119  	cli := httputils.NewJsonClient(client)
  1120  	_, body, err := cli.Send(context.TODO(), req, &ae, self.debug)
  1121  	if err != nil {
  1122  		return nil, err
  1123  	}
  1124  	return body, nil
  1125  }