476 lines
15 KiB
Go
476 lines
15 KiB
Go
// Copyright (c) 2016, 2018, 2021, Oracle and/or its affiliates. All rights reserved.
|
|
// 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.
|
|
|
|
package common
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
//Region type for regions
|
|
type Region string
|
|
|
|
const (
|
|
instanceMetadataRegionInfoURLV2 = "http://169.254.169.254/opc/v2/instance/regionInfo"
|
|
|
|
//RegionSEA region SEA
|
|
RegionSEA Region = "sea"
|
|
//RegionCAToronto1 region for Toronto
|
|
RegionCAToronto1 Region = "ca-toronto-1"
|
|
//RegionCAMontreal1 region for Montreal
|
|
RegionCAMontreal1 Region = "ca-montreal-1"
|
|
//RegionPHX region PHX
|
|
RegionPHX Region = "us-phoenix-1"
|
|
//RegionIAD region IAD
|
|
RegionIAD Region = "us-ashburn-1"
|
|
//RegionSJC1 region SJC
|
|
RegionSJC1 Region = "us-sanjose-1"
|
|
|
|
//RegionFRA region FRA
|
|
RegionFRA Region = "eu-frankfurt-1"
|
|
|
|
//RegionUKCardiff1 region for Cardiff
|
|
RegionUKCardiff1 Region = "uk-cardiff-1"
|
|
//RegionLHR region LHR
|
|
RegionLHR Region = "uk-london-1"
|
|
|
|
//RegionAPTokyo1 region for Tokyo
|
|
RegionAPTokyo1 Region = "ap-tokyo-1"
|
|
//RegionAPOsaka1 region for Osaka
|
|
RegionAPOsaka1 Region = "ap-osaka-1"
|
|
//RegionAPChiyoda1 region for Chiyoda
|
|
RegionAPChiyoda1 Region = "ap-chiyoda-1"
|
|
//RegionAPSeoul1 region for Seoul
|
|
RegionAPSeoul1 Region = "ap-seoul-1"
|
|
//RegionAPChuncheon1 region for Chuncheon
|
|
RegionAPChuncheon1 Region = "ap-chuncheon-1"
|
|
//RegionAPMumbai1 region for Mumbai
|
|
RegionAPMumbai1 Region = "ap-mumbai-1"
|
|
//RegionAPHyderabad1 region for Hyderabad
|
|
RegionAPHyderabad1 Region = "ap-hyderabad-1"
|
|
//RegionAPMelbourne1 region for Melbourne
|
|
RegionAPMelbourne1 Region = "ap-melbourne-1"
|
|
//RegionAPSydney1 region for Sydney
|
|
RegionAPSydney1 Region = "ap-sydney-1"
|
|
|
|
//RegionMEJeddah1 region for Jeddah
|
|
RegionMEJeddah1 Region = "me-jeddah-1"
|
|
//RegionMEDubai1 region for Dubai
|
|
RegionMEDubai1 Region = "me-dubai-1"
|
|
|
|
//RegionEUZurich1 region for Zurich
|
|
RegionEUZurich1 Region = "eu-zurich-1"
|
|
//RegionEUAmsterdam1 region for Amsterdam
|
|
RegionEUAmsterdam1 Region = "eu-amsterdam-1"
|
|
|
|
//RegionSASaopaulo1 region for Sao Paulo
|
|
RegionSASaopaulo1 Region = "sa-saopaulo-1"
|
|
//RegionSASantiago1 region for santiago
|
|
RegionSASantiago1 Region = "sa-santiago-1"
|
|
|
|
//RegionUSLangley1 region for Langley
|
|
RegionUSLangley1 Region = "us-langley-1"
|
|
//RegionUSLuke1 region for Luke
|
|
RegionUSLuke1 Region = "us-luke-1"
|
|
|
|
//RegionUSGovAshburn1 gov region Ashburn
|
|
RegionUSGovAshburn1 Region = "us-gov-ashburn-1"
|
|
//RegionUSGovChicago1 gov region Chicago
|
|
RegionUSGovChicago1 Region = "us-gov-chicago-1"
|
|
//RegionUSGovPhoenix1 region for Phoenix
|
|
RegionUSGovPhoenix1 Region = "us-gov-phoenix-1"
|
|
//RegionUKGovLondon1 gov region London
|
|
RegionUKGovLondon1 Region = "uk-gov-london-1"
|
|
//RegionUKGovCardiff1 gov region Cardiff
|
|
RegionUKGovCardiff1 Region = "uk-gov-cardiff-1"
|
|
|
|
// Region Metadata Configuration File
|
|
regionMetadataCfgDirName = ".oci"
|
|
regionMetadataCfgFileName = "regions-config.json"
|
|
|
|
// Region Metadata Environment Variable
|
|
regionMetadataEnvVarName = "OCI_REGION_METADATA"
|
|
|
|
// Region Metadata
|
|
regionIdentifierPropertyName = "regionIdentifier" // e.g. "ap-sydney-1"
|
|
realmKeyPropertyName = "realmKey" // e.g. "oc1"
|
|
realmDomainComponentPropertyName = "realmDomainComponent" // e.g. "oraclecloud.com"
|
|
regionKeyPropertyName = "regionKey" // e.g. "SYD"
|
|
)
|
|
|
|
var shortNameRegion = map[string]Region{
|
|
"sea": RegionSEA,
|
|
"phx": RegionPHX,
|
|
"iad": RegionIAD,
|
|
"fra": RegionFRA,
|
|
"lhr": RegionLHR,
|
|
"cwl": RegionUKCardiff1,
|
|
"ams": RegionEUAmsterdam1,
|
|
"zrh": RegionEUZurich1,
|
|
"mel": RegionAPMelbourne1,
|
|
"bom": RegionAPMumbai1,
|
|
"hyd": RegionAPHyderabad1,
|
|
"icn": RegionAPSeoul1,
|
|
"yny": RegionAPChuncheon1,
|
|
"nrt": RegionAPTokyo1,
|
|
"kix": RegionAPOsaka1,
|
|
"nja": RegionAPChiyoda1,
|
|
"jed": RegionMEJeddah1,
|
|
"dxb": RegionMEDubai1,
|
|
"syd": RegionAPSydney1,
|
|
"yul": RegionCAMontreal1,
|
|
"yyz": RegionCAToronto1,
|
|
"sjc": RegionSJC1,
|
|
"gru": RegionSASaopaulo1,
|
|
"scl": RegionSASantiago1,
|
|
"ltn": RegionUKGovLondon1,
|
|
"brs": RegionUKGovCardiff1,
|
|
}
|
|
|
|
var realm = map[string]string{
|
|
"oc1": "oraclecloud.com",
|
|
"oc2": "oraclegovcloud.com",
|
|
"oc3": "oraclegovcloud.com",
|
|
"oc4": "oraclegovcloud.uk",
|
|
"oc8": "oraclecloud8.com",
|
|
}
|
|
|
|
var regionRealm = map[Region]string{
|
|
RegionPHX: "oc1",
|
|
RegionIAD: "oc1",
|
|
RegionFRA: "oc1",
|
|
RegionLHR: "oc1",
|
|
RegionSJC1: "oc1",
|
|
|
|
RegionUKCardiff1: "oc1",
|
|
|
|
RegionCAToronto1: "oc1",
|
|
RegionCAMontreal1: "oc1",
|
|
|
|
RegionAPTokyo1: "oc1",
|
|
RegionAPOsaka1: "oc1",
|
|
RegionAPSeoul1: "oc1",
|
|
RegionAPChuncheon1: "oc1",
|
|
RegionAPSydney1: "oc1",
|
|
RegionAPMumbai1: "oc1",
|
|
RegionAPHyderabad1: "oc1",
|
|
RegionAPMelbourne1: "oc1",
|
|
|
|
RegionMEJeddah1: "oc1",
|
|
RegionMEDubai1: "oc1",
|
|
|
|
RegionEUZurich1: "oc1",
|
|
RegionEUAmsterdam1: "oc1",
|
|
|
|
RegionSASaopaulo1: "oc1",
|
|
RegionSASantiago1: "oc1",
|
|
|
|
RegionUSLangley1: "oc2",
|
|
RegionUSLuke1: "oc2",
|
|
|
|
RegionUSGovAshburn1: "oc3",
|
|
RegionUSGovChicago1: "oc3",
|
|
RegionUSGovPhoenix1: "oc3",
|
|
RegionUKGovCardiff1: "oc4",
|
|
RegionUKGovLondon1: "oc4",
|
|
|
|
RegionAPChiyoda1: "oc8",
|
|
}
|
|
|
|
// External region metadata info flag, used to control adding these metadata region info only once.
|
|
var readCfgFile, readEnvVar, visitIMDS bool = true, true, false
|
|
|
|
// getRegionInfoFromInstanceMetadataService gets the region information
|
|
var getRegionInfoFromInstanceMetadataService = getRegionInfoFromInstanceMetadataServiceProd
|
|
|
|
// Endpoint returns a endpoint for a service
|
|
func (region Region) Endpoint(service string) string {
|
|
return fmt.Sprintf("%s.%s.%s", service, region, region.secondLevelDomain())
|
|
}
|
|
|
|
// 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.
|
|
func (region Region) EndpointForTemplate(service string, serviceEndpointTemplate string) string {
|
|
if serviceEndpointTemplate == "" {
|
|
return region.Endpoint(service)
|
|
}
|
|
|
|
// replace service prefix
|
|
endpoint := strings.Replace(serviceEndpointTemplate, "{serviceEndpointPrefix}", service, 1)
|
|
|
|
// replace region
|
|
endpoint = strings.Replace(endpoint, "{region}", string(region), 1)
|
|
|
|
// replace second level domain
|
|
endpoint = strings.Replace(endpoint, "{secondLevelDomain}", region.secondLevelDomain(), 1)
|
|
|
|
return endpoint
|
|
}
|
|
|
|
func (region Region) secondLevelDomain() string {
|
|
if realmID, ok := regionRealm[region]; ok {
|
|
if secondLevelDomain, ok := realm[realmID]; ok {
|
|
return secondLevelDomain
|
|
}
|
|
}
|
|
|
|
Debugf("cannot find realm for region : %s, return default realm value.", region)
|
|
return realm["oc1"]
|
|
}
|
|
|
|
//StringToRegion convert a string to Region type
|
|
func StringToRegion(stringRegion string) (r Region) {
|
|
regionStr := strings.ToLower(stringRegion)
|
|
// check if short region name provided
|
|
if region, ok := shortNameRegion[regionStr]; ok {
|
|
r = region
|
|
return
|
|
}
|
|
// check if normal region name provided
|
|
potentialRegion := Region(regionStr)
|
|
if _, ok := regionRealm[potentialRegion]; ok {
|
|
r = potentialRegion
|
|
return
|
|
}
|
|
|
|
Debugf("region named: %s, is not recognized from hard-coded region list, will check Region metadata info", stringRegion)
|
|
r = checkAndAddRegionMetadata(stringRegion)
|
|
|
|
return
|
|
}
|
|
|
|
// canStringBeRegion test if the string can be a region, if it can, returns the string as is, otherwise it
|
|
// returns an error
|
|
var blankRegex = regexp.MustCompile("\\s")
|
|
|
|
func canStringBeRegion(stringRegion string) (region string, err error) {
|
|
if blankRegex.MatchString(stringRegion) || stringRegion == "" {
|
|
return "", fmt.Errorf("region can not be empty or have spaces")
|
|
}
|
|
return stringRegion, nil
|
|
}
|
|
|
|
// check region info from original map
|
|
func checkAndAddRegionMetadata(region string) Region {
|
|
switch {
|
|
case setRegionMetadataFromCfgFile(®ion):
|
|
case setRegionMetadataFromEnvVar(®ion):
|
|
case setRegionFromInstanceMetadataService(®ion):
|
|
default:
|
|
//err := fmt.Errorf("failed to get region metadata information.")
|
|
return Region(region)
|
|
}
|
|
return Region(region)
|
|
}
|
|
|
|
// EnableInstanceMetadataServiceLookup provides the interface to lookup IMDS region info
|
|
func EnableInstanceMetadataServiceLookup() {
|
|
Debugf("Set visitIMDS 'true' to enable IMDS Lookup.")
|
|
visitIMDS = true
|
|
}
|
|
|
|
// setRegionMetadataFromEnvVar checks if region metadata env variable is provided, once it's there, parse and added it
|
|
// to region map, and it can make sure the env var can only be visited once.
|
|
// Once successfully find the expected region(region name or short code), return true, region name will be stored in
|
|
// the input pointer.
|
|
func setRegionMetadataFromEnvVar(region *string) bool {
|
|
if readEnvVar == false {
|
|
Debugf("metadata region env variable had already been checked, no need to check again.")
|
|
return false //no need to check it again.
|
|
}
|
|
// Mark readEnvVar Flag as false since it has already been visited.
|
|
readEnvVar = false
|
|
// check from env variable
|
|
if jsonStr, existed := os.LookupEnv(regionMetadataEnvVarName); existed {
|
|
Debugf("Raw content of region metadata env var:", jsonStr)
|
|
var regionSchema map[string]string
|
|
if err := json.Unmarshal([]byte(jsonStr), ®ionSchema); err != nil {
|
|
Debugf("Can't unmarshal env var, the error info is", err)
|
|
return false
|
|
}
|
|
// check if the specified region is in the env var.
|
|
if checkSchemaItems(regionSchema) {
|
|
// set mapping table
|
|
addRegionSchema(regionSchema)
|
|
if regionSchema[regionKeyPropertyName] == *region ||
|
|
regionSchema[regionIdentifierPropertyName] == *region {
|
|
*region = regionSchema[regionIdentifierPropertyName]
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
Debugf("The Region Metadata Schema wasn't set in env variable - OCI_REGION_METADATA.")
|
|
return false
|
|
}
|
|
|
|
// setRegionMetadataFromCfgFile checks if region metadata config file is provided, once it's there, parse and add all
|
|
// the valid regions to region map, the configuration file can only be visited once.
|
|
// Once successfully find the expected region(region name or short code), return true, region name will be stored in
|
|
// the input pointer.
|
|
func setRegionMetadataFromCfgFile(region *string) bool {
|
|
if readCfgFile == false {
|
|
Debugf("metadata region config file had already been checked, no need to check again.")
|
|
return false //no need to check it again.
|
|
}
|
|
// Mark readCfgFile Flag as false since it has already been visited.
|
|
readCfgFile = false
|
|
homeFolder := getHomeFolder()
|
|
configFile := path.Join(homeFolder, regionMetadataCfgDirName, regionMetadataCfgFileName)
|
|
if jsonArr, ok := readAndParseConfigFile(&configFile); ok {
|
|
added := false
|
|
for _, jsonItem := range jsonArr {
|
|
if checkSchemaItems(jsonItem) {
|
|
addRegionSchema(jsonItem)
|
|
if jsonItem[regionKeyPropertyName] == *region ||
|
|
jsonItem[regionIdentifierPropertyName] == *region {
|
|
*region = jsonItem[regionIdentifierPropertyName]
|
|
added = true
|
|
}
|
|
}
|
|
}
|
|
return added
|
|
}
|
|
return false
|
|
}
|
|
|
|
func readAndParseConfigFile(configFileName *string) (fileContent []map[string]string, ok bool) {
|
|
|
|
if content, err := ioutil.ReadFile(*configFileName); err == nil {
|
|
Debugf("Raw content of region metadata config file content:", string(content[:]))
|
|
if err := json.Unmarshal(content, &fileContent); err != nil {
|
|
Debugf("Can't unmarshal config file, the error info is", err)
|
|
return
|
|
}
|
|
ok = true
|
|
return
|
|
}
|
|
Debugf("No Region Metadata Config File provided.")
|
|
return
|
|
|
|
}
|
|
|
|
// check map regionRealm's region name, if it's already there, no need to add it.
|
|
func addRegionSchema(regionSchema map[string]string) {
|
|
r := Region(strings.ToLower(regionSchema[regionIdentifierPropertyName]))
|
|
if _, ok := regionRealm[r]; !ok {
|
|
// set mapping table
|
|
shortNameRegion[regionSchema[regionKeyPropertyName]] = r
|
|
realm[regionSchema[realmKeyPropertyName]] = regionSchema[realmDomainComponentPropertyName]
|
|
regionRealm[r] = regionSchema[realmKeyPropertyName]
|
|
return
|
|
}
|
|
Debugf("Region {} has already been added, no need to add again.", regionSchema[regionIdentifierPropertyName])
|
|
}
|
|
|
|
// check region schema content if all the required contents are provided
|
|
func checkSchemaItems(regionSchema map[string]string) bool {
|
|
if checkSchemaItem(regionSchema, regionIdentifierPropertyName) &&
|
|
checkSchemaItem(regionSchema, realmKeyPropertyName) &&
|
|
checkSchemaItem(regionSchema, realmDomainComponentPropertyName) &&
|
|
checkSchemaItem(regionSchema, regionKeyPropertyName) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// check region schema item is valid, if so, convert it to lower case.
|
|
func checkSchemaItem(regionSchema map[string]string, key string) bool {
|
|
if val, ok := regionSchema[key]; ok {
|
|
if val != "" {
|
|
regionSchema[key] = strings.ToLower(val)
|
|
return true
|
|
}
|
|
Debugf("Region metadata schema {} is provided,but content is empty.", key)
|
|
return false
|
|
}
|
|
Debugf("Region metadata schema {} is not provided, please update the content", key)
|
|
return false
|
|
}
|
|
|
|
// setRegionFromInstanceMetadataService checks if region metadata can be provided from InstanceMetadataService.
|
|
// Once successfully find the expected region(region name or short code), return true, region name will be stored in
|
|
// the input pointer.
|
|
// setRegionFromInstanceMetadataService will only be checked on the instance, by default it will not be enabled unless
|
|
// user explicitly enable it.
|
|
func setRegionFromInstanceMetadataService(region *string) bool {
|
|
// example of content:
|
|
// {
|
|
// "realmKey" : "oc1",
|
|
// "realmDomainComponent" : "oraclecloud.com",
|
|
// "regionKey" : "YUL",
|
|
// "regionIdentifier" : "ca-montreal-1"
|
|
// }
|
|
// Mark visitIMDS Flag as false since it has already been visited.
|
|
if visitIMDS == false {
|
|
Debugf("check from IMDS is disabled or IMDS had already been successfully visited, no need to check again.")
|
|
return false
|
|
}
|
|
content, err := getRegionInfoFromInstanceMetadataService()
|
|
if err != nil {
|
|
Debugf("Failed to get instance metadata. Error: %v", err)
|
|
return false
|
|
}
|
|
|
|
// Mark visitIMDS Flag as false since we have already successfully get the region info from IMDS.
|
|
visitIMDS = false
|
|
|
|
var regionInfo map[string]string
|
|
err = json.Unmarshal(content, ®ionInfo)
|
|
if err != nil {
|
|
Debugf("Failed to unmarshal the response content: %v \nError: %v", string(content), err)
|
|
return false
|
|
}
|
|
|
|
if checkSchemaItems(regionInfo) {
|
|
addRegionSchema(regionInfo)
|
|
if regionInfo[regionKeyPropertyName] == *region ||
|
|
regionInfo[regionIdentifierPropertyName] == *region {
|
|
*region = regionInfo[regionIdentifierPropertyName]
|
|
}
|
|
} else {
|
|
Debugf("Region information is not valid.")
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// getRegionInfoFromInstanceMetadataServiceProd calls instance metadata service and get the region information
|
|
func getRegionInfoFromInstanceMetadataServiceProd() ([]byte, error) {
|
|
request, err := http.NewRequest(http.MethodGet, instanceMetadataRegionInfoURLV2, nil)
|
|
request.Header.Add("Authorization", "Bearer Oracle")
|
|
|
|
client := &http.Client{
|
|
Timeout: time.Second * 10,
|
|
}
|
|
resp, err := client.Do(request)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to call instance metadata service. Error: %v", err)
|
|
}
|
|
|
|
statusCode := resp.StatusCode
|
|
|
|
defer resp.Body.Close()
|
|
|
|
content, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to get region information from response body. Error: %v", err)
|
|
}
|
|
|
|
if statusCode != http.StatusOK {
|
|
err = fmt.Errorf("HTTP Get failed: URL: %s, Status: %s, Message: %s",
|
|
instanceMetadataRegionInfoURLV2, resp.Status, string(content))
|
|
return nil, err
|
|
}
|
|
|
|
return content, nil
|
|
}
|