Blame cloudinit/ec2_utils.py

Packit bc9a3a
# Copyright (C) 2012 Yahoo! Inc.
Packit bc9a3a
# Copyright (C) 2014 Amazon.com, Inc. or its affiliates.
Packit bc9a3a
#
Packit bc9a3a
# Author: Joshua Harlow <harlowja@yahoo-inc.com>
Packit bc9a3a
# Author: Andrew Jorgensen <ajorgens@amazon.com>
Packit bc9a3a
#
Packit bc9a3a
# This file is part of cloud-init. See LICENSE file for license information.
Packit bc9a3a
Packit bc9a3a
import functools
Packit bc9a3a
import json
Packit bc9a3a
Packit bc9a3a
from cloudinit import log as logging
Packit bc9a3a
from cloudinit import url_helper
Packit bc9a3a
from cloudinit import util
Packit bc9a3a
Packit bc9a3a
LOG = logging.getLogger(__name__)
Packit bc9a3a
SKIP_USERDATA_CODES = frozenset([url_helper.NOT_FOUND])
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
class MetadataLeafDecoder(object):
Packit bc9a3a
    """Decodes a leaf blob into something meaningful."""
Packit bc9a3a
Packit bc9a3a
    def _maybe_json_object(self, text):
Packit bc9a3a
        if not text:
Packit bc9a3a
            return False
Packit bc9a3a
        text = text.strip()
Packit bc9a3a
        if text.startswith("{") and text.endswith("}"):
Packit bc9a3a
            return True
Packit bc9a3a
        return False
Packit bc9a3a
Packit bc9a3a
    def __call__(self, field, blob):
Packit bc9a3a
        if not blob:
Packit bc9a3a
            return ''
Packit bc9a3a
        try:
Packit bc9a3a
            blob = util.decode_binary(blob)
Packit bc9a3a
        except UnicodeDecodeError:
Packit bc9a3a
            return blob
Packit bc9a3a
        if self._maybe_json_object(blob):
Packit bc9a3a
            try:
Packit bc9a3a
                # Assume it's json, unless it fails parsing...
Packit bc9a3a
                return json.loads(blob)
Packit bc9a3a
            except (ValueError, TypeError) as e:
Packit bc9a3a
                LOG.warning("Field %s looked like a json object, but it"
Packit bc9a3a
                            " was not: %s", field, e)
Packit bc9a3a
        if blob.find("\n") != -1:
Packit bc9a3a
            return blob.splitlines()
Packit bc9a3a
        return blob
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
# See: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/
Packit bc9a3a
#         ec2-instance-metadata.html
Packit bc9a3a
class MetadataMaterializer(object):
Packit bc9a3a
    def __init__(self, blob, base_url, caller, leaf_decoder=None):
Packit bc9a3a
        self._blob = blob
Packit bc9a3a
        self._md = None
Packit bc9a3a
        self._base_url = base_url
Packit bc9a3a
        self._caller = caller
Packit bc9a3a
        if leaf_decoder is None:
Packit bc9a3a
            self._leaf_decoder = MetadataLeafDecoder()
Packit bc9a3a
        else:
Packit bc9a3a
            self._leaf_decoder = leaf_decoder
Packit bc9a3a
Packit bc9a3a
    def _parse(self, blob):
Packit bc9a3a
        leaves = {}
Packit bc9a3a
        children = []
Packit bc9a3a
        blob = util.decode_binary(blob)
Packit bc9a3a
Packit bc9a3a
        if not blob:
Packit bc9a3a
            return (leaves, children)
Packit bc9a3a
Packit bc9a3a
        def has_children(item):
Packit bc9a3a
            if item.endswith("/"):
Packit bc9a3a
                return True
Packit bc9a3a
            else:
Packit bc9a3a
                return False
Packit bc9a3a
Packit bc9a3a
        def get_name(item):
Packit bc9a3a
            if item.endswith("/"):
Packit bc9a3a
                return item.rstrip("/")
Packit bc9a3a
            return item
Packit bc9a3a
Packit bc9a3a
        for field in blob.splitlines():
Packit bc9a3a
            field = field.strip()
Packit bc9a3a
            field_name = get_name(field)
Packit bc9a3a
            if not field or not field_name:
Packit bc9a3a
                continue
Packit bc9a3a
            # Don't materialize credentials
Packit bc9a3a
            if field_name == 'security-credentials':
Packit bc9a3a
                continue
Packit bc9a3a
            if has_children(field):
Packit bc9a3a
                if field_name not in children:
Packit bc9a3a
                    children.append(field_name)
Packit bc9a3a
            else:
Packit bc9a3a
                contents = field.split("=", 1)
Packit bc9a3a
                resource = field_name
Packit bc9a3a
                if len(contents) > 1:
Packit bc9a3a
                    # What a PITA...
Packit bc9a3a
                    (ident, sub_contents) = contents
Packit bc9a3a
                    ident = util.safe_int(ident)
Packit bc9a3a
                    if ident is not None:
Packit bc9a3a
                        resource = "%s/openssh-key" % (ident)
Packit bc9a3a
                        field_name = sub_contents
Packit bc9a3a
                leaves[field_name] = resource
Packit bc9a3a
        return (leaves, children)
Packit bc9a3a
Packit bc9a3a
    def materialize(self):
Packit bc9a3a
        if self._md is not None:
Packit bc9a3a
            return self._md
Packit bc9a3a
        self._md = self._materialize(self._blob, self._base_url)
Packit bc9a3a
        return self._md
Packit bc9a3a
Packit bc9a3a
    def _materialize(self, blob, base_url):
Packit bc9a3a
        (leaves, children) = self._parse(blob)
Packit bc9a3a
        child_contents = {}
Packit bc9a3a
        for c in children:
Packit bc9a3a
            child_url = url_helper.combine_url(base_url, c)
Packit bc9a3a
            if not child_url.endswith("/"):
Packit bc9a3a
                child_url += "/"
Packit bc9a3a
            child_blob = self._caller(child_url)
Packit bc9a3a
            child_contents[c] = self._materialize(child_blob, child_url)
Packit bc9a3a
        leaf_contents = {}
Packit bc9a3a
        for (field, resource) in leaves.items():
Packit bc9a3a
            leaf_url = url_helper.combine_url(base_url, resource)
Packit bc9a3a
            leaf_blob = self._caller(leaf_url)
Packit bc9a3a
            leaf_contents[field] = self._leaf_decoder(field, leaf_blob)
Packit bc9a3a
        joined = {}
Packit bc9a3a
        joined.update(child_contents)
Packit bc9a3a
        for field in leaf_contents.keys():
Packit bc9a3a
            if field in joined:
Packit bc9a3a
                LOG.warning("Duplicate key found in results from %s",
Packit bc9a3a
                            base_url)
Packit bc9a3a
            else:
Packit bc9a3a
                joined[field] = leaf_contents[field]
Packit bc9a3a
        return joined
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
def skip_retry_on_codes(status_codes, _request_args, cause):
Packit bc9a3a
    """Returns False if cause.code is in status_codes."""
Packit bc9a3a
    return cause.code not in status_codes
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
def get_instance_userdata(api_version='latest',
Packit bc9a3a
                          metadata_address='http://169.254.169.254',
Packit bc9a3a
                          ssl_details=None, timeout=5, retries=5,
Packit Service b501d3
                          headers_cb=None, exception_cb=None):
Packit bc9a3a
    ud_url = url_helper.combine_url(metadata_address, api_version)
Packit bc9a3a
    ud_url = url_helper.combine_url(ud_url, 'user-data')
Packit bc9a3a
    user_data = ''
Packit bc9a3a
    try:
Packit bc9a3a
        if not exception_cb:
Packit bc9a3a
            # It is ok for userdata to not exist (thats why we are stopping if
Packit bc9a3a
            # NOT_FOUND occurs) and just in that case returning an empty
Packit bc9a3a
            # string.
Packit bc9a3a
            exception_cb = functools.partial(skip_retry_on_codes,
Packit bc9a3a
                                             SKIP_USERDATA_CODES)
Packit bc9a3a
        response = url_helper.read_file_or_url(
Packit bc9a3a
            ud_url, ssl_details=ssl_details, timeout=timeout,
Packit Service b501d3
            retries=retries, exception_cb=exception_cb, headers_cb=headers_cb)
Packit bc9a3a
        user_data = response.contents
Packit bc9a3a
    except url_helper.UrlError as e:
Packit bc9a3a
        if e.code not in SKIP_USERDATA_CODES:
Packit bc9a3a
            util.logexc(LOG, "Failed fetching userdata from url %s", ud_url)
Packit bc9a3a
    except Exception:
Packit bc9a3a
        util.logexc(LOG, "Failed fetching userdata from url %s", ud_url)
Packit bc9a3a
    return user_data
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
def _get_instance_metadata(tree, api_version='latest',
Packit bc9a3a
                           metadata_address='http://169.254.169.254',
Packit bc9a3a
                           ssl_details=None, timeout=5, retries=5,
Packit bc9a3a
                           leaf_decoder=None, headers_cb=None,
Packit bc9a3a
                           exception_cb=None):
Packit bc9a3a
    md_url = url_helper.combine_url(metadata_address, api_version, tree)
Packit bc9a3a
    caller = functools.partial(
Packit bc9a3a
        url_helper.read_file_or_url, ssl_details=ssl_details,
Packit bc9a3a
        timeout=timeout, retries=retries, headers_cb=headers_cb,
Packit bc9a3a
        exception_cb=exception_cb)
Packit bc9a3a
Packit bc9a3a
    def mcaller(url):
Packit bc9a3a
        return caller(url).contents
Packit bc9a3a
Packit bc9a3a
    try:
Packit bc9a3a
        response = caller(md_url)
Packit bc9a3a
        materializer = MetadataMaterializer(response.contents,
Packit bc9a3a
                                            md_url, mcaller,
Packit bc9a3a
                                            leaf_decoder=leaf_decoder)
Packit bc9a3a
        md = materializer.materialize()
Packit bc9a3a
        if not isinstance(md, (dict)):
Packit bc9a3a
            md = {}
Packit bc9a3a
        return md
Packit bc9a3a
    except Exception:
Packit bc9a3a
        util.logexc(LOG, "Failed fetching %s from url %s", tree, md_url)
Packit bc9a3a
        return {}
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
def get_instance_metadata(api_version='latest',
Packit bc9a3a
                          metadata_address='http://169.254.169.254',
Packit bc9a3a
                          ssl_details=None, timeout=5, retries=5,
Packit bc9a3a
                          leaf_decoder=None, headers_cb=None,
Packit bc9a3a
                          exception_cb=None):
Packit bc9a3a
    # Note, 'meta-data' explicitly has trailing /.
Packit bc9a3a
    # this is required for CloudStack (LP: #1356855)
Packit bc9a3a
    return _get_instance_metadata(tree='meta-data/', api_version=api_version,
Packit bc9a3a
                                  metadata_address=metadata_address,
Packit bc9a3a
                                  ssl_details=ssl_details, timeout=timeout,
Packit bc9a3a
                                  retries=retries, leaf_decoder=leaf_decoder,
Packit bc9a3a
                                  headers_cb=headers_cb,
Packit bc9a3a
                                  exception_cb=exception_cb)
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
def get_instance_identity(api_version='latest',
Packit bc9a3a
                          metadata_address='http://169.254.169.254',
Packit bc9a3a
                          ssl_details=None, timeout=5, retries=5,
Packit bc9a3a
                          leaf_decoder=None, headers_cb=None,
Packit bc9a3a
                          exception_cb=None):
Packit bc9a3a
    return _get_instance_metadata(tree='dynamic/instance-identity',
Packit bc9a3a
                                  api_version=api_version,
Packit bc9a3a
                                  metadata_address=metadata_address,
Packit bc9a3a
                                  ssl_details=ssl_details, timeout=timeout,
Packit bc9a3a
                                  retries=retries, leaf_decoder=leaf_decoder,
Packit bc9a3a
                                  headers_cb=headers_cb,
Packit bc9a3a
                                  exception_cb=exception_cb)
Packit bc9a3a
# vi: ts=4 expandtab