golden hour
/opt/saltstack/salt/lib/python3.10/site-packages/salt/utils
⬆️ Go Up
Upload
File/Folder
Size
Actions
__init__.py
237 B
Del
OK
__pycache__
-
Del
OK
aggregation.py
5.17 KB
Del
OK
ansible.py
1.48 KB
Del
OK
args.py
18.33 KB
Del
OK
asynchronous.py
4.06 KB
Del
OK
atomicfile.py
5.33 KB
Del
OK
aws.py
20.37 KB
Del
OK
azurearm.py
11.42 KB
Del
OK
beacons.py
517 B
Del
OK
boto3mod.py
8.36 KB
Del
OK
boto_elb_tag.py
3.02 KB
Del
OK
botomod.py
7.98 KB
Del
OK
cache.py
11.49 KB
Del
OK
channel.py
489 B
Del
OK
cloud.py
116.3 KB
Del
OK
color.py
2.72 KB
Del
OK
compat.py
1.89 KB
Del
OK
configcomparer.py
3.88 KB
Del
OK
configparser.py
10.82 KB
Del
OK
context.py
6.8 KB
Del
OK
crypt.py
5 KB
Del
OK
ctx.py
1.42 KB
Del
OK
data.py
53.03 KB
Del
OK
dateutils.py
2.3 KB
Del
OK
debug.py
4.19 KB
Del
OK
decorators
-
Del
OK
dictdiffer.py
16.48 KB
Del
OK
dicttrim.py
3.9 KB
Del
OK
dictupdate.py
11.33 KB
Del
OK
dns.py
35.21 KB
Del
OK
doc.py
2.25 KB
Del
OK
dockermod
-
Del
OK
entrypoints.py
1.83 KB
Del
OK
environment.py
2.2 KB
Del
OK
error.py
1.18 KB
Del
OK
etcd_util.py
33.25 KB
Del
OK
event.py
52.45 KB
Del
OK
extend.py
8.87 KB
Del
OK
extmods.py
6.04 KB
Del
OK
filebuffer.py
3.15 KB
Del
OK
files.py
27.94 KB
Del
OK
find.py
22.08 KB
Del
OK
fsutils.py
3.29 KB
Del
OK
functools.py
6.02 KB
Del
OK
gitfs.py
130.41 KB
Del
OK
github.py
1.52 KB
Del
OK
gzip_util.py
2.86 KB
Del
OK
hashutils.py
5.91 KB
Del
OK
http.py
33.9 KB
Del
OK
iam.py
1.22 KB
Del
OK
icinga2.py
754 B
Del
OK
idem.py
1.22 KB
Del
OK
immutabletypes.py
2.46 KB
Del
OK
itertools.py
2.36 KB
Del
OK
jid.py
3 KB
Del
OK
jinja.py
33.92 KB
Del
OK
job.py
6.89 KB
Del
OK
json.py
3.78 KB
Del
OK
kickstart.py
41.04 KB
Del
OK
kinds.py
493 B
Del
OK
lazy.py
3.06 KB
Del
OK
listdiffer.py
10.9 KB
Del
OK
locales.py
2.06 KB
Del
OK
mac_utils.py
14.01 KB
Del
OK
mako.py
3.97 KB
Del
OK
master.py
29.7 KB
Del
OK
mattermost.py
1.77 KB
Del
OK
memcached.py
3.56 KB
Del
OK
migrations.py
1.46 KB
Del
OK
mine.py
3.68 KB
Del
OK
minion.py
4.13 KB
Del
OK
minions.py
43.38 KB
Del
OK
mount.py
1.15 KB
Del
OK
msazure.py
5.36 KB
Del
OK
msgpack.py
4.69 KB
Del
OK
nacl.py
13.65 KB
Del
OK
namecheap.py
4.32 KB
Del
OK
napalm.py
23.22 KB
Del
OK
nb_popen.py
7.24 KB
Del
OK
network.py
73.86 KB
Del
OK
nxos.py
12.94 KB
Del
OK
nxos_api.py
4 KB
Del
OK
odict.py
13.21 KB
Del
OK
openstack
-
Del
OK
oset.py
6.41 KB
Del
OK
pagerduty.py
3.03 KB
Del
OK
parsers.py
122.69 KB
Del
OK
path.py
11.24 KB
Del
OK
pbm.py
9.81 KB
Del
OK
pkg
-
Del
OK
platform.py
5.84 KB
Del
OK
powershell.py
4.15 KB
Del
OK
preseed.py
2.64 KB
Del
OK
process.py
40.76 KB
Del
OK
profile.py
3.21 KB
Del
OK
proxy.py
331 B
Del
OK
psutil_compat.py
3.63 KB
Del
OK
pushover.py
4.51 KB
Del
OK
pycrypto.py
5.41 KB
Del
OK
pydsl.py
13.74 KB
Del
OK
pyobjects.py
10.75 KB
Del
OK
reactor.py
18.99 KB
Del
OK
reclass.py
752 B
Del
OK
roster_matcher.py
3.55 KB
Del
OK
rsax931.py
8.42 KB
Del
OK
s3.py
8.78 KB
Del
OK
saltclass.py
14.27 KB
Del
OK
sanitizers.py
2.51 KB
Del
OK
schedule.py
71.81 KB
Del
OK
schema.py
54.26 KB
Del
OK
sdb.py
4.04 KB
Del
OK
slack.py
3.58 KB
Del
OK
smb.py
11.16 KB
Del
OK
smtp.py
3.27 KB
Del
OK
ssdp.py
14.75 KB
Del
OK
ssh.py
769 B
Del
OK
state.py
8.43 KB
Del
OK
stringio.py
355 B
Del
OK
stringutils.py
16.95 KB
Del
OK
systemd.py
5.51 KB
Del
OK
templates.py
24.03 KB
Del
OK
textformat.py
5.03 KB
Del
OK
thin.py
31.91 KB
Del
OK
timed_subprocess.py
4.06 KB
Del
OK
timeout.py
1.53 KB
Del
OK
timeutil.py
2.4 KB
Del
OK
url.py
5 KB
Del
OK
user.py
11.86 KB
Del
OK
validate
-
Del
OK
value.py
247 B
Del
OK
vault.py
21.74 KB
Del
OK
verify.py
25.34 KB
Del
OK
versions.py
17.17 KB
Del
OK
virt.py
3.24 KB
Del
OK
virtualbox.py
22.43 KB
Del
OK
vmware.py
129.74 KB
Del
OK
vsan.py
17.18 KB
Del
OK
vt.py
31.47 KB
Del
OK
vt_helper.py
4.4 KB
Del
OK
win_chcp.py
3.7 KB
Del
OK
win_dacl.py
95.49 KB
Del
OK
win_dotnet.py
4.74 KB
Del
OK
win_functions.py
12.69 KB
Del
OK
win_lgpo_auditpol.py
8.48 KB
Del
OK
win_lgpo_netsh.py
17.87 KB
Del
OK
win_lgpo_reg.py
16.98 KB
Del
OK
win_network.py
16.35 KB
Del
OK
win_osinfo.py
2.83 KB
Del
OK
win_pdh.py
13.85 KB
Del
OK
win_reg.py
30.82 KB
Del
OK
win_runas.py
10.53 KB
Del
OK
win_service.py
5.2 KB
Del
OK
win_system.py
14.47 KB
Del
OK
win_update.py
40.36 KB
Del
OK
winapi.py
818 B
Del
OK
x509.py
73.19 KB
Del
OK
xdg.py
316 B
Del
OK
xmlutil.py
13.91 KB
Del
OK
yaml.py
349 B
Del
OK
yamldumper.py
3.37 KB
Del
OK
yamlencoding.py
1.55 KB
Del
OK
yamllint.py
1.61 KB
Del
OK
yamlloader.py
6.04 KB
Del
OK
yamlloader_old.py
8.15 KB
Del
OK
yast.py
619 B
Del
OK
zeromq.py
1.74 KB
Del
OK
zfs.py
19.15 KB
Del
OK
Edit: aws.py
""" Connection library for AWS .. versionadded:: 2015.5.0 This is a base library used by a number of AWS services. :depends: requests """ import copy import hashlib import hmac import logging import random import re import time from datetime import datetime import salt.config import salt.utils.hashutils import salt.utils.xmlutil as xml try: import requests HAS_REQUESTS = True # pylint: disable=W0612 except ImportError: HAS_REQUESTS = False # pylint: disable=W0612 try: import binascii HAS_BINASCII = True # pylint: disable=W0612 except ImportError: HAS_BINASCII = False # pylint: disable=W0612 try: import urllib.parse HAS_URLLIB = True # pylint: disable=W0612 except ImportError: HAS_URLLIB = False # pylint: disable=W0612 try: import xml.etree.ElementTree as ET HAS_ETREE = True # pylint: disable=W0612 except ImportError: HAS_ETREE = False # pylint: disable=W0612 # pylint: enable=import-error,redefined-builtin,no-name-in-module log = logging.getLogger(__name__) DEFAULT_LOCATION = "us-east-1" DEFAULT_AWS_API_VERSION = "2016-11-15" AWS_RETRY_CODES = [ "RequestLimitExceeded", "InsufficientInstanceCapacity", "InternalError", "Unavailable", "InsufficientAddressCapacity", "InsufficientReservedInstanceCapacity", ] AWS_METADATA_TIMEOUT = 3.05 AWS_MAX_RETRIES = 7 IROLE_CODE = "use-instance-role-credentials" __AccessKeyId__ = "" __SecretAccessKey__ = "" __Token__ = "" __Expiration__ = "" __Location__ = "" __AssumeCache__ = {} __IMDS_Token__ = None def sleep_exponential_backoff(attempts): """ backoff an exponential amount of time to throttle requests during "API Rate Exceeded" failures as suggested by the AWS documentation here: https://docs.aws.amazon.com/AWSEC2/latest/APIReference/query-api-troubleshooting.html and also here: https://docs.aws.amazon.com/general/latest/gr/api-retries.html Failure to implement this approach results in a failure rate of >30% when using salt-cloud with "--parallel" when creating 50 or more instances with a fixed delay of 2 seconds. A failure rate of >10% is observed when using the salt-api with an asynchronous client specified (runner_async). """ time.sleep(random.uniform(1, 2**attempts)) def get_metadata(path, refresh_token_if_needed=True): """ Get the instance metadata at the provided path The path argument will be prepended by http://169.254.169.254/latest/ If using IMDSv2 with tokens required, the token will be fetched and used for subsequent requests (unless refresh_token_if_needed is False, in which case this will fail if tokens are required and no token was already cached) """ global __IMDS_Token__ headers = {} if __IMDS_Token__ is not None: headers["X-aws-ec2-metadata-token"] = __IMDS_Token__ # Connections to instance meta-data must fail fast and never be proxied result = requests.get( "http://169.254.169.254/latest/{}".format(path), proxies={"http": ""}, headers=headers, timeout=AWS_METADATA_TIMEOUT, ) if result.status_code == 401 and refresh_token_if_needed: # Probably using IMDSv2 with tokens required, so fetch token and retry token_result = requests.put( "http://169.254.169.254/latest/api/token", headers={"X-aws-ec2-metadata-token-ttl-seconds": "21600"}, proxies={"http": ""}, timeout=AWS_METADATA_TIMEOUT, ) __IMDS_Token__ = token_result.text if token_result.ok: return get_metadata(path, False) result.raise_for_status() return result def creds(provider): """ Return the credentials for AWS signing. This could be just the id and key specified in the provider configuration, or if the id or key is set to the literal string 'use-instance-role-credentials' creds will pull the instance role credentials from the meta data, cache them, and provide them instead. """ # Declare globals global __AccessKeyId__, __SecretAccessKey__, __Token__, __Expiration__ ret_credentials = () # if id or key is 'use-instance-role-credentials', pull them from meta-data ## if needed if provider["id"] == IROLE_CODE or provider["key"] == IROLE_CODE: # Check to see if we have cache credentials that are still good if not __Expiration__ or __Expiration__ < datetime.utcnow().strftime( "%Y-%m-%dT%H:%M:%SZ" ): # We don't have any cached credentials, or they are expired, get them try: result = get_metadata("meta-data/iam/security-credentials/") role = result.text except (requests.exceptions.HTTPError, requests.exceptions.ConnectionError): return provider["id"], provider["key"], "" try: result = get_metadata( "meta-data/iam/security-credentials/{}".format(role) ) except (requests.exceptions.HTTPError, requests.exceptions.ConnectionError): return provider["id"], provider["key"], "" data = result.json() __AccessKeyId__ = data["AccessKeyId"] __SecretAccessKey__ = data["SecretAccessKey"] __Token__ = data["Token"] __Expiration__ = data["Expiration"] ret_credentials = __AccessKeyId__, __SecretAccessKey__, __Token__ else: ret_credentials = provider["id"], provider["key"], "" if provider.get("role_arn") is not None: provider_shadow = provider.copy() provider_shadow.pop("role_arn", None) log.info("Assuming the role: %s", provider.get("role_arn")) ret_credentials = assumed_creds( provider_shadow, role_arn=provider.get("role_arn"), location="us-east-1" ) return ret_credentials def sig2(method, endpoint, params, provider, aws_api_version): """ Sign a query against AWS services using Signature Version 2 Signing Process. This is documented at: http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html """ timenow = datetime.utcnow() timestamp = timenow.strftime("%Y-%m-%dT%H:%M:%SZ") # Retrieve access credentials from meta-data, or use provided access_key_id, secret_access_key, token = creds(provider) params_with_headers = params.copy() params_with_headers["AWSAccessKeyId"] = access_key_id params_with_headers["SignatureVersion"] = "2" params_with_headers["SignatureMethod"] = "HmacSHA256" params_with_headers["Timestamp"] = "{}".format(timestamp) params_with_headers["Version"] = aws_api_version keys = sorted(params_with_headers.keys()) values = list(list(map(params_with_headers.get, keys))) querystring = urllib.parse.urlencode(list(zip(keys, values))) canonical = "{}\n{}\n/\n{}".format( method.encode("utf-8"), endpoint.encode("utf-8"), querystring.encode("utf-8"), ) hashed = hmac.new(secret_access_key, canonical, hashlib.sha256) sig = binascii.b2a_base64(hashed.digest()) params_with_headers["Signature"] = sig.strip() # Add in security token if we have one if token != "": params_with_headers["SecurityToken"] = token return params_with_headers def assumed_creds(prov_dict, role_arn, location=None): valid_session_name_re = re.compile("[^a-z0-9A-Z+=,.@-]") # current time in epoch seconds now = time.mktime(datetime.utcnow().timetuple()) for key, creds in copy.deepcopy(__AssumeCache__).items(): if (creds["Expiration"] - now) <= 120: del __AssumeCache__[key] if role_arn in __AssumeCache__: c = __AssumeCache__[role_arn] return c["AccessKeyId"], c["SecretAccessKey"], c["SessionToken"] version = "2011-06-15" session_name = valid_session_name_re.sub( "", salt.config.get_id({"root_dir": None})[0] )[0:63] headers, requesturl = sig4( "GET", "sts.amazonaws.com", params={ "Version": version, "Action": "AssumeRole", "RoleSessionName": session_name, "RoleArn": role_arn, "Policy": ( '{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1",' ' "Effect":"Allow","Action":"*","Resource":"*"}]}' ), "DurationSeconds": "3600", }, aws_api_version=version, data="", uri="/", prov_dict=prov_dict, product="sts", location=location, requesturl="https://sts.amazonaws.com/", ) headers["Accept"] = "application/json" result = requests.request("GET", requesturl, headers=headers, data="", verify=True) if result.status_code >= 400: log.info("AssumeRole response: %s", result.content) result.raise_for_status() resp = result.json() data = resp["AssumeRoleResponse"]["AssumeRoleResult"]["Credentials"] __AssumeCache__[role_arn] = data return data["AccessKeyId"], data["SecretAccessKey"], data["SessionToken"] def sig4( method, endpoint, params, prov_dict, aws_api_version=DEFAULT_AWS_API_VERSION, location=None, product="ec2", uri="/", requesturl=None, data="", headers=None, role_arn=None, payload_hash=None, ): """ Sign a query against AWS services using Signature Version 4 Signing Process. This is documented at: http://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html http://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html """ timenow = datetime.utcnow() # Retrieve access credentials from meta-data, or use provided if role_arn is None: access_key_id, secret_access_key, token = creds(prov_dict) else: access_key_id, secret_access_key, token = assumed_creds( prov_dict, role_arn, location=location ) if location is None: location = get_region_from_metadata() if location is None: location = DEFAULT_LOCATION params_with_headers = params.copy() if product not in ("s3", "ssm"): params_with_headers["Version"] = aws_api_version keys = sorted(params_with_headers.keys()) values = list(map(params_with_headers.get, keys)) querystring = urllib.parse.urlencode(list(zip(keys, values))).replace("+", "%20") amzdate = timenow.strftime("%Y%m%dT%H%M%SZ") datestamp = timenow.strftime("%Y%m%d") new_headers = {} if isinstance(headers, dict): new_headers = headers.copy() # Create payload hash (hash of the request body content). For GET # requests, the payload is an empty string (''). if not payload_hash: payload_hash = salt.utils.hashutils.sha256_digest(data) new_headers["X-Amz-date"] = amzdate new_headers["host"] = endpoint new_headers["x-amz-content-sha256"] = payload_hash a_canonical_headers = [] a_signed_headers = [] if token != "": new_headers["X-Amz-security-token"] = token for header in sorted(new_headers.keys(), key=str.lower): lower_header = header.lower() a_canonical_headers.append( "{}:{}".format(lower_header, new_headers[header].strip()) ) a_signed_headers.append(lower_header) canonical_headers = "\n".join(a_canonical_headers) + "\n" signed_headers = ";".join(a_signed_headers) algorithm = "AWS4-HMAC-SHA256" # Combine elements to create create canonical request canonical_request = "\n".join( (method, uri, querystring, canonical_headers, signed_headers, payload_hash) ) # Create the string to sign credential_scope = "/".join((datestamp, location, product, "aws4_request")) string_to_sign = "\n".join( ( algorithm, amzdate, credential_scope, salt.utils.hashutils.sha256_digest(canonical_request), ) ) # Create the signing key using the function defined above. signing_key = _sig_key(secret_access_key, datestamp, location, product) # Sign the string_to_sign using the signing_key signature = hmac.new( signing_key, string_to_sign.encode("utf-8"), hashlib.sha256 ).hexdigest() # Add signing information to the request authorization_header = "{} Credential={}/{}, SignedHeaders={}, Signature={}".format( algorithm, access_key_id, credential_scope, signed_headers, signature, ) new_headers["Authorization"] = authorization_header requesturl = "{}?{}".format(requesturl, querystring) return new_headers, requesturl def _sign(key, msg): """ Key derivation functions. See: http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-python """ return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest() def _sig_key(key, date_stamp, regionName, serviceName): """ Get a signature key. See: http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-python """ kDate = _sign(("AWS4" + key).encode("utf-8"), date_stamp) if regionName: kRegion = _sign(kDate, regionName) kService = _sign(kRegion, serviceName) else: kService = _sign(kDate, serviceName) kSigning = _sign(kService, "aws4_request") return kSigning def query( params=None, setname=None, requesturl=None, location=None, return_url=False, return_root=False, opts=None, provider=None, endpoint=None, product="ec2", sigver="2", ): """ Perform a query against AWS services using Signature Version 2 Signing Process. This is documented at: http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html Regions and endpoints are documented at: http://docs.aws.amazon.com/general/latest/gr/rande.html Default ``product`` is ``ec2``. Valid ``product`` names are: .. code-block:: yaml - autoscaling (Auto Scaling) - cloudformation (CloudFormation) - ec2 (Elastic Compute Cloud) - elasticache (ElastiCache) - elasticbeanstalk (Elastic BeanStalk) - elasticloadbalancing (Elastic Load Balancing) - elasticmapreduce (Elastic MapReduce) - iam (Identity and Access Management) - importexport (Import/Export) - monitoring (CloudWatch) - rds (Relational Database Service) - simpledb (SimpleDB) - sns (Simple Notification Service) - sqs (Simple Queue Service) """ if params is None: params = {} if opts is None: opts = {} function = opts.get("function", (None, product)) providers = opts.get("providers", {}) if provider is None: prov_dict = providers.get(function[1], {}).get(product, {}) if prov_dict: driver = list(list(prov_dict.keys()))[0] provider = providers.get(driver, product) else: prov_dict = providers.get(provider, {}).get(product, {}) service_url = prov_dict.get("service_url", "amazonaws.com") if not location: location = get_location(opts, prov_dict) if endpoint is None: if not requesturl: endpoint = prov_dict.get( "endpoint", "{}.{}.{}".format(product, location, service_url) ) requesturl = "https://{}/".format(endpoint) else: endpoint = urllib.parse.urlparse(requesturl).netloc if endpoint == "": endpoint_err = ( "Could not find a valid endpoint in the " "requesturl: {}. Looking for something " "like https://some.aws.endpoint/?args".format(requesturl) ) log.error(endpoint_err) if return_url is True: return {"error": endpoint_err}, requesturl return {"error": endpoint_err} log.debug("Using AWS endpoint: %s", endpoint) method = "GET" aws_api_version = prov_dict.get( "aws_api_version", prov_dict.get("{}_api_version".format(product), DEFAULT_AWS_API_VERSION), ) # Fallback to ec2's id & key if none is found, for this component if not prov_dict.get("id", None): prov_dict["id"] = providers.get(provider, {}).get("ec2", {}).get("id", {}) prov_dict["key"] = providers.get(provider, {}).get("ec2", {}).get("key", {}) if sigver == "4": headers, requesturl = sig4( method, endpoint, params, prov_dict, aws_api_version, location, product, requesturl=requesturl, ) params_with_headers = {} else: params_with_headers = sig2(method, endpoint, params, prov_dict, aws_api_version) headers = {} attempts = 0 while attempts < AWS_MAX_RETRIES: log.debug("AWS Request: %s", requesturl) log.trace("AWS Request Parameters: %s", params_with_headers) try: result = requests.get( requesturl, headers=headers, params=params_with_headers ) log.debug("AWS Response Status Code: %s", result.status_code) log.trace("AWS Response Text: %s", result.text) result.raise_for_status() break except requests.exceptions.HTTPError as exc: root = ET.fromstring(exc.response.content) data = xml.to_dict(root) # check to see if we should retry the query err_code = data.get("Errors", {}).get("Error", {}).get("Code", "") if attempts < AWS_MAX_RETRIES and err_code and err_code in AWS_RETRY_CODES: attempts += 1 log.error( "AWS Response Status Code and Error: [%s %s] %s; " "Attempts remaining: %s", exc.response.status_code, exc, data, attempts, ) sleep_exponential_backoff(attempts) continue log.error( "AWS Response Status Code and Error: [%s %s] %s", exc.response.status_code, exc, data, ) if return_url is True: return {"error": data}, requesturl return {"error": data} else: log.error( "AWS Response Status Code and Error: [%s %s] %s", exc.response.status_code, exc, data, ) if return_url is True: return {"error": data}, requesturl return {"error": data} root = ET.fromstring(result.text) items = root[1] if return_root is True: items = root if setname: for idx, item in enumerate(root): comps = item.tag.split("}") if comps[1] == setname: items = root[idx] ret = [] for item in items: ret.append(xml.to_dict(item)) if return_url is True: return ret, requesturl return ret def get_region_from_metadata(): """ Try to get region from instance identity document and cache it .. versionadded:: 2015.5.6 """ global __Location__ if __Location__ == "do-not-get-from-metadata": log.debug( "Previously failed to get AWS region from metadata. Not trying again." ) return None # Cached region if __Location__ != "": return __Location__ try: result = get_metadata("dynamic/instance-identity/document") except requests.exceptions.RequestException: log.warning("Failed to get AWS region from instance metadata.", exc_info=True) # Do not try again __Location__ = "do-not-get-from-metadata" return None try: region = result.json()["region"] __Location__ = region return __Location__ except (ValueError, KeyError): log.warning("Failed to decode JSON from instance metadata.") return None return None def get_location(opts=None, provider=None): """ Return the region to use, in this order: opts['location'] provider['location'] get_region_from_metadata() DEFAULT_LOCATION """ if opts is None: opts = {} ret = opts.get("location") if ret is None and provider is not None: ret = provider.get("location") if ret is None: ret = get_region_from_metadata() if ret is None: ret = DEFAULT_LOCATION return ret
Save