| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099 |
- // 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.
- // 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 azure
- import (
- "context"
- "fmt"
- "net/http"
- "net/url"
- "strconv"
- "strings"
- "sync"
- "time"
- "github.com/Azure/go-autorest/autorest"
- azureenv "github.com/Azure/go-autorest/autorest/azure"
- "github.com/Azure/go-autorest/autorest/azure/auth"
- "github.com/pkg/errors"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/gotypes"
- "yunion.io/x/pkg/util/httputils"
- "yunion.io/x/pkg/utils"
- api "yunion.io/x/cloudmux/pkg/apis/compute"
- "yunion.io/x/cloudmux/pkg/cloudprovider"
- )
- const (
- CLOUD_PROVIDER_AZURE = api.CLOUD_PROVIDER_AZURE
- CLOUD_PROVIDER_AZURE_CN = "微软"
- CLOUD_PROVIDER_AZURE_EN = "Azure"
- AZURE_API_VERSION = "2016-02-01"
- )
- type TAzureResource string
- var (
- DefaultResource = TAzureResource("default")
- LoganalyticsResource = TAzureResource("loganalytics")
- )
- type azureAuthClient struct {
- client *autorest.Client
- domain string
- }
- type SAzureClient struct {
- *AzureClientConfig
- clientCache map[TAzureResource]*azureAuthClient
- lock sync.Mutex
- tokenLock sync.Mutex
- tokenMap map[string]*Token
- httpClient *http.Client
- regions []SRegion
- iBuckets []cloudprovider.ICloudBucket
- subscriptions []SSubscription
- debug bool
- ctx context.Context
- workspaces []SLoganalyticsWorkspace
- }
- type AzureClientConfig struct {
- cpcfg cloudprovider.ProviderConfig
- envName string
- tenantId string
- clientId string
- clientSecret string
- subscriptionId string
- debug bool
- }
- func NewAzureClientConfig(envName, tenantId, clientId, clientSecret string) *AzureClientConfig {
- cfg := &AzureClientConfig{
- envName: envName,
- tenantId: tenantId,
- clientId: clientId,
- clientSecret: clientSecret,
- }
- return cfg
- }
- func (cfg *AzureClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *AzureClientConfig {
- cfg.cpcfg = cpcfg
- return cfg
- }
- func (cfg *AzureClientConfig) SubscriptionId(id string) *AzureClientConfig {
- cfg.subscriptionId = id
- return cfg
- }
- func (cfg *AzureClientConfig) Debug(debug bool) *AzureClientConfig {
- cfg.debug = debug
- return cfg
- }
- func NewAzureClient(cfg *AzureClientConfig) (*SAzureClient, error) {
- client := SAzureClient{
- AzureClientConfig: cfg,
- debug: cfg.debug,
- clientCache: map[TAzureResource]*azureAuthClient{},
- tokenMap: map[string]*Token{},
- ctx: context.Background(),
- }
- var err error
- client.subscriptions, err = client.ListSubscriptions()
- if err != nil {
- return nil, errors.Wrap(err, "ListSubscriptions")
- }
- client.regions, err = client.ListRegions()
- if err != nil {
- return nil, errors.Wrapf(err, "ListRegions")
- }
- for i := range client.regions {
- client.regions[i].client = &client
- }
- return &client, nil
- }
- func (self *SAzureClient) getClient(resource TAzureResource) (*azureAuthClient, error) {
- self.lock.Lock()
- defer self.lock.Unlock()
- _client, ok := self.clientCache[resource]
- if ok {
- return _client, nil
- }
- ret := &azureAuthClient{}
- client := autorest.NewClientWithUserAgent("Yunion API")
- conf := auth.NewClientCredentialsConfig(self.clientId, self.clientSecret, self.tenantId)
- env, err := azureenv.EnvironmentFromName(self.envName)
- if err != nil {
- return nil, errors.Wrapf(err, "azureenv.EnvironmentFromName(%s)", self.envName)
- }
- httpClient := self.cpcfg.AdaptiveTimeoutHttpClient()
- transport, _ := httpClient.Transport.(*http.Transport)
- httpClient.Transport = cloudprovider.GetCheckTransport(transport, func(req *http.Request) (func(resp *http.Response) error, error) {
- if self.cpcfg.ReadOnly {
- if req.Method == "GET" || (req.Method == "POST" && strings.HasSuffix(req.URL.Path, "oauth2/token")) {
- return nil, nil
- }
- return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path)
- }
- return nil, nil
- })
- client.Sender = httpClient
- switch resource {
- case LoganalyticsResource:
- ret.domain = env.ResourceIdentifiers.OperationalInsights
- conf.Resource = env.ResourceIdentifiers.OperationalInsights
- if conf.Resource == "N/A" && self.envName == "AzureChinaCloud" {
- ret.domain = "https://api.loganalytics.azure.cn"
- conf.Resource = ret.domain
- }
- default:
- ret.domain = env.ResourceManagerEndpoint
- conf.Resource = env.ResourceManagerEndpoint
- }
- conf.AADEndpoint = env.ActiveDirectoryEndpoint
- {
- spt, err := conf.ServicePrincipalToken()
- if err != nil {
- return nil, errors.Wrapf(err, "ServicePrincipalToken")
- }
- spt.SetSender(httpClient)
- client.Authorizer = autorest.NewBearerAuthorizer(spt)
- }
- if self.debug {
- client.RequestInspector = LogRequest()
- }
- ret.client = &client
- self.clientCache[resource] = ret
- return ret, nil
- }
- func (self *SAzureClient) getDefaultClient() (*azureAuthClient, error) {
- return self.getClient(DefaultResource)
- }
- func (self *SAzureClient) getLoganalyticsClient() (*azureAuthClient, error) {
- return self.getClient(LoganalyticsResource)
- }
- func (self *SAzureClient) jsonRequest(method, path string, body jsonutils.JSONObject, params url.Values, showErrorMsg bool) (jsonutils.JSONObject, error) {
- cli, err := self.getDefaultClient()
- if err != nil {
- return nil, errors.Wrapf(err, "getDefaultClient")
- }
- defer func() {
- if err != nil && showErrorMsg {
- bj := ""
- if body != nil {
- bj = body.PrettyString()
- }
- log.Errorf("%s %s?%s \n%s error: %v", method, path, params.Encode(), bj, err)
- }
- }()
- var resp jsonutils.JSONObject
- for i := 0; i < 2; i++ {
- resp, err = jsonRequest(cli.client, method, cli.domain, path, body, params, self.debug)
- if err != nil {
- if ae, ok := err.(*AzureResponseError); ok {
- switch ae.AzureError.Code {
- case "SubscriptionNotRegistered":
- service := self.getService(path)
- if len(service) == 0 {
- return nil, err
- }
- re := self.ServiceRegister("Microsoft.Network")
- if re != nil {
- return nil, errors.Wrapf(re, "self.registerService(Microsoft.Network)")
- }
- continue
- case "MissingSubscriptionRegistration":
- for _, serviceType := range ae.AzureError.Details {
- re := self.ServiceRegister(serviceType.Target)
- if err != nil {
- return nil, errors.Wrapf(re, "self.registerService(%s)", serviceType.Target)
- }
- }
- continue
- }
- }
- return resp, err
- }
- return resp, err
- }
- return resp, err
- }
- func (self *SAzureClient) ljsonRequest(method, path string, body jsonutils.JSONObject, params url.Values) (jsonutils.JSONObject, error) {
- cli, err := self.getLoganalyticsClient()
- if err != nil {
- return nil, errors.Wrapf(err, "getLoganalyticsClient")
- }
- if params == nil {
- params = url.Values{}
- }
- params.Set("api-version", "2021-12-01-preview")
- return jsonRequest(cli.client, method, cli.domain, path, body, params, self.debug)
- }
- func (self *SAzureClient) put(path string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
- params := url.Values{}
- params.Set("api-version", self._apiVersion(path, params))
- return self.jsonRequest("PUT", path, body, params, true)
- }
- func (self *SAzureClient) post(path string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
- params := url.Values{}
- params.Set("api-version", self._apiVersion(path, params))
- return self.jsonRequest("POST", path, body, params, true)
- }
- func (self *SAzureClient) patch(resource string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
- params := url.Values{}
- params.Set("api-version", self._apiVersion(resource, params))
- return self.jsonRequest("PATCH", resource, body, params, true)
- }
- func (self *SAzureClient) _get(resourceId string, params url.Values, retVal interface{}, showErrorMsg bool) error {
- if len(resourceId) == 0 {
- return cloudprovider.ErrNotFound
- }
- if params == nil {
- params = url.Values{}
- }
- params.Set("api-version", self._apiVersion(resourceId, params))
- body, err := self.jsonRequest("GET", resourceId, nil, params, showErrorMsg)
- if err != nil {
- return err
- }
- if gotypes.IsNil(body) {
- return fmt.Errorf("empty response")
- }
- err = body.Unmarshal(retVal)
- if err != nil {
- return err
- }
- return nil
- }
- func (self *SAzureClient) get(resourceId string, params url.Values, retVal interface{}) error {
- return self._get(resourceId, params, retVal, true)
- }
- func (self *SAzureClient) list(resource string, params url.Values, retVal interface{}) error {
- if params == nil {
- params = url.Values{}
- }
- result := []jsonutils.JSONObject{}
- var key, skipToken string
- for {
- resp, err := self._list(resource, params)
- if err != nil {
- return errors.Wrapf(err, "_list(%s)", resource)
- }
- if gotypes.IsNil(resp) {
- return fmt.Errorf("empty response for %s", resource)
- }
- keys := []string{}
- if resp.Contains("value") {
- keys = []string{"value"}
- }
- part, err := resp.GetArray(keys...)
- if err != nil {
- return errors.Wrapf(err, "resp.GetArray(%s)", keys)
- }
- result = append(result, part...)
- nextLink, _ := resp.GetString("nextLink")
- if len(nextLink) == 0 {
- break
- }
- link, err := url.Parse(nextLink)
- if err != nil {
- return errors.Wrapf(err, "url.Parse(%s)", nextLink)
- }
- prevSkipToken := params.Get(key)
- key, skipToken = func() (string, string) {
- for _, _key := range []string{"$skipToken", "$skiptoken"} {
- tokens, ok := link.Query()[_key]
- if ok {
- for _, token := range tokens {
- if len(token) > 0 && token != prevSkipToken {
- return _key, token
- }
- }
- }
- }
- return "", ""
- }()
- if len(skipToken) == 0 {
- break
- }
- params.Del("$skipToken")
- params.Del("$skiptoken")
- params.Set(key, skipToken)
- }
- return jsonutils.Update(retVal, result)
- }
- func (self *SAzureClient) getService(path string) string {
- for _, service := range []string{
- "microsoft.compute",
- "microsoft.network",
- "microsoft.storage",
- "microsoft.billing",
- "microsoft.insights",
- "microsoft.authorization",
- } {
- if strings.Contains(strings.ToLower(path), service) {
- return service
- }
- }
- return ""
- }
- func (self *SAzureClient) _apiVersion(resource string, params url.Values) string {
- version := params.Get("api-version")
- if len(version) > 0 {
- return version
- }
- info := strings.Split(strings.ToLower(resource), "/")
- if utils.IsInStringArray("microsoft.dbformariadb", info) {
- return "2018-06-01-preview"
- } else if utils.IsInStringArray("microsoft.dbformysql", info) {
- if utils.IsInStringArray("flexibleservers", info) {
- return "2020-07-01-privatepreview"
- }
- return "2017-12-01"
- } else if utils.IsInStringArray("microsoft.dbforpostgresql", info) {
- if utils.IsInStringArray("flexibleservers", info) {
- return "2020-02-14-preview"
- }
- return "2017-12-01"
- } else if utils.IsInStringArray("microsoft.sql", info) {
- return "2020-08-01-preview"
- } else if utils.IsInStringArray("microsoft.compute", info) {
- if utils.IsInStringArray("tags", info) {
- return "2020-06-01"
- }
- if utils.IsInStringArray("publishers", info) {
- return "2020-06-01"
- }
- if utils.IsInStringArray("virtualmachines", info) {
- return "2021-11-01"
- }
- if utils.IsInStringArray("skus", info) {
- return "2019-04-01"
- }
- return "2018-06-01"
- } else if utils.IsInStringArray("microsoft.network", info) {
- if utils.IsInStringArray("virtualnetworks", info) {
- return "2018-08-01"
- }
- if utils.IsInStringArray("publicipaddresses", info) {
- return "2018-03-01"
- }
- if utils.IsInStringArray("frontdoorwebapplicationfirewallmanagedrulesets", info) {
- return "2020-11-01"
- }
- if utils.IsInStringArray("frontdoorwebapplicationfirewallpolicies", info) {
- return "2020-11-01"
- }
- if utils.IsInStringArray("applicationgatewaywebapplicationfirewallpolicies", info) {
- return "2021-01-01"
- }
- if utils.IsInStringArray("applicationgatewayavailablewafrulesets", info) {
- return "2018-06-01"
- }
- if utils.IsInStringArray("securityrules", info) {
- return "2023-05-01"
- }
- return "2018-06-01"
- } else if utils.IsInStringArray("microsoft.storage", info) {
- if utils.IsInStringArray("storageaccounts", info) {
- return "2016-12-01"
- }
- if utils.IsInStringArray("checknameavailability", info) {
- return "2019-04-01"
- }
- if utils.IsInStringArray("skus", info) {
- return "2019-04-01"
- }
- if utils.IsInStringArray("usages", info) {
- return "2018-07-01"
- }
- } else if utils.IsInStringArray("microsoft.billing", info) {
- return "2018-03-01-preview"
- } else if utils.IsInStringArray("microsoft.insights", info) {
- return "2017-03-01-preview"
- } else if utils.IsInStringArray("microsoft.authorization", info) {
- return "2022-04-01"
- } else if utils.IsInStringArray("microsoft.cache", info) {
- if utils.IsInStringArray("redisenterprise", info) {
- return "2021-03-01"
- }
- return "2020-06-01"
- } else if utils.IsInStringArray("microsoft.containerservice", info) {
- return "2021-05-01"
- } else if utils.IsInStringArray("microsoft.operationalinsights", info) {
- return "2021-12-01-preview"
- }
- return AZURE_API_VERSION
- }
- func (self *SAzureClient) _subscriptionId() string {
- if len(self.subscriptionId) > 0 {
- return self.subscriptionId
- }
- for _, sub := range self.subscriptions {
- if sub.State == "Enabled" {
- return sub.SubscriptionId
- }
- }
- return ""
- }
- func (self *SAzureClient) _list(resource string, params url.Values) (jsonutils.JSONObject, error) {
- subId := self._subscriptionId()
- path := "subscriptions"
- switch resource {
- case "subscriptions", "providers/Microsoft.Billing/enrollmentAccounts":
- path = resource
- case "locations", "resourcegroups", "providers":
- if len(subId) == 0 {
- return nil, fmt.Errorf("no avaiable subscriptions")
- }
- path = fmt.Sprintf("subscriptions/%s/%s", subId, resource)
- case "Microsoft.Network/frontdoorWebApplicationFirewallPolicies":
- path = fmt.Sprintf("subscriptions/%s/resourceGroups/%s/providers/%s", subId, params.Get("resourceGroups"), resource)
- params.Del("resourceGroups")
- default:
- if strings.HasPrefix(resource, "subscriptions/") || strings.HasPrefix(resource, "/subscriptions/") {
- path = resource
- } else {
- if len(subId) == 0 {
- return nil, fmt.Errorf("no avaiable subscriptions")
- }
- path = fmt.Sprintf("subscriptions/%s/providers/%s", subId, resource)
- }
- }
- params.Set("api-version", self._apiVersion(resource, params))
- return self.jsonRequest("GET", path, nil, params, true)
- }
- func (self *SAzureClient) del(resourceId string) error {
- params := url.Values{}
- params.Set("api-version", self._apiVersion(resourceId, params))
- _, err := self.jsonRequest("DELETE", resourceId, nil, params, true)
- return err
- }
- func (self *SAzureClient) perform(resourceId string, action string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
- path := fmt.Sprintf("%s/%s", resourceId, action)
- return self.post(path, body)
- }
- func (self *SAzureClient) CreateIProject(name string) (cloudprovider.ICloudProject, error) {
- if len(self.regions) > 0 {
- _, err := self.regions[0].CreateResourceGroup(name)
- if err != nil {
- return nil, errors.Wrapf(err, "CreateResourceGroup")
- }
- return self.regions[0].GetResourceGroupDetail(name)
- }
- return nil, fmt.Errorf("no region found ???")
- }
- func (self *SAzureClient) ListResourceGroups() ([]SResourceGroup, error) {
- resourceGroups := []SResourceGroup{}
- err := self.list("resourcegroups", url.Values{}, &resourceGroups)
- if err != nil {
- return nil, errors.Wrap(err, "list")
- }
- for i := range resourceGroups {
- resourceGroups[i].client = self
- resourceGroups[i].subId = self.subscriptionId
- }
- return resourceGroups, nil
- }
- type AzureErrorDetail struct {
- Code string `json:"code,omitempty"`
- Message string `json:"message,omitempty"`
- Target string `json:"target,omitempty"`
- }
- type AzureError struct {
- Code string `json:"code,omitempty"`
- Details []AzureErrorDetail `json:"details,omitempty"`
- Message string `json:"message,omitempty"`
- }
- func (e *AzureError) Error() string {
- return jsonutils.Marshal(e).String()
- }
- func (self *SAzureClient) getUniqName(resourceGroup, resourceType, name string) (string, error) {
- prefix := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s/", self.subscriptionId, resourceGroup, resourceType)
- newName := name
- for i := 0; i < 20; i++ {
- err := self._get(prefix+newName, nil, url.Values{}, false)
- if errors.Cause(err) == cloudprovider.ErrNotFound {
- return newName, nil
- }
- info := strings.Split(newName, "-")
- num, err := strconv.Atoi(info[len(info)-1])
- if err != nil {
- info = append(info, "1")
- } else {
- info[len(info)-1] = fmt.Sprintf("%d", num+1)
- }
- newName = strings.Join(info, "-")
- }
- return "", fmt.Errorf("not find uniq name for %s[%s]", resourceType, name)
- }
- func (self *SAzureClient) create(resourceGroup, resourceType, name string, body jsonutils.JSONObject, retVal interface{}) error {
- resource := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s/%s", self.subscriptionId, resourceGroup, resourceType, name)
- params := url.Values{}
- params.Set("api-version", self._apiVersion(resourceType, params))
- resp, err := self.jsonRequest("PUT", resource, body, params, true)
- if err != nil {
- return errors.Wrapf(err, "jsonRequest")
- }
- if gotypes.IsNil(resp) {
- return fmt.Errorf("empty response")
- }
- if retVal != nil {
- return resp.Unmarshal(retVal)
- }
- return nil
- }
- func (self *SAzureClient) CheckNameAvailability(resourceType, name string) (bool, error) {
- path := fmt.Sprintf("/subscriptions/%s/providers/%s/checkNameAvailability", self.subscriptionId, strings.Split(resourceType, "/")[0])
- body := map[string]string{
- "Name": name,
- "Type": resourceType,
- }
- resp, err := self.post(path, jsonutils.Marshal(body))
- if err != nil {
- return false, errors.Wrapf(err, "post(%s)", path)
- }
- if gotypes.IsNil(resp) {
- return false, fmt.Errorf("empty response")
- }
- output := sNameAvailableOutput{}
- err = resp.Unmarshal(&output)
- if err != nil {
- return false, errors.Wrap(err, "resp.Unmarshal")
- }
- if output.NameAvailable {
- return true, nil
- }
- if output.Reason == "AlreadyExists" {
- return false, nil
- }
- return true, nil
- }
- func (self *SAzureClient) update(body jsonutils.JSONObject, retVal interface{}) error {
- id := jsonutils.GetAnyString(body, []string{"Id", "id", "ID"})
- if len(id) == 0 {
- return fmt.Errorf("failed to found id for update operation")
- }
- params := url.Values{}
- params.Set("api-version", self._apiVersion(id, params))
- resp, err := self.jsonRequest("PUT", id, body, params, true)
- if err != nil {
- return err
- }
- if gotypes.IsNil(resp) {
- return fmt.Errorf("empty response")
- }
- if retVal != nil {
- return resp.Unmarshal(retVal)
- }
- return nil
- }
- func jsonRequest(client *autorest.Client, method, domain, baseUrl string, body jsonutils.JSONObject, params url.Values, debug bool) (jsonutils.JSONObject, error) {
- result, err := _jsonRequest(client, method, domain, baseUrl, body, params, debug)
- if err != nil {
- return nil, err
- }
- return result, nil
- }
- // {"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"}}
- type sMessage struct {
- Lang string
- Value string
- }
- type sOdataError struct {
- Code string
- Message sMessage
- RequestId string
- Date time.Time
- }
- type AzureResponseError struct {
- OdataError sOdataError `json:"odata.error"`
- AzureError AzureError `json:"error"`
- Code string
- Message string
- }
- func (ae AzureResponseError) Error() string {
- return jsonutils.Marshal(ae).String()
- }
- func (ae *AzureResponseError) ParseErrorFromJsonResponse(statusCode int, status string, body jsonutils.JSONObject) error {
- if body != nil {
- body.Unmarshal(ae)
- }
- if statusCode == 404 {
- msg := ""
- if body != nil {
- msg = body.String()
- }
- return errors.Wrap(cloudprovider.ErrNotFound, msg)
- }
- if ae.AzureError.Code == "AuthorizationFailed" {
- return errors.Wrapf(cloudprovider.ErrForbidden, "%s", jsonutils.Marshal(ae).String())
- }
- if ae.AzureError.Code == "ResourceCollectionRequestsThrottled" {
- return errors.Wrapf(cloudprovider.ErrTooManyRequests, "%s", jsonutils.Marshal(ae).String())
- }
- if len(ae.OdataError.Code) > 0 || len(ae.AzureError.Code) > 0 || (len(ae.Code) > 0 && len(ae.Message) > 0) {
- return ae
- }
- return nil
- }
- func _jsonRequest(client *autorest.Client, method, domain, path string, body jsonutils.JSONObject, params url.Values, debug bool) (jsonutils.JSONObject, error) {
- uri := fmt.Sprintf("%s/%s?%s", strings.TrimSuffix(domain, "/"), strings.TrimPrefix(path, "/"), params.Encode())
- req := httputils.NewJsonRequest(httputils.THttpMethod(method), uri, body)
- ae := AzureResponseError{}
- cli := httputils.NewJsonClient(client)
- header, body, err := cli.Send(context.TODO(), req, &ae, debug)
- if err != nil {
- if strings.Contains(err.Error(), "azure.BearerAuthorizer#WithAuthorization") {
- return nil, errors.Wrapf(cloudprovider.ErrInvalidAccessKey, "%s", err.Error())
- }
- return nil, err
- }
- locationFunc := func(head http.Header) string {
- for _, k := range []string{"Azure-Asyncoperation", "Location"} {
- link := head.Get(k)
- if len(link) > 0 {
- return link
- }
- }
- return ""
- }
- location := locationFunc(header)
- if len(location) > 0 && (body == nil || body.IsZero() || !body.Contains("id")) {
- err = cloudprovider.Wait(time.Second*10, time.Minute*30, func() (bool, error) {
- locationUrl, err := url.Parse(location)
- if err != nil {
- return false, errors.Wrapf(err, "url.Parse(%s)", location)
- }
- if len(locationUrl.Query().Get("api-version")) == 0 {
- q, _ := url.ParseQuery(locationUrl.RawQuery)
- q.Set("api-version", params.Get("api-version"))
- locationUrl.RawQuery = q.Encode()
- }
- req := httputils.NewJsonRequest(httputils.GET, locationUrl.String(), nil)
- lae := AzureResponseError{}
- _header, _body, _err := cli.Send(context.TODO(), req, &lae, debug)
- if _err != nil {
- if utils.IsInStringArray(lae.AzureError.Code, []string{"OSProvisioningTimedOut", "OSProvisioningClientError", "OSProvisioningInternalError"}) {
- body = _body
- return true, nil
- }
- return false, errors.Wrapf(_err, "cli.Send(%s)", location)
- }
- if retryAfter := _header.Get("Retry-After"); len(retryAfter) > 0 {
- sleepTime, _ := strconv.Atoi(retryAfter)
- time.Sleep(time.Second * time.Duration(sleepTime))
- return false, nil
- }
- if _body != nil {
- task := struct {
- Status string
- Properties struct {
- Output *jsonutils.JSONDict
- }
- }{}
- _body.Unmarshal(&task)
- if len(task.Status) == 0 {
- body = _body
- return true, nil
- }
- switch task.Status {
- case "InProgress":
- log.Debugf("process %s %s InProgress", method, path)
- return false, nil
- case "Succeeded":
- log.Debugf("process %s %s Succeeded", method, path)
- if task.Properties.Output != nil {
- body = task.Properties.Output
- }
- return true, nil
- case "Failed":
- return false, fmt.Errorf("%s %s failed", method, path)
- default:
- return false, fmt.Errorf("Unknow status %s %s %s", task.Status, method, path)
- }
- }
- return false, nil
- })
- if err != nil {
- return nil, errors.Wrapf(err, "time out for waiting %s %s", method, uri)
- }
- }
- return body, nil
- }
- func (self *SAzureClient) ListRegions() ([]SRegion, error) {
- resp, err := self.list_v2("locations", "2014-02-26", nil)
- if err != nil {
- return nil, err
- }
- regions := []SRegion{}
- err = resp.Unmarshal(®ions, "value")
- if err != nil {
- return nil, err
- }
- return regions, nil
- }
- func (self *SAzureClient) GetRegions() []SRegion {
- return self.regions
- }
- func (self *SAzureClient) GetSubAccounts() (subAccounts []cloudprovider.SSubAccount, err error) {
- levelBySub, err := self.getSubscriptionManagementGroupLevels()
- if err != nil {
- levelBySub = map[string]map[string]string{}
- }
- subAccounts = make([]cloudprovider.SSubAccount, len(self.subscriptions))
- for i, subscription := range self.subscriptions {
- subAccounts[i].Account = fmt.Sprintf("%s/%s", self.tenantId, subscription.SubscriptionId)
- subAccounts[i].Id = subscription.SubscriptionId
- subAccounts[i].Name = subscription.DisplayName
- subAccounts[i].HealthStatus = subscription.GetHealthStatus()
- if tags, ok := levelBySub[subscription.SubscriptionId]; ok {
- subAccounts[i].Tags = tags
- }
- }
- return subAccounts, nil
- }
- func (self *SAzureClient) GetAccountId() string {
- return self.tenantId
- }
- func (self *SAzureClient) GetIamLoginUrl() string {
- switch self.envName {
- case "AzureChinaCloud":
- return "http://portal.azure.cn"
- default:
- return "http://portal.azure.com"
- }
- }
- func (self *SAzureClient) GetIRegions() ([]cloudprovider.ICloudRegion, error) {
- ret := []cloudprovider.ICloudRegion{}
- for i := range self.regions {
- ret = append(ret, &self.regions[i])
- }
- return ret, nil
- }
- func (self *SAzureClient) getDefaultRegion() (cloudprovider.ICloudRegion, error) {
- if len(self.regions) > 0 {
- return &self.regions[0], nil
- }
- return nil, cloudprovider.ErrNotFound
- }
- func (self *SAzureClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) {
- for i := 0; i < len(self.regions); i += 1 {
- if self.regions[i].GetGlobalId() == id {
- return &self.regions[i], nil
- }
- }
- return nil, cloudprovider.ErrNotFound
- }
- func (self *SAzureClient) GetRegion(regionId string) *SRegion {
- for i := 0; i < len(self.regions); i += 1 {
- if self.regions[i].GetId() == regionId {
- return &self.regions[i]
- }
- }
- return nil
- }
- func (self *SAzureClient) GetIHostById(id string) (cloudprovider.ICloudHost, error) {
- for i := 0; i < len(self.regions); i += 1 {
- ihost, err := self.regions[i].GetIHostById(id)
- if err == nil {
- return ihost, nil
- } else if err != cloudprovider.ErrNotFound {
- return nil, err
- }
- }
- return nil, cloudprovider.ErrNotFound
- }
- func (self *SAzureClient) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) {
- for i := 0; i < len(self.regions); i += 1 {
- ihost, err := self.regions[i].GetIVpcById(id)
- if err == nil {
- return ihost, nil
- } else if err != cloudprovider.ErrNotFound {
- return nil, err
- }
- }
- return nil, cloudprovider.ErrNotFound
- }
- func (self *SAzureClient) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) {
- for i := 0; i < len(self.regions); i += 1 {
- ihost, err := self.regions[i].GetIStorageById(id)
- if err == nil {
- return ihost, nil
- } else if err != cloudprovider.ErrNotFound {
- return nil, err
- }
- }
- return nil, cloudprovider.ErrNotFound
- }
- func getResourceGroup(id string) string {
- info := strings.Split(strings.ToLower(id), "/")
- idx := -1
- for i := range info {
- if info[i] == "resourcegroups" {
- idx = i + 1
- break
- }
- }
- if idx > 0 && idx < len(info)-2 {
- return fmt.Sprintf("%s/%s", info[2], info[idx])
- }
- return ""
- }
- func (self *SAzureClient) GetIProjects() ([]cloudprovider.ICloudProject, error) {
- resourceGroups, err := self.ListResourceGroups()
- if err != nil {
- return nil, errors.Wrapf(err, "ListResourceGroups")
- }
- iprojects := []cloudprovider.ICloudProject{}
- for i := range resourceGroups {
- resourceGroups[i].client = self
- iprojects = append(iprojects, &resourceGroups[i])
- }
- return iprojects, nil
- }
- func (self *SAzureClient) GetStorageClasses(regionExtId string) ([]string, error) {
- var iRegion cloudprovider.ICloudRegion
- var err error
- if regionExtId == "" {
- iRegion, err = self.getDefaultRegion()
- } else {
- iRegion, err = self.GetIRegionById(regionExtId)
- }
- if err != nil {
- return nil, errors.Wrapf(err, "self.GetIRegionById %s", regionExtId)
- }
- skus, err := iRegion.(*SRegion).GetStorageAccountSkus()
- if err != nil {
- return nil, errors.Wrap(err, "GetStorageAccountSkus")
- }
- ret := make([]string, 0)
- for i := range skus {
- ret = append(ret, skus[i].Name)
- }
- return ret, nil
- }
- func (self *SAzureClient) GetAccessEnv() string {
- env, _ := azureenv.EnvironmentFromName(self.envName)
- switch env.Name {
- case azureenv.PublicCloud.Name:
- return api.CLOUD_ACCESS_ENV_AZURE_GLOBAL
- case azureenv.ChinaCloud.Name:
- return api.CLOUD_ACCESS_ENV_AZURE_CHINA
- case azureenv.GermanCloud.Name:
- return api.CLOUD_ACCESS_ENV_AZURE_GERMAN
- case azureenv.USGovernmentCloud.Name:
- return api.CLOUD_ACCESS_ENV_AZURE_US_GOVERNMENT
- default:
- return api.CLOUD_ACCESS_ENV_AZURE_CHINA
- }
- }
- func (self *SAzureClient) 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.READ_ONLY_SUFFIX,
- cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE,
- cloudprovider.CLOUD_CAPABILITY_RDS + cloudprovider.READ_ONLY_SUFFIX,
- cloudprovider.CLOUD_CAPABILITY_CACHE + cloudprovider.READ_ONLY_SUFFIX,
- cloudprovider.CLOUD_CAPABILITY_EVENT,
- cloudprovider.CLOUD_CAPABILITY_CLOUDID,
- cloudprovider.CLOUD_CAPABILITY_WAF,
- cloudprovider.CLOUD_CAPABILITY_QUOTA + cloudprovider.READ_ONLY_SUFFIX,
- cloudprovider.CLOUD_CAPABILITY_CACHE + cloudprovider.READ_ONLY_SUFFIX,
- cloudprovider.CLOUD_CAPABILITY_APP + cloudprovider.READ_ONLY_SUFFIX,
- cloudprovider.CLOUD_CAPABILITY_CONTAINER + cloudprovider.READ_ONLY_SUFFIX,
- }
- return caps
- }
- type SManagementEntity struct {
- Id string `json:"id"`
- Name string `json:"name"`
- Type string `json:"type"`
- Properties struct {
- DisplayName string `json:"displayName"`
- ParentDisplayNameChain []string `json:"parentDisplayNameChain"`
- } `json:"properties"`
- }
- type SManagementEntities struct {
- Value []SManagementEntity `json:"value"`
- }
- func (self *SAzureClient) getSubscriptionManagementGroupLevels() (map[string]map[string]string, error) {
- result := map[string]map[string]string{}
- resp, err := self.post_v2("/providers/Microsoft.Management/getEntities", "2020-05-01", map[string]interface{}{
- "$select": "Name,DisplayName,Type,ParentDisplayNameChain",
- })
- if err != nil {
- return result, errors.Wrap(err, "post_v2 Microsoft.Management/getEntities")
- }
- entities := SManagementEntities{}
- if err := resp.Unmarshal(&entities); err != nil {
- return result, errors.Wrap(err, "resp.Unmarshal SManagementEntities")
- }
- for i := range entities.Value {
- e := entities.Value[i]
- if !strings.Contains(strings.ToLower(e.Type), "subscriptions") {
- continue
- }
- chain := e.Properties.ParentDisplayNameChain
- if len(chain) == 0 {
- continue
- }
- tags := map[string]string{}
- for idx, name := range chain {
- key := ""
- switch idx {
- case 0:
- key = "L1"
- case 1:
- key = "L2"
- case 2:
- key = "L3"
- default:
- key = fmt.Sprintf("L%d", idx+1)
- }
- tags[key] = name
- }
- result[e.Name] = tags
- }
- return result, nil
- }
- type TagParams struct {
- Properties TagProperties `json:"properties"`
- Operation string `json:"operation"`
- }
- type TagProperties struct {
- Tags map[string]string `json:"tags"`
- }
- func (self *SAzureClient) GetTags(resourceId string) (map[string]string, error) {
- path := fmt.Sprintf("/%s/providers/Microsoft.Resources/tags/default", resourceId)
- tags := &TagParams{}
- err := self.get(path, nil, tags)
- if err != nil {
- return nil, errors.Wrap(err, "self.get(path, nil, tags)")
- }
- return tags.Properties.Tags, nil
- }
- func (self *SAzureClient) SetTags(resourceId string, tags map[string]string) (jsonutils.JSONObject, error) {
- //reserved prefix 'microsoft', 'azure', 'windows'.
- for k := range tags {
- if strings.HasPrefix(k, "microsoft") || strings.HasPrefix(k, "azure") || strings.HasPrefix(k, "windows") {
- return nil, errors.Wrap(cloudprovider.ErrNotSupported, "reserved prefix microsoft, azure, windows")
- }
- }
- path := fmt.Sprintf("/%s/providers/Microsoft.Resources/tags/default", resourceId)
- input := TagParams{}
- input.Operation = "replace"
- input.Properties.Tags = tags
- if len(tags) == 0 {
- return nil, self.del(path)
- }
- return self.patch(path, jsonutils.Marshal(input))
- }
|