← Back to team overview

cloud-init-dev team mailing list archive

Re: [Merge] lp:~nshrader/cloud-init/digitalocean-datasource into lp:cloud-init

 


Diff comments:

> === added file 'cloudinit/sources/DataSourceDigitalOcean.py'
> --- cloudinit/sources/DataSourceDigitalOcean.py	1970-01-01 00:00:00 +0000
> +++ cloudinit/sources/DataSourceDigitalOcean.py	2014-10-17 22:04:47 +0000
> @@ -0,0 +1,105 @@
> +# vi: ts=4 expandtab
> +#
> +#    Author: Neal Shrader <neal@xxxxxxxxxxxxxxxx>
> +#
> +#    This program is free software: you can redistribute it and/or modify
> +#    it under the terms of the GNU General Public License version 3, as
> +#    published by the Free Software Foundation.
> +#
> +#    This program is distributed in the hope that it will be useful,
> +#    but WITHOUT ANY WARRANTY; without even the implied warranty of
> +#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +#    GNU General Public License for more details.
> +#
> +#    You should have received a copy of the GNU General Public License
> +#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +from cloudinit import log as logging
> +from cloudinit import util
> +from cloudinit import sources
> +from cloudinit import url_helper
> +from cloudinit import ec2_utils
> +from types import *

Can u explicitly import what u need, this avoids namespace pollution. Thanks :)

> +import functools
> +
> +
> +LOG = logging.getLogger(__name__)
> +
> +BUILTIN_DS_CONFIG = {
> +    'metadata_url': 'http://169.254.169.254/metadata/v1/',
> +    'mirrors_url': 'http://mirrors.digitalocean.com/'
> +}
> +MD_RETRIES = 0
> +MD_TIMEOUT = 1
> +
> +class DataSourceDigitalOcean(sources.DataSource):
> +    def __init__(self, sys_cfg, distro, paths):
> +        sources.DataSource.__init__(self, sys_cfg, distro, paths)
> +        self.metadata = dict()
> +        self.ds_cfg = util.mergemanydict([
> +            util.get_cfg_by_path(sys_cfg, ["datasource", "DigitalOcean"], {}),
> +            BUILTIN_DS_CONFIG])
> +        self.metadata_address = self.ds_cfg['metadata_url']
> +
> +        if self.ds_cfg.get('retries'):
> +            self.retries = self.ds_cfg['retries']
> +        else:
> +            self.retries = MD_RETRIES
> +
> +        if self.ds_cfg.get('timeout'):
> +            self.timeout = self.ds_cfg['timeout']
> +        else:
> +            self.timeout = MD_TIMEOUT
> +
> +    def get_data(self):
> +        caller = functools.partial(util.read_file_or_url, timeout=self.timeout, 
> +                                   retries=self.retries)
> +        md = ec2_utils.MetadataMaterializer(str(caller(self.metadata_address)),
> +                                            base_url=self.metadata_address, 
> +                                            caller=caller)
> +
> +        self.metadata = md.materialize()
> +
> +        if self.metadata.get('id'):
> +            return True
> +        else:
> +            return False
> +
> +    def get_userdata_raw(self):
> +        return "\n".join(self.metadata['user-data'])
> +
> +    def get_vendordata_raw(self):
> +        return "\n".join(self.metadata['vendor-data'])
> +
> +    def get_public_ssh_keys(self):
> +        if type(self.metadata['public-keys']) is StringType:
> +           return [self.metadata['public-keys']]
> +        else:
> +           return self.metadata['public-keys']
> +
> +    @property
> +    def availability_zone(self):
> +        return self.metadata['region']
> +
> +    def get_instance_id(self):
> +        return self.metadata['id']
> +
> +    def get_hostname(self, fqdn=False):
> +        return self.metadata['hostname']
> +
> +    def get_package_mirror_info(self):
> +        return self.ds_cfg['mirrors_url']
> +
> +    @property
> +    def launch_index(self):
> +        return None
> +
> +# Used to match classes to dependencies
> +datasources = [
> +  (DataSourceDigitalOcean, (sources.DEP_FILESYSTEM, sources.DEP_NETWORK)),
> +  ]
> +
> +
> +# Return a list of data sources that match this set of dependencies
> +def get_datasource_list(depends):
> +    return sources.list_from_depends(depends, datasources)
> 
> === added directory 'doc/sources/digitalocean'
> === added file 'doc/sources/digitalocean/README.rst'
> --- doc/sources/digitalocean/README.rst	1970-01-01 00:00:00 +0000
> +++ doc/sources/digitalocean/README.rst	2014-10-17 22:04:47 +0000
> @@ -0,0 +1,21 @@
> + The `DigitalOcean`_ datasource consumes the content served from DigitalOcean's `metadata service`_.  This
> +metadata service serves information about the running droplet via HTTP over the link local address
> +169.254.169.254.  The metadata API endpoints are fully described at 
> +`https://developers.digitalocean.com/metadata/ <https://developers.digitalocean.com/metadata/>`_.
> +
> +Configuration
> +~~~~~~~~~~~~~
> +
> +DigitalOcean's datasource can be configured as follows:
> +
> +  datasource:
> +    DigitalOcean:
> +      retries: 3
> +      timeout: 2
> +
> +- *retries*: Determines the number of times to attempt to connect to the metadata service
> +- *timeout*: Determines the timeout in seconds to wait for a response from the metadata service
> +
> +.. _DigitalOcean: http://digitalocean.com/
> +.. _metadata service: https://developers.digitalocean.com/metadata/
> +.. _Full documentation: https://developers.digitalocean.com/metadata/
> 
> === added file 'tests/unittests/test_datasource/test_digitalocean.py'
> --- tests/unittests/test_datasource/test_digitalocean.py	1970-01-01 00:00:00 +0000
> +++ tests/unittests/test_datasource/test_digitalocean.py	2014-10-17 22:04:47 +0000
> @@ -0,0 +1,126 @@
> +#
> +#    Copyright (C) 2014 Neal Shrader
> +#
> +#    Author: Neal Shrader <neal@xxxxxxxxxxxxxxxx>
> +#
> +#    This program is free software: you can redistribute it and/or modify
> +#    it under the terms of the GNU General Public License version 3, as
> +#    published by the Free Software Foundation.
> +#
> +#    This program is distributed in the hope that it will be useful,
> +#    but WITHOUT ANY WARRANTY; without even the implied warranty of
> +#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +#    GNU General Public License for more details.
> +#
> +#    You should have received a copy of the GNU General Public License
> +#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +import httpretty
> +import re
> +
> +from types import *

Same here.

> +from urlparse import urlparse
> +
> +from cloudinit import settings
> +from cloudinit import helpers
> +from cloudinit.sources import DataSourceDigitalOcean
> +
> +from .. import helpers as test_helpers
> +
> +# Abbreviated for the test
> +DO_INDEX = """id
> +           hostname
> +           user-data
> +           vendor-data
> +           public-keys
> +           region"""
> +
> +DO_MULTIPLE_KEYS = """ssh-rsa AAAAB3NzaC1yc2EAAAA... neal@xxxxxxxxxxxxxxxx
> +                   ssh-rsa AAAAB3NzaC1yc2EAAAA... neal2@xxxxxxxxxxxxxxxx"""
> +DO_SINGLE_KEY = "ssh-rsa AAAAB3NzaC1yc2EAAAA... neal@xxxxxxxxxxxxxxxx"
> +
> +DO_META = {
> +    '': DO_INDEX,
> +    'user-data': '#!/bin/bash\necho "user-data"',
> +    'vendor-data': '#!/bin/bash\necho "vendor-data"',
> +    'public-keys': DO_SINGLE_KEY,
> +    'region': 'nyc3',
> +    'id': '2000000',
> +    'hostname': 'cloudinit-test',
> +}
> +
> +MD_URL_RE = re.compile(r'http://169.254.169.254/metadata/v1/.*')
> +
> +def _request_callback(method, uri, headers):
> +    url_path = urlparse(uri).path
> +    if url_path.startswith('/metadata/v1/'):
> +        path = url_path.split('/metadata/v1/')[1:][0]
> +    else:
> +        path = None
> +    if path in DO_META:
> +        return (200, headers, DO_META.get(path))
> +    else:
> +        return (404, headers, '')
> +
> +
> +class TestDataSourceDigitalOcean(test_helpers.HttprettyTestCase):
> +
> +    def setUp(self):
> +        self.ds = DataSourceDigitalOcean.DataSourceDigitalOcean(
> +            settings.CFG_BUILTIN, None,
> +            helpers.Paths({}))
> +        super(TestDataSourceDigitalOcean, self).setUp()
> +
> +    @httpretty.activate
> +    def test_connection(self):
> +        httpretty.register_uri(
> +            httpretty.GET, MD_URL_RE,
> +            body=_request_callback)
> +
> +        success = self.ds.get_data()
> +        self.assertTrue(success)
> +
> +    @httpretty.activate
> +    def test_metadata(self):
> +        httpretty.register_uri(
> +            httpretty.GET, MD_URL_RE,
> +            body=_request_callback)
> +        self.ds.get_data()
> +
> +        self.assertEqual(DO_META.get('user-data'),
> +                         self.ds.get_userdata_raw())
> +
> +        self.assertEqual(DO_META.get('vendor-data'),
> +                         self.ds.get_vendordata_raw())
> +
> +        self.assertEqual(DO_META.get('region'),
> +                         self.ds.availability_zone)
> +
> +        self.assertEqual(DO_META.get('id'),
> +                         self.ds.get_instance_id())
> +
> +        self.assertEqual(DO_META.get('hostname'),
> +                         self.ds.get_hostname())
> +
> +        self.assertEqual('http://mirrors.digitalocean.com/',
> +                         self.ds.get_package_mirror_info())
> +
> +        # Single key
> +        self.assertEqual([DO_META.get('public-keys')],
> +                         self.ds.get_public_ssh_keys())
> +
> +        self.assertIs(type(self.ds.get_public_ssh_keys()), ListType)
> +
> +    @httpretty.activate
> +    def test_multiple_ssh_keys(self):
> +        DO_META['public_keys'] = DO_MULTIPLE_KEYS
> +        httpretty.register_uri(
> +            httpretty.GET, MD_URL_RE,
> +            body=_request_callback)
> +        self.ds.get_data()
> +
> +        # Multiple keys
> +        self.assertEqual(DO_META.get('public-keys').splitlines(),
> +                         self.ds.get_public_ssh_keys())
> +
> +        self.assertIs(type(self.ds.get_public_ssh_keys()), ListType)
> 


-- 
https://code.launchpad.net/~nshrader/cloud-init/digitalocean-datasource/+merge/238590
Your team cloud init development team is requested to review the proposed merge of lp:~nshrader/cloud-init/digitalocean-datasource into lp:cloud-init.


References