common.go raw

   1  // Copyright (c) 2016, 2018, 2025, 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  
   4  package common
   5  
   6  import (
   7  	"encoding/json"
   8  	"fmt"
   9  	"io/ioutil"
  10  	"net/http"
  11  	"os"
  12  	"path/filepath"
  13  	"regexp"
  14  	"strings"
  15  	"sync"
  16  	"time"
  17  )
  18  
  19  // Region type for regions
  20  type Region string
  21  
  22  const (
  23  	instanceMetadataRegionInfoURLV2 = "http://169.254.169.254/opc/v2/instance/regionInfo"
  24  
  25  	// Region Metadata Configuration File
  26  	regionMetadataCfgDirName  = ".oci"
  27  	regionMetadataCfgFileName = "regions-config.json"
  28  
  29  	// Region Metadata Environment Variable
  30  	regionMetadataEnvVarName = "OCI_REGION_METADATA"
  31  
  32  	// Default Realm Environment Variable
  33  	defaultRealmEnvVarName = "OCI_DEFAULT_REALM"
  34  
  35  	//EndpointTemplateForRegionWithDot Environment Variable
  36  	EndpointTemplateForRegionWithDot = "https://{endpoint_service_name}.{region}"
  37  
  38  	// Region Metadata
  39  	regionIdentifierPropertyName     = "regionIdentifier"     // e.g. "ap-sydney-1"
  40  	realmKeyPropertyName             = "realmKey"             // e.g. "oc1"
  41  	realmDomainComponentPropertyName = "realmDomainComponent" // e.g. "oraclecloud.com"
  42  	regionKeyPropertyName            = "regionKey"            // e.g. "SYD"
  43  
  44  	// OciRealmSpecificServiceEndpointTemplateEnabledEnvVar is the environment variable name to enable the realm specific service endpoint template.
  45  	OciRealmSpecificServiceEndpointTemplateEnabledEnvVar = "OCI_REALM_SPECIFIC_SERVICE_ENDPOINT_TEMPLATE_ENABLED"
  46  )
  47  
  48  // External region metadata info flag, used to control adding these metadata region info only once.
  49  var readCfgFile, readEnvVar, visitIMDS bool = true, true, false
  50  
  51  // getRegionInfoFromInstanceMetadataService gets the region information
  52  var getRegionInfoFromInstanceMetadataService = getRegionInfoFromInstanceMetadataServiceProd
  53  
  54  // OciRealmSpecificServiceEndpointTemplateEnabled is the flag to enable the realm specific service endpoint template. This one has higher priority than the environment variable.
  55  var OciRealmSpecificServiceEndpointTemplateEnabled *bool = nil
  56  
  57  // reNonWord precomiles the regex once at the package scope
  58  var reNonWord = regexp.MustCompile(`[^\w]`)
  59  
  60  // OciSdkEnabledServicesMap is a list of services that are enabled, default is an empty list which means all services are enabled
  61  var OciSdkEnabledServicesMap map[string]bool
  62  
  63  // OciSdkEnabledServicesOnce is a sync.Once variable to ensure the OciSdkEnabledServicesMap is initialized only once
  64  var OciSdkEnabledServicesOnce sync.Once
  65  
  66  // OciSdkEnabledServicesMu is a mutex to protect access to the OciSdkEnabledServicesMap
  67  var OciSdkEnabledServicesMu sync.RWMutex
  68  
  69  // OciDeveloperToolConfigurationFilePathEnvVar is the environment variable name for the OCI Developer Tool Config File Path
  70  const OciDeveloperToolConfigurationFilePathEnvVar = "OCI_DEVELOPER_TOOL_CONFIGURATION_FILE_PATH"
  71  
  72  // OciAllowOnlyDeveloperToolConfigurationRegionsEnvVar is the environment variable name for the OCI Allow only Dev Tool Config Regions
  73  const OciAllowOnlyDeveloperToolConfigurationRegionsEnvVar = "OCI_ALLOW_ONLY_DEVELOPER_TOOL_CONFIGURATION_REGIONS"
  74  
  75  // defaultRealmForUnknownDeveloperToolConfigurationRegion is the default realm for unknown Developer Tool Configuration Regions
  76  const defaultRealmForUnknownDeveloperToolConfigurationRegion = "oraclecloud.com"
  77  
  78  // OciDeveloperToolConfigurationProvider is the provider name for the OCI Developer Tool Configuration file
  79  var OciDeveloperToolConfigurationProvider string
  80  
  81  // ociAllowOnlyDeveloperToolConfigurationRegions is the flag to enable the OCI Allow Only Developer Tool Configuration Regions. This one has lower priority than the environment variable.
  82  var ociAllowOnlyDeveloperToolConfigurationRegions bool
  83  
  84  var ociDeveloperToolConfigurationRegionSchemaList []map[string]string
  85  
  86  // Endpoint returns a endpoint for a service
  87  func (region Region) Endpoint(service string) string {
  88  	// Endpoint for dotted region
  89  	if strings.Contains(string(region), ".") {
  90  		return fmt.Sprintf("%s.%s", service, region)
  91  	}
  92  	return fmt.Sprintf("%s.%s.%s", service, region, region.SecondLevelDomain())
  93  }
  94  
  95  // 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.
  96  func (region Region) EndpointForTemplate(service string, serviceEndpointTemplate string) string {
  97  	if strings.Contains(string(region), ".") {
  98  		endpoint, error := region.EndpointForTemplateDottedRegion(service, serviceEndpointTemplate, "")
  99  		if error != nil {
 100  			Debugf("%v", error)
 101  
 102  			return ""
 103  		}
 104  		return endpoint
 105  	}
 106  
 107  	if serviceEndpointTemplate == "" {
 108  		return region.Endpoint(service)
 109  	}
 110  
 111  	// replace service prefix
 112  	endpoint := strings.Replace(serviceEndpointTemplate, "{serviceEndpointPrefix}", service, 1)
 113  
 114  	// replace region
 115  	endpoint = strings.Replace(endpoint, "{region}", string(region), 1)
 116  
 117  	// replace second level domain
 118  	endpoint = strings.Replace(endpoint, "{secondLevelDomain}", region.SecondLevelDomain(), 1)
 119  
 120  	return endpoint
 121  }
 122  
 123  // EndpointForTemplateDottedRegion returns a endpoint for a service based on the service name and EndpointTemplateForRegionWithDot template. If a service name is missing it is obtained from serviceEndpointTemplate and endpoint is constructed usingEndpointTemplateForRegionWithDot template.
 124  func (region Region) EndpointForTemplateDottedRegion(service string, serviceEndpointTemplate string, endpointServiceName string) (string, error) {
 125  	if !strings.Contains(string(region), ".") {
 126  		var endpoint = ""
 127  		if serviceEndpointTemplate != "" {
 128  			endpoint = region.EndpointForTemplate(service, serviceEndpointTemplate)
 129  			return endpoint, nil
 130  		}
 131  		endpoint = region.EndpointForTemplate(service, "")
 132  		return endpoint, nil
 133  	}
 134  
 135  	if endpointServiceName != "" {
 136  		endpoint := strings.Replace(EndpointTemplateForRegionWithDot, "{endpoint_service_name}", endpointServiceName, 1)
 137  		endpoint = strings.Replace(endpoint, "{region}", string(region), 1)
 138  		Debugf("Constructing endpoint from service name %s and region %s. Endpoint: %s", endpointServiceName, region, endpoint)
 139  		return endpoint, nil
 140  	}
 141  	if serviceEndpointTemplate != "" {
 142  		var endpoint = ""
 143  		res := strings.Split(serviceEndpointTemplate, "//")
 144  		if len(res) > 1 {
 145  			res = strings.Split(res[1], ".")
 146  			if len(res) > 1 {
 147  				endpoint = strings.Replace(EndpointTemplateForRegionWithDot, "{endpoint_service_name}", res[0], 1)
 148  				endpoint = strings.Replace(endpoint, "{region}", string(region), 1)
 149  				Debugf("Constructing endpoint from service endpoint template %s and region %s. Endpoint: %s", serviceEndpointTemplate, region, endpoint)
 150  			} else {
 151  				return endpoint, fmt.Errorf("Endpoint service name not present in endpoint template")
 152  			}
 153  		} else {
 154  			return endpoint, fmt.Errorf("invalid serviceEndpointTemplates. ServiceEndpointTemplate should start with https://")
 155  		}
 156  		return endpoint, nil
 157  	}
 158  	return "", fmt.Errorf("EndpointForTemplateDottedRegion function requires endpointServiceName or serviceEndpointTemplate, no endpointServiceName or serviceEndpointTemplate provided")
 159  }
 160  
 161  func (region Region) SecondLevelDomain() string {
 162  	if realmID, ok := regionRealm[region]; ok {
 163  		if secondLevelDomain, ok := realm[realmID]; ok {
 164  			return secondLevelDomain
 165  		}
 166  	}
 167  	if value, ok := os.LookupEnv(defaultRealmEnvVarName); ok {
 168  		return value
 169  	}
 170  	Debugf("cannot find realm for region : %s, return default realm value.", region)
 171  	if _, ok := realm["oc1"]; !ok {
 172  		return defaultRealmForUnknownDeveloperToolConfigurationRegion
 173  	}
 174  	return realm["oc1"]
 175  }
 176  
 177  // RealmID is used for getting realmID from region, if no region found, directly throw error
 178  func (region Region) RealmID() (string, error) {
 179  	if realmID, ok := regionRealm[region]; ok {
 180  		return realmID, nil
 181  	}
 182  
 183  	return "", fmt.Errorf("cannot find realm for region : %s", region)
 184  }
 185  
 186  // StringToRegion convert a string to Region type
 187  func StringToRegion(stringRegion string) (r Region) {
 188  	regionStr := strings.ToLower(stringRegion)
 189  	// check for PLC related regions
 190  	if checkAllowOnlyDeveloperToolConfigurationRegions() && (checkDeveloperToolConfigurationFile() || len(ociDeveloperToolConfigurationRegionSchemaList) != 0) {
 191  		Debugf("Developer Tool config detected and OCI_ALLOW_ONLY_DEVELOPER_TOOL_CONFIGURATION_REGIONS is set to True, SDK will only use regions defined for Developer Tool Configuration Regions")
 192  		setRegionMetadataFromDeveloperToolConfigurationFile(&stringRegion)
 193  		if len(ociDeveloperToolConfigurationRegionSchemaList) != 0 {
 194  			resetRegionInfo()
 195  			bulkAddRegionSchema(ociDeveloperToolConfigurationRegionSchemaList)
 196  		}
 197  		r = Region(stringRegion)
 198  		if _, ok := regionRealm[r]; !ok {
 199  			Logf("You're using the %s Developer Tool configuration file, the region you're targeting is not declared in this config file. Please check if this is the correct region you're targeting or contact the %s cloud provider for help. If you want to target both OCI regions and %s regions, please set the OCI_ALLOW_ONLY_DEVELOPER_TOOL_CONFIGURATION_REGIONS env var to False.", OciDeveloperToolConfigurationProvider, OciDeveloperToolConfigurationProvider, regionStr)
 200  		}
 201  		return r
 202  	}
 203  
 204  	// check if short region name provided
 205  	if region, ok := shortNameRegion[regionStr]; ok {
 206  		r = region
 207  		return
 208  	}
 209  	// check if normal region name provided
 210  	potentialRegion := Region(regionStr)
 211  	if _, ok := regionRealm[potentialRegion]; ok {
 212  		r = potentialRegion
 213  		return
 214  	}
 215  
 216  	Debugf("region named: %s, is not recognized from hard-coded region list, will check Region metadata info", stringRegion)
 217  	r = checkAndAddRegionMetadata(stringRegion)
 218  
 219  	return
 220  }
 221  
 222  // canStringBeRegion test if the string can be a region, if it can, returns the string as is, otherwise it
 223  // returns an error
 224  var blankRegex = regexp.MustCompile(`\s`)
 225  
 226  func canStringBeRegion(stringRegion string) (region string, err error) {
 227  	if blankRegex.MatchString(stringRegion) || stringRegion == "" {
 228  		return "", fmt.Errorf("region can not be empty or have spaces")
 229  	}
 230  	return stringRegion, nil
 231  }
 232  
 233  // check region info from original map
 234  func checkAndAddRegionMetadata(region string) Region {
 235  	switch {
 236  	case setRegionMetadataFromCfgFile(&region):
 237  	case setRegionMetadataFromEnvVar(&region):
 238  	case setRegionFromInstanceMetadataService(&region):
 239  	default:
 240  		//err := fmt.Errorf("failed to get region metadata information.")
 241  		return Region(region)
 242  	}
 243  	return Region(region)
 244  }
 245  
 246  // EnableInstanceMetadataServiceLookup provides the interface to lookup IMDS region info
 247  func EnableInstanceMetadataServiceLookup() {
 248  	Debugf("Set visitIMDS 'true' to enable IMDS Lookup.")
 249  	visitIMDS = true
 250  }
 251  
 252  // setRegionMetadataFromEnvVar checks if region metadata env variable is provided, once it's there, parse and added it
 253  // to region map, and it can make sure the env var can only be visited once.
 254  // Once successfully find the expected region(region name or short code), return true, region name will be stored in
 255  // the input pointer.
 256  func setRegionMetadataFromEnvVar(region *string) bool {
 257  	if !readEnvVar {
 258  		Debugf("metadata region env variable had already been checked, no need to check again.")
 259  		return false //no need to check it again.
 260  	}
 261  	// Mark readEnvVar Flag as false since it has already been visited.
 262  	readEnvVar = false
 263  	// check from env variable
 264  	if jsonStr, existed := os.LookupEnv(regionMetadataEnvVarName); existed {
 265  		Debugf("Raw content of region metadata env var:", jsonStr)
 266  		var regionSchema map[string]string
 267  		if err := json.Unmarshal([]byte(jsonStr), &regionSchema); err != nil {
 268  			Debugf("Can't unmarshal env var, the error info is", err)
 269  			return false
 270  		}
 271  		// check if the specified region is in the env var.
 272  		if checkSchemaItems(regionSchema) {
 273  			// set mapping table
 274  			addRegionSchema(regionSchema)
 275  			if regionSchema[regionKeyPropertyName] == *region ||
 276  				regionSchema[regionIdentifierPropertyName] == *region {
 277  				*region = regionSchema[regionIdentifierPropertyName]
 278  				return true
 279  			}
 280  		}
 281  		return false
 282  	}
 283  	Debugf("The Region Metadata Schema wasn't set in env variable - OCI_REGION_METADATA.")
 284  	return false
 285  }
 286  
 287  func setRegionMetadataFromCfgFile(region *string) bool {
 288  	if setRegionMetadataFromDeveloperToolConfigurationFile(region) {
 289  		return true
 290  	}
 291  	if setRegionMetadataFromRegionCfgFile(region) {
 292  		return true
 293  	}
 294  	return false
 295  }
 296  
 297  // setRegionMetadataFromCfgFile checks if region metadata config file is provided, once it's there, parse and add all
 298  // the valid regions to region map, the configuration file can only be visited once.
 299  // Once successfully find the expected region(region name or short code), return true, region name will be stored in
 300  // the input pointer.
 301  func setRegionMetadataFromRegionCfgFile(region *string) bool {
 302  	if !readCfgFile {
 303  		Debugf("metadata region config file had already been checked, no need to check again.")
 304  		return false //no need to check it again.
 305  	}
 306  	// Mark readCfgFile Flag as false since it has already been visited.
 307  	readCfgFile = false
 308  	homeFolder := getHomeFolder()
 309  	configFile := filepath.Join(homeFolder, regionMetadataCfgDirName, regionMetadataCfgFileName)
 310  	if jsonArr, ok := readAndParseConfigFile(&configFile); ok {
 311  		added := false
 312  		for _, jsonItem := range jsonArr {
 313  			if checkSchemaItems(jsonItem) {
 314  				addRegionSchema(jsonItem)
 315  				if jsonItem[regionKeyPropertyName] == *region ||
 316  					jsonItem[regionIdentifierPropertyName] == *region {
 317  					*region = jsonItem[regionIdentifierPropertyName]
 318  					added = true
 319  				}
 320  			}
 321  		}
 322  		return added
 323  	}
 324  	return false
 325  }
 326  
 327  // setRegionMetadataFromDeveloperToolConfigurationFile checks if Developer Tool config file is provided, once it's there, parse and add all
 328  // The default location of the Developer Tool config file is ~/.oci/developer-tool-configuration.json. It will also check the environment variable
 329  // the valid regions to region map, the configuration file can only be visited once.
 330  // Once successfully find the expected region(region name or short code), return true, region name will be stored in
 331  // the input pointer.
 332  func setRegionMetadataFromDeveloperToolConfigurationFile(region *string) bool {
 333  	if jsonArr, ok := readAndParseDeveloperToolConfigurationFile(); ok {
 334  		added := false
 335  		if jsonArr["regions"] == nil {
 336  			return false
 337  		}
 338  		var regionJSON []map[string]string
 339  		originalJSONContent, err := json.Marshal(jsonArr["regions"])
 340  		if err != nil {
 341  			return false
 342  		}
 343  		err = json.Unmarshal(originalJSONContent, &regionJSON)
 344  		if err != nil {
 345  			return false
 346  		}
 347  
 348  		if IsEnvVarTrue(OciAllowOnlyDeveloperToolConfigurationRegionsEnvVar) {
 349  			resetRegionInfo()
 350  		}
 351  		for _, jsonItem := range regionJSON {
 352  			if checkSchemaItems(jsonItem) {
 353  				addRegionSchema(jsonItem)
 354  				if jsonItem[regionKeyPropertyName] == *region ||
 355  					jsonItem[regionIdentifierPropertyName] == *region {
 356  					*region = jsonItem[regionIdentifierPropertyName]
 357  					added = true
 358  				}
 359  			}
 360  		}
 361  		return added
 362  	}
 363  	return false
 364  }
 365  
 366  func readAndParseConfigFile(configFileName *string) (fileContent []map[string]string, ok bool) {
 367  	if content, err := ioutil.ReadFile(*configFileName); err == nil {
 368  		Debugf("Raw content of region metadata config file content:", string(content[:]))
 369  		if err := json.Unmarshal(content, &fileContent); err != nil {
 370  			Debugf("Can't unmarshal config file, the error info is", err)
 371  			return
 372  		}
 373  		ok = true
 374  		return
 375  	}
 376  	Debugf("No Region Metadata Config File provided.")
 377  	return
 378  }
 379  
 380  func readAndParseDeveloperToolConfigurationFile() (fileContent map[string]interface{}, ok bool) {
 381  	homeFolder := getHomeFolder()
 382  	configFileName := filepath.Join(homeFolder, regionMetadataCfgDirName, "developer-tool-configuration.json")
 383  	if path := os.Getenv(OciDeveloperToolConfigurationFilePathEnvVar); path != "" {
 384  		configFileName = path
 385  	}
 386  	if content, err := ioutil.ReadFile(configFileName); err == nil {
 387  		Debugf("Raw content of Developer Tool config file content:", string(content[:]))
 388  		if err := json.Unmarshal(content, &fileContent); err != nil {
 389  			Debugf("Can't unmarshal env var, the error info is", err)
 390  			return
 391  		}
 392  		ok = true
 393  		return
 394  	}
 395  	Debugf("No Developer Tool Config File provided.")
 396  	return
 397  }
 398  
 399  func checkDeveloperToolConfigurationFile() bool {
 400  	homeFolder := getHomeFolder()
 401  	configFileName := filepath.Join(homeFolder, regionMetadataCfgDirName, "developer-tool-configuration.json")
 402  	if path := os.Getenv(OciDeveloperToolConfigurationFilePathEnvVar); path != "" {
 403  		configFileName = path
 404  	}
 405  	if _, err := os.Stat(configFileName); err == nil {
 406  		return true
 407  	}
 408  	return false
 409  }
 410  
 411  // check map regionRealm's region name, if it's already there, no need to add it.
 412  func addRegionSchema(regionSchema map[string]string) {
 413  	r := Region(strings.ToLower(regionSchema[regionIdentifierPropertyName]))
 414  	if _, ok := regionRealm[r]; !ok {
 415  		// set mapping table
 416  		shortNameRegion[regionSchema[regionKeyPropertyName]] = r
 417  		realm[regionSchema[realmKeyPropertyName]] = regionSchema[realmDomainComponentPropertyName]
 418  		regionRealm[r] = regionSchema[realmKeyPropertyName]
 419  		return
 420  	}
 421  	Debugf("Region {} has already been added, no need to add again.", regionSchema[regionIdentifierPropertyName])
 422  }
 423  
 424  // AddRegionSchemaForPlc add region schema to region map
 425  func AddRegionSchemaForPlc(regionSchema map[string]string) {
 426  	ociDeveloperToolConfigurationRegionSchemaList = append(ociDeveloperToolConfigurationRegionSchemaList, regionSchema)
 427  	addRegionSchema(regionSchema)
 428  	// if !IsEnvVarTrue(OciPlcRegionExclusiveEnvVar) {
 429  	// 	addRegionSchema(regionSchema)
 430  	// 	return
 431  	// }
 432  	// Debugf("Plc region coexist is not enabled, remove exisiting OCI region schema and add PLC region schema.")
 433  	// resetRegionInfo()
 434  	// bulkAddRegionSchema(ociPlcRegionSchemaList)
 435  }
 436  
 437  func resetRegionInfo() {
 438  	shortNameRegion = make(map[string]Region)
 439  	realm = make(map[string]string)
 440  	regionRealm = make(map[Region]string)
 441  }
 442  
 443  func bulkAddRegionSchema(regionSchemaList []map[string]string) {
 444  	for _, regionSchema := range regionSchemaList {
 445  		if checkSchemaItems(regionSchema) {
 446  			addRegionSchema(regionSchema)
 447  		}
 448  	}
 449  }
 450  
 451  // check region schema content if all the required contents are provided
 452  func checkSchemaItems(regionSchema map[string]string) bool {
 453  	if checkSchemaItem(regionSchema, regionIdentifierPropertyName) &&
 454  		checkSchemaItem(regionSchema, realmKeyPropertyName) &&
 455  		checkSchemaItem(regionSchema, realmDomainComponentPropertyName) &&
 456  		checkSchemaItem(regionSchema, regionKeyPropertyName) {
 457  		return true
 458  	}
 459  	return false
 460  }
 461  
 462  // check region schema item is valid, if so, convert it to lower case.
 463  func checkSchemaItem(regionSchema map[string]string, key string) bool {
 464  	if val, ok := regionSchema[key]; ok {
 465  		if val != "" {
 466  			regionSchema[key] = strings.ToLower(val)
 467  			return true
 468  		}
 469  		Debugf("Region metadata schema {} is provided,but content is empty.", key)
 470  		return false
 471  	}
 472  	Debugf("Region metadata schema {} is not provided, please update the content", key)
 473  	return false
 474  }
 475  
 476  // setRegionFromInstanceMetadataService checks if region metadata can be provided from InstanceMetadataService.
 477  // Once successfully find the expected region(region name or short code), return true, region name will be stored in
 478  // the input pointer.
 479  // setRegionFromInstanceMetadataService will only be checked on the instance, by default it will not be enabled unless
 480  // user explicitly enable it.
 481  func setRegionFromInstanceMetadataService(region *string) bool {
 482  	// example of content:
 483  	// {
 484  	// 	"realmKey" : "oc1",
 485  	// 	"realmDomainComponent" : "oraclecloud.com",
 486  	// 	"regionKey" : "YUL",
 487  	// 	"regionIdentifier" : "ca-montreal-1"
 488  	// }
 489  	// Mark visitIMDS Flag as false since it has already been visited.
 490  	if !visitIMDS {
 491  		Debugf("check from IMDS is disabled or IMDS had already been successfully visited, no need to check again.")
 492  		return false
 493  	}
 494  	content, err := getRegionInfoFromInstanceMetadataService()
 495  	if err != nil {
 496  		Debugf("Failed to get instance metadata. Error: %v", err)
 497  		return false
 498  	}
 499  
 500  	// Mark visitIMDS Flag as false since we have already successfully get the region info from IMDS.
 501  	visitIMDS = false
 502  
 503  	var regionInfo map[string]string
 504  	err = json.Unmarshal(content, &regionInfo)
 505  	if err != nil {
 506  		Debugf("Failed to unmarshal the response content: %v \nError: %v", string(content), err)
 507  		return false
 508  	}
 509  
 510  	if checkSchemaItems(regionInfo) {
 511  		addRegionSchema(regionInfo)
 512  		if regionInfo[regionKeyPropertyName] == *region ||
 513  			regionInfo[regionIdentifierPropertyName] == *region {
 514  			*region = regionInfo[regionIdentifierPropertyName]
 515  		}
 516  	} else {
 517  		Debugf("Region information is not valid.")
 518  		return false
 519  	}
 520  
 521  	return true
 522  }
 523  
 524  // getRegionInfoFromInstanceMetadataServiceProd calls instance metadata service and get the region information
 525  func getRegionInfoFromInstanceMetadataServiceProd() ([]byte, error) {
 526  	request, _ := http.NewRequest(http.MethodGet, instanceMetadataRegionInfoURLV2, nil)
 527  	request.Header.Add("Authorization", "Bearer Oracle")
 528  
 529  	client := &http.Client{
 530  		Timeout: time.Second * 10,
 531  	}
 532  	resp, err := client.Do(request)
 533  	if err != nil {
 534  		return nil, fmt.Errorf("failed to call instance metadata service. Error: %v", err)
 535  	}
 536  
 537  	statusCode := resp.StatusCode
 538  
 539  	defer resp.Body.Close()
 540  
 541  	content, err := ioutil.ReadAll(resp.Body)
 542  	if err != nil {
 543  		return nil, fmt.Errorf("failed to get region information from response body. Error: %v", err)
 544  	}
 545  
 546  	if statusCode != http.StatusOK {
 547  		err = fmt.Errorf("HTTP Get failed: URL: %s, Status: %s, Message: %s",
 548  			instanceMetadataRegionInfoURLV2, resp.Status, string(content))
 549  		return nil, err
 550  	}
 551  
 552  	return content, nil
 553  }
 554  
 555  // TemplateParamForPerRealmEndpoint is a template parameter for per-realm endpoint.
 556  type TemplateParamForPerRealmEndpoint struct {
 557  	Template    string
 558  	EndsWithDot bool
 559  }
 560  
 561  // SetMissingTemplateParams function will parse the {} template in client host and replace with empty string.
 562  func SetMissingTemplateParams(client *BaseClient) {
 563  	templateRegex := regexp.MustCompile(`{.*?}`)
 564  	templates := templateRegex.FindAllString(client.Host, -1)
 565  	for _, template := range templates {
 566  		client.Host = strings.Replace(client.Host, template, "", -1)
 567  	}
 568  }
 569  
 570  func getOciSdkEnabledServicesMap() map[string]bool {
 571  	var enabledMap = make(map[string]bool)
 572  	if jsonArr, ok := readAndParseDeveloperToolConfigurationFile(); ok {
 573  		if jsonArr["provider"] != nil {
 574  			OciDeveloperToolConfigurationProvider = jsonArr["provider"].(string)
 575  		}
 576  		if jsonArr["allowOnlyDeveloperToolConfigurationRegions"] != nil && jsonArr["allowOnlyDeveloperToolConfigurationRegions"] == false {
 577  			ociAllowOnlyDeveloperToolConfigurationRegions = jsonArr["allowOnlyDeveloperToolConfigurationRegions"].(bool)
 578  		}
 579  		if jsonArr["services"] == nil {
 580  			return enabledMap
 581  		}
 582  		serviesJSON, ok := jsonArr["services"].([]interface{})
 583  		if !ok {
 584  			return enabledMap
 585  		}
 586  		re, _ := regexp.Compile(`[^\w]`)
 587  		for _, jsonItem := range serviesJSON {
 588  			serviceName := strings.ToLower(fmt.Sprint(jsonItem))
 589  			serviceName = re.ReplaceAllString(serviceName, "")
 590  			enabledMap[serviceName] = true
 591  		}
 592  	}
 593  	return enabledMap
 594  }
 595  
 596  // AddServiceToEnabledServicesMap adds the service to the enabledServiceMap
 597  // The service name will auto transit to lower case and remove all the non-word characters.
 598  // Concurrency (goroutine-safe). The map is initialized with sync.Once, and writes are protected by a RWMutex.
 599  func AddServiceToEnabledServicesMap(serviceName string) {
 600  	OciSdkEnabledServicesOnce.Do(func() {
 601  		OciSdkEnabledServicesMap = getOciSdkEnabledServicesMap()
 602  		if OciSdkEnabledServicesMap == nil {
 603  			OciSdkEnabledServicesMap = make(map[string]bool)
 604  		}
 605  	})
 606  	serviceName = strings.ToLower(serviceName)
 607  	serviceName = reNonWord.ReplaceAllString(serviceName, "")
 608  
 609  	OciSdkEnabledServicesMu.Lock()
 610  	defer OciSdkEnabledServicesMu.Unlock()
 611  	OciSdkEnabledServicesMap[serviceName] = true
 612  }
 613  
 614  // CheckForEnabledServices checks if the service is enabled in the enabledServiceMap.
 615  // It will first check if the map is initialized, if not, it will initialize the map.
 616  // If the map is empty, it means all the services are enabled.
 617  // If the map is not empty, it means only the services in the map and value is true are enabled.
 618  // Concurrency (goroutine-safe). Initialization uses sync.Once and reads are protected by a RWMutex.
 619  func CheckForEnabledServices(serviceName string) bool {
 620  	OciSdkEnabledServicesOnce.Do(func() {
 621  		OciSdkEnabledServicesMap = getOciSdkEnabledServicesMap()
 622  		if OciSdkEnabledServicesMap == nil {
 623  			OciSdkEnabledServicesMap = make(map[string]bool)
 624  		}
 625  	})
 626  	serviceName = strings.ToLower(serviceName)
 627  	serviceName = reNonWord.ReplaceAllString(serviceName, "")
 628  
 629  	OciSdkEnabledServicesMu.RLock()
 630  	defer OciSdkEnabledServicesMu.RUnlock()
 631  
 632  	if len(OciSdkEnabledServicesMap) == 0 {
 633  		return true
 634  	}
 635  	allowed, ok := OciSdkEnabledServicesMap[serviceName]
 636  	if !ok {
 637  		return false
 638  	}
 639  	return allowed
 640  }
 641  
 642  // CheckAllowOnlyDeveloperToolConfigurationRegions checks if only developer tool configuration regions are allowed
 643  // This function will first check if the OCI_ALLOW_ONLY_DEVELOPER_TOOL_CONFIGURATION_REGIONS environment variable is set.
 644  // If it is set, it will return the value.
 645  // If it is not set, it will return the value from the ociAllowOnlyDeveloperToolConfigurationRegions variable.
 646  func checkAllowOnlyDeveloperToolConfigurationRegions() bool {
 647  	if val, ok := os.LookupEnv("OCI_ALLOW_ONLY_DEVELOPER_TOOL_CONFIGURATION_REGIONS"); ok {
 648  		return val == "true"
 649  	}
 650  	return ociAllowOnlyDeveloperToolConfigurationRegions
 651  }
 652