azure.go 32 KB

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