diff --git a/Makefile b/Makefile index 9e8e5c0..71dfd40 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ #!/usr/bin/make lint: - flake8 --exclude hooks/charmhelpers hooks - charm proof + @flake8 --exclude hooks/charmhelpers hooks + @charm proof sync: - charm-helper-sync -c charm-helpers-sync.yaml + @charm-helper-sync -c charm-helpers-sync.yaml diff --git a/charm-helpers-sync.yaml b/charm-helpers-sync.yaml index 032b9b6..21c0bc6 100644 --- a/charm-helpers-sync.yaml +++ b/charm-helpers-sync.yaml @@ -1,4 +1,4 @@ -branch: lp:~james-page/charm-helpers/configure_source_redux +branch: lp:charm-helpers destination: hooks/charmhelpers include: - core diff --git a/hooks/charmhelpers/core/hookenv.py b/hooks/charmhelpers/core/hookenv.py index b28240c..b1ede91 100644 --- a/hooks/charmhelpers/core/hookenv.py +++ b/hooks/charmhelpers/core/hookenv.py @@ -333,3 +333,6 @@ class Hooks(object): self.register(decorated.__name__, decorated) return decorated return wrapper + +def charm_dir(): + return os.environ.get('CHARM_DIR') diff --git a/hooks/charmhelpers/fetch/__init__.py b/hooks/charmhelpers/fetch/__init__.py index 19d83b8..e3c4242 100644 --- a/hooks/charmhelpers/fetch/__init__.py +++ b/hooks/charmhelpers/fetch/__init__.py @@ -1,11 +1,21 @@ +import importlib from yaml import safe_load -from charmhelpers.core.hookenv import config from charmhelpers.core.host import ( - apt_install, apt_update, filter_installed_packages + apt_install, + apt_update, + filter_installed_packages +) +from urlparse import ( + urlparse, + urlunparse, ) import subprocess +from charmhelpers.core.hookenv import ( + config, + log, +) -CLOUD_ARCHIVE = """ # Ubuntu Cloud Archive +CLOUD_ARCHIVE = """# Ubuntu Cloud Archive deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main """ @@ -56,3 +66,79 @@ def configure_sources(update=False, add_source(sources[src_num], keys[src_num]) if update: apt_update(fatal=True) + +# The order of this list is very important. Handlers should be listed in from +# least- to most-specific URL matching. +FETCH_HANDLERS = ( + 'charmhelpers.fetch.archiveurl.ArchiveUrlFetchHandler', +) + + +class UnhandledSource(Exception): + pass + + +def install_remote(source): + """ + Install a file tree from a remote source + + The specified source should be a url of the form: + scheme://[host]/path[#[option=value][&...]] + + Schemes supported are based on this modules submodules + Options supported are submodule-specific""" + # We ONLY check for True here because can_handle may return a string + # explaining why it can't handle a given source. + handlers = [h for h in plugins() if h.can_handle(source) is True] + for handler in handlers: + try: + installed_to = handler.install(source) + except UnhandledSource: + pass + if not installed_to: + raise UnhandledSource("No handler found for source {}".format(source)) + return installed_to + + +def install_from_config(config_var_name): + charm_config = config() + source = charm_config[config_var_name] + return install_remote(source) + + +class BaseFetchHandler(object): + """Base class for FetchHandler implementations in fetch plugins""" + def can_handle(self, source): + """Returns True if the source can be handled. Otherwise returns + a string explaining why it cannot""" + return "Wrong source type" + + def install(self, source): + """Try to download and unpack the source. Return the path to the + unpacked files or raise UnhandledSource.""" + raise UnhandledSource("Wrong source type {}".format(source)) + + def parse_url(self, url): + return urlparse(url) + + def base_url(self, url): + """Return url without querystring or fragment""" + parts = list(self.parse_url(url)) + parts[4:] = ['' for i in parts[4:]] + return urlunparse(parts) + + +def plugins(fetch_handlers=None): + if not fetch_handlers: + fetch_handlers = FETCH_HANDLERS + plugin_list = [] + for handler_name in fetch_handlers: + package, classname = handler_name.rsplit('.', 1) + try: + handler_class = getattr(importlib.import_module(package), classname) + plugin_list.append(handler_class()) + except (ImportError, AttributeError): + # Skip missing plugins so that they can be ommitted from + # installation if desired + log("FetchHandler {} not found, skipping plugin".format(handler_name)) + return plugin_list