# HG changeset patch # User Franz Glasner # Date 1607299593 -3600 # Node ID da159603495447cf54062a7393eb1b4819c1a40a # Parent 2ea0aa9fb4024b1eabe9f28c582a07c51e4947f5 Implemented an "AWS" namespace to retrieve some AWS-specific metadata diff -r 2ea0aa9fb402 -r da1596034954 configmix/extras/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/configmix/extras/__init__.py Mon Dec 07 01:06:33 2020 +0100 @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# :- +# :Copyright: (c) 2015-2020, Franz Glasner. All rights reserved. +# :License: 3-clause BSD. See LICENSE.txt for details. +# :- +"""Sub-package for some extras implementations. + +""" + +from __future__ import division, absolute_import, print_function + + +__all__ = [] diff -r 2ea0aa9fb402 -r da1596034954 configmix/extras/aws.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/configmix/extras/aws.py Mon Dec 07 01:06:33 2020 +0100 @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +# :- +# :Copyright: (c) 2015-2020, Franz Glasner. All rights reserved. +# :License: 3-clause BSD. See LICENSE.txt for details. +# :- +"""AWS namespace implementation + +""" + +from __future__ import division, absolute_import, print_function + + +__all__ = [] + + +import requests +import requests.exceptions + + +_MARKER = object() + + +URL_META_INSTANCEID = "http://169.254.169.254/latest/meta-data/instance-id" +URL_META_REGION = "http://169.254.169.254/latest/meta-data/placement/region" +URL_META_AVAILABILITY_ZONE = "http://169.254.169.254/latest/meta-data/availability-zone" +URL_DYN_INSTANCE_IDENTITY_DOC = "http://169.254.169.254/latest/dynamic/instance-identity/document" +TIMEOUT = 10 + +_meta_instanceid = None +_meta_region = None +_meta_availability_zone = None +_dyn_instance_identity_doc = None + + +def _get_text_req(url): + with requests.Session() as sess: + try: + resp = sess.get(url, timeout=TIMEOUT) + resp.raise_for_status() + return resp.text + except requests.exceptions.RequestException: + return _MARKER + + +def _get_json_req(url): + with requests.Session() as sess: + try: + resp = sess.get(url, timeout=TIMEOUT) + resp.raise_for_status() + return resp.json() + except requests.exceptions.RequestException: + return _MARKER + except ValueError: + # JSON error + return _MARKER + + +def _get_meta_instanceid(): + global _meta_instanceid + if _meta_instanceid is None: + _meta_instanceid = _get_text_req(URL_META_INSTANCEID) + return _meta_instanceid + + +def _get_meta_region(): + global _meta_region + if _meta_region is None: + _meta_region = _get_text_req(URL_META_REGION) + return _meta_region + + +def _get_meta_avzone(): + global _meta_availability_zone + if _meta_availability_zone is None: + _meta_availability_zone = _get_text_req(URL_META_AVAILABILITY_ZONE) + return _meta_availability_zone + + +def _get_dyn_instance_identity_doc(): + global _dyn_instance_identity_doc + if _dyn_instance_identity_doc is None: + _dyn_instance_identity_doc = _get_json_req( + URL_DYN_INSTANCE_IDENTITY_DOC) + return _dyn_instance_identity_doc + + +def _awslookup(name, default=_MARKER): + if name == "metadata.instance-id": + v = _get_meta_instanceid() + elif name == "metadata.placement.region": + v = _get_meta_region() + elif name == "metadata.placement.availability-zone": + v = _get_meta_avzone() + elif name.startswith("dynamic.instance-identity."): + idoc = _get_dyn_instance_identity_doc() + if idoc is _MARKER: + v = _MARKER + else: + v = idoc.get(name[26:], _MARKER) + else: + v = _MARKER + if v is _MARKER: + if default is _MARKER: + raise KeyError("key %r not found in the AWS namespace" % name) + else: + return default + return v diff -r 2ea0aa9fb402 -r da1596034954 configmix/variables.py --- a/configmix/variables.py Mon Oct 05 09:25:11 2020 +0200 +++ b/configmix/variables.py Mon Dec 07 01:06:33 2020 +0100 @@ -215,3 +215,9 @@ add_varns("ENV", _envlookup) add_varns("OS", _oslookup) add_varns("PY", _pylookup) +try: + from .extras import aws +except ImportError: + pass +else: + add_varns("AWS", aws._awslookup) diff -r 2ea0aa9fb402 -r da1596034954 docs/introduction.rst --- a/docs/introduction.rst Mon Oct 05 09:25:11 2020 +0200 +++ b/docs/introduction.rst Mon Dec 07 01:06:33 2020 +0100 @@ -293,6 +293,23 @@ ``implementation`` The return value of :py:func:`platform.python_implementation` +5. The namespace ``AWS`` + + Contains some metadata for AWS instances when running from within + AWS: + + ``metadata.instance-id`` + + ``metadata.placement.region`` + + ``metadata.placement.availability-zone`` + + ``dynamic.instance-identity.region`` + and all other properties of the instance-identity document + (e.g. ``instanceId``, ``instanceType``, ``imageId``, ``pendingTime``, + ``architecture``, ``availabilityZone``, ``privateIp``, ``version`` + et al.). + Examples ~~~~~~~~ diff -r 2ea0aa9fb402 -r da1596034954 setup.py --- a/setup.py Mon Oct 05 09:25:11 2020 +0200 +++ b/setup.py Mon Dec 07 01:06:33 2020 +0100 @@ -26,6 +26,10 @@ with open(os.path.join(pkg_root, "README.txt"), "rt") as rf: long_description = rf.read() +aws_requirements = [ + "requests", +] + yaml_requirements = [ "PyYAML>=3.0,<6", ] @@ -35,6 +39,7 @@ ] all_requirements = [] +all_requirements.extend(aws_requirements) all_requirements.extend(yaml_requirements) all_requirements.extend(toml_requirements) @@ -46,7 +51,8 @@ url="https://pypi.dom66.de/simple/configmix/", description="Library for extended configuration files", long_description=long_description, - packages=["configmix"], + packages=["configmix", + "configmix.extras"], include_package_data=False, zip_safe=True, platforms="any", @@ -64,6 +70,7 @@ ], python_requires=">=2.6", extras_require={ + "aws" : aws_requirements, "toml": toml_requirements, "yaml": yaml_requirements, "all" : all_requirements,