changeset 282:da1596034954

Implemented an "AWS" namespace to retrieve some AWS-specific metadata
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 07 Dec 2020 01:06:33 +0100
parents 2ea0aa9fb402
children 503768f91a05
files configmix/extras/__init__.py configmix/extras/aws.py configmix/variables.py docs/introduction.rst setup.py
diffstat 5 files changed, 151 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- /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__ = []
--- /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
--- 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)
--- 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
 ~~~~~~~~
--- 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,