common.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. // Copyright (c) 2016, 2018, 2020, Oracle and/or its affiliates. All rights reserved.
  2. // This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
  3. package common
  4. import (
  5. "encoding/json"
  6. "fmt"
  7. "io/ioutil"
  8. "net/http"
  9. "os"
  10. "path"
  11. "regexp"
  12. "strings"
  13. "time"
  14. )
  15. //Region type for regions
  16. type Region string
  17. const (
  18. instanceMetadataRegionInfoURLV2 = "http://169.254.169.254/opc/v2/instance/regionInfo"
  19. //RegionSEA region SEA
  20. RegionSEA Region = "sea"
  21. //RegionCAToronto1 region for Toronto
  22. RegionCAToronto1 Region = "ca-toronto-1"
  23. //RegionCAMontreal1 region for Montreal
  24. RegionCAMontreal1 Region = "ca-montreal-1"
  25. //RegionPHX region PHX
  26. RegionPHX Region = "us-phoenix-1"
  27. //RegionIAD region IAD
  28. RegionIAD Region = "us-ashburn-1"
  29. //RegionSJC1 region SJC
  30. RegionSJC1 Region = "us-sanjose-1"
  31. //RegionFRA region FRA
  32. RegionFRA Region = "eu-frankfurt-1"
  33. //RegionLHR region LHR
  34. RegionLHR Region = "uk-london-1"
  35. //RegionAPTokyo1 region for Tokyo
  36. RegionAPTokyo1 Region = "ap-tokyo-1"
  37. //RegionAPOsaka1 region for Osaka
  38. RegionAPOsaka1 Region = "ap-osaka-1"
  39. //RegionAPChiyoda1 region for Chiyoda
  40. RegionAPChiyoda1 Region = "ap-chiyoda-1"
  41. //RegionAPSeoul1 region for Seoul
  42. RegionAPSeoul1 Region = "ap-seoul-1"
  43. //RegionAPChuncheon1 region for Chuncheon
  44. RegionAPChuncheon1 Region = "ap-chuncheon-1"
  45. //RegionAPMumbai1 region for Mumbai
  46. RegionAPMumbai1 Region = "ap-mumbai-1"
  47. //RegionAPHyderabad1 region for Hyderabad
  48. RegionAPHyderabad1 Region = "ap-hyderabad-1"
  49. //RegionAPMelbourne1 region for Melbourne
  50. RegionAPMelbourne1 Region = "ap-melbourne-1"
  51. //RegionAPSydney1 region for Sydney
  52. RegionAPSydney1 Region = "ap-sydney-1"
  53. //RegionMEJeddah1 region for Jeddah
  54. RegionMEJeddah1 Region = "me-jeddah-1"
  55. //RegionEUZurich1 region for Zurich
  56. RegionEUZurich1 Region = "eu-zurich-1"
  57. //RegionEUAmsterdam1 region for Amsterdam
  58. RegionEUAmsterdam1 Region = "eu-amsterdam-1"
  59. //RegionSASaopaulo1 region for Sao Paulo
  60. RegionSASaopaulo1 Region = "sa-saopaulo-1"
  61. //RegionUSLangley1 region for Langley
  62. RegionUSLangley1 Region = "us-langley-1"
  63. //RegionUSLuke1 region for Luke
  64. RegionUSLuke1 Region = "us-luke-1"
  65. //RegionUSGovAshburn1 gov region Ashburn
  66. RegionUSGovAshburn1 Region = "us-gov-ashburn-1"
  67. //RegionUSGovChicago1 gov region Chicago
  68. RegionUSGovChicago1 Region = "us-gov-chicago-1"
  69. //RegionUSGovPhoenix1 region for Phoenix
  70. RegionUSGovPhoenix1 Region = "us-gov-phoenix-1"
  71. //RegionUKGovLondon1 gov region London
  72. RegionUKGovLondon1 Region = "uk-gov-london-1"
  73. //RegionUKGovCardiff1 gov region Cardiff
  74. RegionUKGovCardiff1 Region = "uk-gov-cardiff-1"
  75. // Region Metadata Configuration File
  76. regionMetadataCfgDirName = ".oci"
  77. regionMetadataCfgFileName = "regions-config.json"
  78. // Region Metadata Environment Variable
  79. regionMetadataEnvVarName = "OCI_REGION_METADATA"
  80. // Region Metadata
  81. regionIdentifierPropertyName = "regionIdentifier" // e.g. "ap-sydney-1"
  82. realmKeyPropertyName = "realmKey" // e.g. "oc1"
  83. realmDomainComponentPropertyName = "realmDomainComponent" // e.g. "oraclecloud.com"
  84. regionKeyPropertyName = "regionKey" // e.g. "SYD"
  85. )
  86. var shortNameRegion = map[string]Region{
  87. "sea": RegionSEA,
  88. "phx": RegionPHX,
  89. "iad": RegionIAD,
  90. "fra": RegionFRA,
  91. "lhr": RegionLHR,
  92. "ams": RegionEUAmsterdam1,
  93. "zrh": RegionEUZurich1,
  94. "mel": RegionAPMelbourne1,
  95. "bom": RegionAPMumbai1,
  96. "hyd": RegionAPHyderabad1,
  97. "icn": RegionAPSeoul1,
  98. "yny": RegionAPChuncheon1,
  99. "nrt": RegionAPTokyo1,
  100. "kix": RegionAPOsaka1,
  101. "nja": RegionAPChiyoda1,
  102. "syd": RegionAPSydney1,
  103. "yul": RegionCAMontreal1,
  104. "yyz": RegionCAToronto1,
  105. "sjc": RegionSJC1,
  106. "gru": RegionSASaopaulo1,
  107. "jed": RegionMEJeddah1,
  108. "ltn": RegionUKGovLondon1,
  109. "brs": RegionUKGovCardiff1,
  110. }
  111. var realm = map[string]string{
  112. "oc1": "oraclecloud.com",
  113. "oc2": "oraclegovcloud.com",
  114. "oc3": "oraclegovcloud.com",
  115. "oc4": "oraclegovcloud.uk",
  116. "oc8": "oraclecloud8.com",
  117. }
  118. var regionRealm = map[Region]string{
  119. RegionPHX: "oc1",
  120. RegionIAD: "oc1",
  121. RegionFRA: "oc1",
  122. RegionLHR: "oc1",
  123. RegionCAToronto1: "oc1",
  124. RegionCAMontreal1: "oc1",
  125. RegionSJC1: "oc1",
  126. RegionAPTokyo1: "oc1",
  127. RegionAPOsaka1: "oc1",
  128. RegionAPSeoul1: "oc1",
  129. RegionAPChuncheon1: "oc1",
  130. RegionAPSydney1: "oc1",
  131. RegionAPMumbai1: "oc1",
  132. RegionAPHyderabad1: "oc1",
  133. RegionAPMelbourne1: "oc1",
  134. RegionMEJeddah1: "oc1",
  135. RegionEUZurich1: "oc1",
  136. RegionEUAmsterdam1: "oc1",
  137. RegionSASaopaulo1: "oc1",
  138. RegionUSLangley1: "oc2",
  139. RegionUSLuke1: "oc2",
  140. RegionUSGovAshburn1: "oc3",
  141. RegionUSGovChicago1: "oc3",
  142. RegionUSGovPhoenix1: "oc3",
  143. RegionUKGovCardiff1: "oc4",
  144. RegionUKGovLondon1: "oc4",
  145. RegionAPChiyoda1: "oc8",
  146. }
  147. // External region metadata info flag, used to control adding these metadata region info only once.
  148. var readCfgFile, readEnvVar, visitIMDS bool = true, true, false
  149. // getRegionInfoFromInstanceMetadataService gets the region information
  150. var getRegionInfoFromInstanceMetadataService = getRegionInfoFromInstanceMetadataServiceProd
  151. // Endpoint returns a endpoint for a service
  152. func (region Region) Endpoint(service string) string {
  153. return fmt.Sprintf("%s.%s.%s", service, region, region.secondLevelDomain())
  154. }
  155. // EndpointForTemplate returns a endpoint for a service based on template, only unknown region name can fall back to "oc1", but not short code region name.
  156. func (region Region) EndpointForTemplate(service string, serviceEndpointTemplate string) string {
  157. if serviceEndpointTemplate == "" {
  158. return region.Endpoint(service)
  159. }
  160. // replace service prefix
  161. endpoint := strings.Replace(serviceEndpointTemplate, "{serviceEndpointPrefix}", service, 1)
  162. // replace region
  163. endpoint = strings.Replace(endpoint, "{region}", string(region), 1)
  164. // replace second level domain
  165. endpoint = strings.Replace(endpoint, "{secondLevelDomain}", region.secondLevelDomain(), 1)
  166. return endpoint
  167. }
  168. func (region Region) secondLevelDomain() string {
  169. if realmID, ok := regionRealm[region]; ok {
  170. if secondLevelDomain, ok := realm[realmID]; ok {
  171. return secondLevelDomain
  172. }
  173. }
  174. Debugf("cannot find realm for region : %s, return default realm value.", region)
  175. return realm["oc1"]
  176. }
  177. //StringToRegion convert a string to Region type
  178. func StringToRegion(stringRegion string) (r Region) {
  179. regionStr := strings.ToLower(stringRegion)
  180. // check if short region name provided
  181. if region, ok := shortNameRegion[regionStr]; ok {
  182. r = region
  183. return
  184. }
  185. // check if normal region name provided
  186. potentialRegion := Region(regionStr)
  187. if _, ok := regionRealm[potentialRegion]; ok {
  188. r = potentialRegion
  189. return
  190. }
  191. Debugf("region named: %s, is not recognized from hard-coded region list, will check Region metadata info", stringRegion)
  192. r = checkAndAddRegionMetadata(stringRegion)
  193. return
  194. }
  195. // canStringBeRegion test if the string can be a region, if it can, returns the string as is, otherwise it
  196. // returns an error
  197. var blankRegex = regexp.MustCompile("\\s")
  198. func canStringBeRegion(stringRegion string) (region string, err error) {
  199. if blankRegex.MatchString(stringRegion) || stringRegion == "" {
  200. return "", fmt.Errorf("region can not be empty or have spaces")
  201. }
  202. return stringRegion, nil
  203. }
  204. // check region info from original map
  205. func checkAndAddRegionMetadata(region string) Region {
  206. switch {
  207. case setRegionMetadataFromCfgFile(&region):
  208. case setRegionMetadataFromEnvVar(&region):
  209. case setRegionFromInstanceMetadataService(&region):
  210. default:
  211. //err := fmt.Errorf("failed to get region metadata information.")
  212. return Region(region)
  213. }
  214. return Region(region)
  215. }
  216. // EnableInstanceMetadataServiceLookup provides the interface to lookup IMDS region info
  217. func EnableInstanceMetadataServiceLookup() {
  218. Debugf("Set visitIMDS 'true' to enable IMDS Lookup.")
  219. visitIMDS = true
  220. }
  221. // setRegionMetadataFromEnvVar checks if region metadata env variable is provided, once it's there, parse and added it
  222. // to region map, and it can make sure the env var can only be visited once.
  223. // Once successfully find the expected region(region name or short code), return true, region name will be stored in
  224. // the input pointer.
  225. func setRegionMetadataFromEnvVar(region *string) bool {
  226. if readEnvVar == false {
  227. Debugf("metadata region env variable had already been checked, no need to check again.")
  228. return false //no need to check it again.
  229. }
  230. // Mark readEnvVar Flag as false since it has already been visited.
  231. readEnvVar = false
  232. // check from env variable
  233. if jsonStr, existed := os.LookupEnv(regionMetadataEnvVarName); existed {
  234. Debugf("Raw content of region metadata env var:", jsonStr)
  235. var regionSchema map[string]string
  236. if err := json.Unmarshal([]byte(jsonStr), &regionSchema); err != nil {
  237. Debugf("Can't unmarshal env var, the error info is", err)
  238. return false
  239. }
  240. // check if the specified region is in the env var.
  241. if checkSchemaItems(regionSchema) {
  242. // set mapping table
  243. addRegionSchema(regionSchema)
  244. if regionSchema[regionKeyPropertyName] == *region ||
  245. regionSchema[regionIdentifierPropertyName] == *region {
  246. *region = regionSchema[regionIdentifierPropertyName]
  247. return true
  248. }
  249. }
  250. return false
  251. }
  252. Debugf("The Region Metadata Schema wasn't set in env variable - OCI_REGION_METADATA.")
  253. return false
  254. }
  255. // setRegionMetadataFromCfgFile checks if region metadata config file is provided, once it's there, parse and add all
  256. // the valid regions to region map, the configuration file can only be visited once.
  257. // Once successfully find the expected region(region name or short code), return true, region name will be stored in
  258. // the input pointer.
  259. func setRegionMetadataFromCfgFile(region *string) bool {
  260. if readCfgFile == false {
  261. Debugf("metadata region config file had already been checked, no need to check again.")
  262. return false //no need to check it again.
  263. }
  264. // Mark readCfgFile Flag as false since it has already been visited.
  265. readCfgFile = false
  266. homeFolder := getHomeFolder()
  267. configFile := path.Join(homeFolder, regionMetadataCfgDirName, regionMetadataCfgFileName)
  268. if jsonArr, ok := readAndParseConfigFile(&configFile); ok {
  269. added := false
  270. for _, jsonItem := range jsonArr {
  271. if checkSchemaItems(jsonItem) {
  272. addRegionSchema(jsonItem)
  273. if jsonItem[regionKeyPropertyName] == *region ||
  274. jsonItem[regionIdentifierPropertyName] == *region {
  275. *region = jsonItem[regionIdentifierPropertyName]
  276. added = true
  277. }
  278. }
  279. }
  280. return added
  281. }
  282. return false
  283. }
  284. func readAndParseConfigFile(configFileName *string) (fileContent []map[string]string, ok bool) {
  285. if content, err := ioutil.ReadFile(*configFileName); err == nil {
  286. Debugf("Raw content of region metadata config file content:", string(content[:]))
  287. if err := json.Unmarshal(content, &fileContent); err != nil {
  288. Debugf("Can't unmarshal config file, the error info is", err)
  289. return
  290. }
  291. ok = true
  292. return
  293. }
  294. Debugf("No Region Metadata Config File provided.")
  295. return
  296. }
  297. // check map regionRealm's region name, if it's already there, no need to add it.
  298. func addRegionSchema(regionSchema map[string]string) {
  299. r := Region(strings.ToLower(regionSchema[regionIdentifierPropertyName]))
  300. if _, ok := regionRealm[r]; !ok {
  301. // set mapping table
  302. shortNameRegion[regionSchema[regionKeyPropertyName]] = r
  303. realm[regionSchema[realmKeyPropertyName]] = regionSchema[realmDomainComponentPropertyName]
  304. regionRealm[r] = regionSchema[realmKeyPropertyName]
  305. return
  306. }
  307. Debugf("Region {} has already been added, no need to add again.", regionSchema[regionIdentifierPropertyName])
  308. }
  309. // check region schema content if all the required contents are provided
  310. func checkSchemaItems(regionSchema map[string]string) bool {
  311. if checkSchemaItem(regionSchema, regionIdentifierPropertyName) &&
  312. checkSchemaItem(regionSchema, realmKeyPropertyName) &&
  313. checkSchemaItem(regionSchema, realmDomainComponentPropertyName) &&
  314. checkSchemaItem(regionSchema, regionKeyPropertyName) {
  315. return true
  316. }
  317. return false
  318. }
  319. // check region schema item is valid, if so, convert it to lower case.
  320. func checkSchemaItem(regionSchema map[string]string, key string) bool {
  321. if val, ok := regionSchema[key]; ok {
  322. if val != "" {
  323. regionSchema[key] = strings.ToLower(val)
  324. return true
  325. }
  326. Debugf("Region metadata schema {} is provided,but content is empty.", key)
  327. return false
  328. }
  329. Debugf("Region metadata schema {} is not provided, please update the content", key)
  330. return false
  331. }
  332. // setRegionFromInstanceMetadataService checks if region metadata can be provided from InstanceMetadataService.
  333. // Once successfully find the expected region(region name or short code), return true, region name will be stored in
  334. // the input pointer.
  335. // setRegionFromInstanceMetadataService will only be checked on the instance, by default it will not be enabled unless
  336. // user explicitly enable it.
  337. func setRegionFromInstanceMetadataService(region *string) bool {
  338. // example of content:
  339. // {
  340. // "realmKey" : "oc1",
  341. // "realmDomainComponent" : "oraclecloud.com",
  342. // "regionKey" : "YUL",
  343. // "regionIdentifier" : "ca-montreal-1"
  344. // }
  345. // Mark visitIMDS Flag as false since it has already been visited.
  346. if visitIMDS == false {
  347. Debugf("check from IMDS is disabled or IMDS had already been successfully visited, no need to check again.")
  348. return false
  349. }
  350. content, err := getRegionInfoFromInstanceMetadataService()
  351. if err != nil {
  352. Debugf("Failed to get instance metadata. Error: %v", err)
  353. return false
  354. }
  355. // Mark visitIMDS Flag as false since we have already successfully get the region info from IMDS.
  356. visitIMDS = false
  357. var regionInfo map[string]string
  358. err = json.Unmarshal(content, &regionInfo)
  359. if err != nil {
  360. Debugf("Failed to unmarshal the response content: %v \nError: %v", string(content), err)
  361. return false
  362. }
  363. if checkSchemaItems(regionInfo) {
  364. addRegionSchema(regionInfo)
  365. if regionInfo[regionKeyPropertyName] == *region ||
  366. regionInfo[regionIdentifierPropertyName] == *region {
  367. *region = regionInfo[regionIdentifierPropertyName]
  368. }
  369. } else {
  370. Debugf("Region information is not valid.")
  371. return false
  372. }
  373. return true
  374. }
  375. // getRegionInfoFromInstanceMetadataServiceProd calls instance metadata service and get the region information
  376. func getRegionInfoFromInstanceMetadataServiceProd() ([]byte, error) {
  377. request, err := http.NewRequest(http.MethodGet, instanceMetadataRegionInfoURLV2, nil)
  378. request.Header.Add("Authorization", "Bearer Oracle")
  379. client := &http.Client{
  380. Timeout: time.Second * 10,
  381. }
  382. resp, err := client.Do(request)
  383. if err != nil {
  384. return nil, fmt.Errorf("Failed to call instance metadata service. Error: %v", err)
  385. }
  386. statusCode := resp.StatusCode
  387. defer resp.Body.Close()
  388. content, err := ioutil.ReadAll(resp.Body)
  389. if err != nil {
  390. return nil, fmt.Errorf("Failed to get region information from response body. Error: %v", err)
  391. }
  392. if statusCode != http.StatusOK {
  393. err = fmt.Errorf("HTTP Get failed: URL: %s, Status: %s, Message: %s",
  394. instanceMetadataRegionInfoURLV2, resp.Status, string(content))
  395. return nil, err
  396. }
  397. return content, nil
  398. }