theblues-0.2.0/0000775000175000017500000000000012675042673012636 5ustar bacbac00000000000000theblues-0.2.0/theblues/0000775000175000017500000000000012675042673014451 5ustar bacbac00000000000000theblues-0.2.0/theblues/charmstore.py0000664000175000017500000004055112675041407017171 0ustar bacbac00000000000000import logging import requests from requests.exceptions import ( HTTPError, RequestException, Timeout, ) from .errors import ( EntityNotFound, ServerError, ) try: from urllib import urlencode except: from urllib.parse import urlencode class CharmStore(object): """A connection to the charmstore.""" def __init__(self, url, macaroons=None, timeout=None, verify=True): super(CharmStore, self).__init__() self.url = url self.verify = verify self.timeout = timeout self.macaroons = macaroons def _get(self, url): if self.macaroons is None or len(self.macaroons) == 0: cookies = {} else: cookies = dict([('macaroon-storefront', self.macaroons)]) try: response = requests.get(url, verify=self.verify, cookies=cookies, timeout=self.timeout) response.raise_for_status() return response # XXX: To be reviewed when splitting the library. # Is it te right place to log or should we let the users of the blues # to handle logging ? except HTTPError as exc: if exc.response.status_code in (404, 407): raise EntityNotFound(url) else: message = ('Error during request: {url} ' 'status code:({code}) ' 'message: {message}').format( url=url, code=exc.response.status_code, message=exc.response.text) logging.error(message) raise ServerError(exc.response.status_code, exc.response.text, message) except Timeout: message = 'Request timed out: {url} timeout: {timeout}' message = message.format(url=url, timeout=self.timeout) logging.error(message) raise ServerError(message) except RequestException as exc: message = ('Error during request: {url} ' 'message: {message}').format( url=url, message=exc.message) logging.error(message) raise ServerError(exc.args[0][1].errno, exc.args[0][1].strerror, exc.message) def _meta(self, entity_id, includes, channel=None): '''Retrieve metadata about an entity in the charmstore. @param entity_id The ID either a reference or a string of the entity to get. @param includes Which metadata fields to include in the response. @param channel optional channel name. ''' queries = [] if includes is not None: queries.extend([('include', include) for include in includes]) if channel is not None: queries.append(('channel', channel)) if len(queries): url = '{}/{}/meta/any?{}'.format(self.url, _get_path(entity_id), urlencode(queries)) else: url = '{}/{}/meta/any'.format(self.url, _get_path(entity_id)) data = self._get(url) return data.json() def entity(self, entity_id, get_files=False, channel=None): '''Get the default data for any entity (e.g. bundle or charm). @param entity_id The entity's id either as a reference or a string @param channel optional channel name. ''' includes = [ 'bundle-machine-count', 'bundle-metadata', 'bundle-unit-count', 'bundles-containing', 'charm-config', 'charm-metadata', 'common-info', 'extra-info', 'revision-info', 'published', 'stats', 'supported-series' ] if get_files: includes.append('manifest') return self._meta(entity_id, includes, channel=channel) def entities(self, entity_ids): '''Get the default data for entities. @param entity_ids A list of entity ids either as strings or references. ''' url = '%s/meta/any?include=id&' % self.url for entity_id in entity_ids: url += 'id=%s&' % _get_path(entity_id) # Remove the trailing '&' from the URL. url = url[:-1] data = self._get(url) return data.json() def bundle(self, bundle_id, channel=None): '''Get the default data for a bundle. @param bundle_id The bundle's id. @param channel optional channel name. ''' return self.entity(bundle_id, get_files=True, channel=channel) def charm(self, charm_id, channel=None): '''Get the default data for a charm. @param charm_id The charm's id. @param channel optional channel name. ''' return self.entity(charm_id, get_files=True, channel=channel) def charm_icon_url(self, charm_id, channel=None): '''Generate the path to the icon for charms. @param charm_id The ID of the charm, bundle icons are not currently supported. @param channel optional channel name. @return url string for the path to the icon.''' url = '{}/{}/icon.svg'.format(self.url, _get_path(charm_id)) return _add_channel(url, channel) def charm_icon(self, charm_id, channel=None): url = self.charm_icon_url(charm_id, channel=channel) response = self._get(url) return response.content def bundle_visualization(self, bundle_id, channel=None): url = self.bundle_visualization_url(bundle_id, channel=channel) response = self._get(url) return response.content def bundle_visualization_url(self, bundle_id, channel=None): url = '{}/{}/diagram.svg'.format(self.url, _get_path(bundle_id)) return _add_channel(url, channel) def entity_readme_url(self, entity_id, channel=None): '''Generate the url path for the readme of the entity.''' url = '{}/{}/readme'.format(self.url, _get_path(entity_id)) return _add_channel(url, channel) def entity_readme_content(self, entity_id, channel=None): readme_url = self.entity_readme_url(entity_id, channel=channel) response = self._get(readme_url) return response.text def archive_url(self, entity_id, channel=None): '''Generate a URL for the archive. @param entity_id The ID of the entity to look up as a string or reference. @param channel optional channel name. ''' url = '{}/{}/archive'.format(self.url, _get_path(entity_id)) return _add_channel(url, channel) def file_url(self, entity_id, filename, channel=None): '''Generate a URL for a file in an archive without requesting it. @param entity_id The ID of the entity to look up. @param filename The name of the file in the archive. @param channel optional channel name. ''' url = '{}/{}/archive/{}'.format(self.url, _get_path(entity_id), filename) return _add_channel(url, channel) def files(self, entity_id, manifest=None, filename=None, read_file=False, channel=None): ''' Get the files or file contents of a file for an entity. If all files are requested, a dictionary of filenames and urls for the files in the archive are returned. If filename is provided, the url of just that file is returned, if it exists. If filename is provided and read_file is true, the *contents* of the file are returned, if the file exists. @param entity_id The id of the entity to get files for @param manifest The manifest of files for the entity. Providing this reduces queries; if not provided, the manifest is looked up in the charmstore. @param filename The name of the file in the archive to get. @param read_file Whether to get the url for the file or the file contents. @param channel optional channel name. ''' if manifest is None: manifest_url = '{}/{}/meta/manifest'.format(self.url, _get_path(entity_id)) manifest_url = _add_channel(manifest_url, channel) manifest = self._get(manifest_url) manifest = manifest.json() files = {} for f in manifest: manifest_name = f['Name'] file_url = self.file_url(_get_path(entity_id), manifest_name, channel=channel) files[manifest_name] = file_url if filename: file_url = files.get(filename, None) if file_url is None: raise EntityNotFound(entity_id, filename) if read_file: data = self._get(file_url) return data.text else: return file_url else: return files def config(self, charm_id, channel=None): '''Get the config data for a charm. @param charm_id The charm's id. @param channel optional channel name. ''' url = '{}/{}/meta/charm-config'.format(self.url, _get_path(charm_id)) data = self._get(_add_channel(url, channel)) return data.json() def entityId(self, partial, channel=None): '''Get an entity's full id provided a partial one. Raises EntityNotFound if partial cannot be resolved. @param partial The partial id (e.g. mysql, precise/mysql). @param channel optional channel name. ''' url = '{}/{}/meta/any'.format(self.url, _get_path(partial)) data = self._get(_add_channel(url, channel)) return data.json()['Id'] def search(self, text, includes=None, doc_type=None, limit=None, autocomplete=False, promulgated_only=False, tags=None, sort=None, owner=None, series=None): ''' Search for entities in the charmstore. @param text The text to search for. @param includes What metadata to return in results (e.g. charm-config). @param doc_type Filter to this type: bundle or charm. @param limit Maximum number of results to return. @param autocomplete Whether to prefix/suffix match search terms. @param promulgated_only Whether to filter to only promulgated charms. @param tags The tags to filter; can be a list of tags or a single tag. @param sort Sorting the result based on the sort string provided which can be name, author, series and - in front for descending. @param owner Optional owner. If provided, search results will only include entities that owner can view. @param series The series to filter; can be a list of series or a single series. ''' queries = self._common_query_parameters(doc_type, includes, owner, promulgated_only, series, sort) if len(text): queries.append(('text', text)) if limit is not None: queries.append(('limit', limit)) if autocomplete: queries.append(('autocomplete', 1)) if tags is not None: if type(tags) is list: tags = ','.join(tags) queries.append(('tags', tags)) if len(queries): url = '{}/search?{}'.format(self.url, urlencode(queries)) else: url = '{}/search'.format(self.url) data = self._get(url) return data.json()['Results'] def list(self, includes=None, doc_type=None, promulgated_only=False, sort=None, owner=None, series=None): ''' List entities in the charmstore. @param includes What metadata to return in results (e.g. charm-config). @param doc_type Filter to this type: bundle or charm. @param promulgated_only Whether to filter to only promulgated charms. @param sort Sorting the result based on the sort string provided which can be name, author, series and - in front for descending. @param owner Optional owner. If provided, search results will only include entities that owner can view. @param series The series to filter; can be a list of series or a single series. ''' queries = self._common_query_parameters(doc_type, includes, owner, promulgated_only, series, sort) if len(queries): url = '{}/list?{}'.format(self.url, urlencode(queries)) else: url = '{}/list'.format(self.url) data = self._get(url) return data.json()['Results'] def _common_query_parameters(self, doc_type, includes, owner, promulgated_only, series, sort): ''' Extract common query parameters between search and list into slice. @param includes What metadata to return in results (e.g. charm-config). @param doc_type Filter to this type: bundle or charm. @param promulgated_only Whether to filter to only promulgated charms. @param sort Sorting the result based on the sort string provided which can be name, author, series and - in front for descending. @param owner Optional owner. If provided, search results will only include entities that owner can view. @param series The series to filter; can be a list of series or a single series. ''' queries = [] if includes is not None: queries.extend([('include', include) for include in includes]) if doc_type is not None: queries.append(('type', doc_type)) if promulgated_only: queries.append(('promulgated', 1)) if owner is not None: queries.append(('owner', owner)) if series is not None: if type(series) is list: series = ','.join(series) queries.append(('series', series)) if sort is not None: queries.append(('sort', sort)) return queries def fetch_related(self, ids): if not ids: return [] meta = '&id='.join(id['Id'] for id in ids) url = ('{url}/meta/any?id={meta}' '&include=bundle-metadata&include=stats' '&include=supported-series&include=extra-info' '&include=bundle-unit-count').format( url=self.url, meta=meta) data = self._get(url) return data.json().values() def fetch_interfaces(self, interface, way): """Get the list of charms that provides or requires this id @param id: charm string @param way: provides or requires @return List of charms """ if not interface: return [] if way == 'requires': request = '&requires=' + interface else: request = '&provides=' + interface url = (self.url + '/search?' + 'include=charm-metadata&include=stats&include=supported-series' '&include=extra-info&include=bundle-unit-count' '&limit=1000' + request) data = self._get(url) return data.json().values() def debug(self): '''Retrieve the debug information from the charmstore.''' url = '{}/debug/status'.format(self.url) data = self._get(url) return data.json() def fetch_macaroon(self): '''Fetch a macaroon from charmstore.''' url = '{charmstore_url}/macaroon'.format( charmstore_url=self.url) response = self._get(url) return response.text def _get_path(entity_id): '''Get the entity_id as a string if it is a Reference. @param entity_id The ID either a reference or a string of the entity to get. @return entity_id as a string ''' try: path = entity_id.path() except AttributeError: path = entity_id return path def _add_channel(url, channel=None): '''Add channel query parameters when present. @param url the url to add the channel query when present. @param channel the channel name. @return the url with channel query parameter when present. ''' if channel is not None: url = '{}?channel={}'.format(url, channel) return url theblues-0.2.0/theblues/__init__.py0000664000175000017500000000002612675041301016543 0ustar bacbac00000000000000__version__ = '0.0.1' theblues-0.2.0/theblues/errors.py0000664000175000017500000000020012675041301016312 0ustar bacbac00000000000000class EntityNotFound(Exception): pass class InvalidMacaroon(Exception): pass class ServerError(Exception): pass theblues-0.2.0/theblues.egg-info/0000775000175000017500000000000012675042673016143 5ustar bacbac00000000000000theblues-0.2.0/theblues.egg-info/not-zip-safe0000664000175000017500000000000112675041422020360 0ustar bacbac00000000000000 theblues-0.2.0/theblues.egg-info/requires.txt0000664000175000017500000000004512675042672020541 0ustar bacbac00000000000000requests>=2.1.1 jujubundlelib>=0.4.1 theblues-0.2.0/theblues.egg-info/top_level.txt0000664000175000017500000000001112675042672020664 0ustar bacbac00000000000000theblues theblues-0.2.0/theblues.egg-info/SOURCES.txt0000664000175000017500000000054512675042673020033 0ustar bacbac00000000000000AUTHORS.rst CHANGELOG.rst HACKING.rst LICENSE MANIFEST.in README.rst setup.py theblues/__init__.py theblues/charmstore.py theblues/errors.py theblues.egg-info/PKG-INFO theblues.egg-info/SOURCES.txt theblues.egg-info/dependency_links.txt theblues.egg-info/not-zip-safe theblues.egg-info/pbr.json theblues.egg-info/requires.txt theblues.egg-info/top_level.txttheblues-0.2.0/theblues.egg-info/dependency_links.txt0000664000175000017500000000000112675042672022210 0ustar bacbac00000000000000 theblues-0.2.0/theblues.egg-info/pbr.json0000664000175000017500000000005712675042672017622 0ustar bacbac00000000000000{"is_release": false, "git_version": "b18668f"}theblues-0.2.0/theblues.egg-info/PKG-INFO0000664000175000017500000000766612675042672017256 0ustar bacbac00000000000000Metadata-Version: 1.1 Name: theblues Version: 0.2.0 Summary: Python library for using the juju charm store API. Home-page: https://github.com/juju/theblues Author: JC Sackett Author-email: jcsackett@canonical.com License: UNKNOWN Description: ============================= theblues ============================= Python library for using the juju charmstore API. Installation ------------ The easiest way to install theblues is via pip:: $ pip install theblues Note that theblues requires python-macaroons (which has its own dependencies), which must be installed from a ppa:: $ sudo add-apt-repository ppa:yellow/ppa -y $ apt-get install libmacaroons0 python-macaroons libsodium13 Without these, theblues cannot communicate with the charmstore. Usage ----- Interacting with the charmstore is pretty simple. To look up an entity on the charmstore (e.g. a charm or bundle):: >>> from theblues.charmstore import CharmStore >>> cs = CharmStore('https://api.jujucharms.com/v4') >>> entity = cs.entity('wordpress') >>> entity['Id'] u'cs:trusty/wordpress-2' Data for an entity is contained in the `Meta` item of the response, matching the json returned from the charmstores:: >>> entity['Meta']['charm-metadata']['Name'] u'wordpress' You can also get files for the entity:: >>> cs.files('wordpress')['hooks/install'] u'https://api.jujucharms.com/v4/wordpress/archive/hooks/install >>> hook = cs.files('wordpress', filename='hooks/install', read_file=True) >>> print hook #!/bin/bash set -xe ... ... juju-log "So, environment is setup. We'll wait for some hooks to fire off before we get all crazy" To see all methods available, refer to the full docs. History ------- 0.2.0 (2016-03-24) ++++++++++++++++++ * Add LGPL3 license. * Add optional channel arguments. * Make deps less strict to work across trusty -> xenial. 0.1.1 (2016-01-25) ++++++++++++++++++ * Use Reference from jujubundlelib as a parameter. * Add list endpoint. 0.1.0 (2015-12-04) ++++++++++++++++++ * Fix for empty macaroon cookie. 0.0.5 (2015-11-20) ++++++++++++++++++ * Expose common-info. * Fix import. 0.0.4 (2015-06-10) ++++++++++++++++++ * Support setting a timeout on charmstore requests. 0.0.3 (2015-05-04) ++++++++++++++++++ * Add type filter to charmstore search. 0.0.2 (2015-04-08) ++++++++++++++++++ * Add series filter to charmstore search. * Handle 407 http error from charmstore as EntityNotFound. * Add simple usage example to README. * Minor changes to HACKING. * Minor fixes. 0.0.1 (2015-03-19) ++++++++++++++++++ * Initial release. Keywords: theblues Platform: UNKNOWN Classifier: Development Status :: 2 - Pre-Alpha Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) Classifier: Natural Language :: English Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 theblues-0.2.0/setup.cfg0000664000175000017500000000007312675042673014457 0ustar bacbac00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 theblues-0.2.0/LICENSE0000664000175000017500000001674312675041407013650 0ustar bacbac00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. theblues-0.2.0/MANIFEST.in0000664000175000017500000000014012675041301014352 0ustar bacbac00000000000000include AUTHORS.rst include HACKING.rst include CHANGELOG.rst include LICENSE include README.rsttheblues-0.2.0/CHANGELOG.rst0000664000175000017500000000161312675041407014652 0ustar bacbac00000000000000.. :changelog: History ------- 0.2.0 (2016-03-24) ++++++++++++++++++ * Add LGPL3 license. * Add optional channel arguments. * Make deps less strict to work across trusty -> xenial. 0.1.1 (2016-01-25) ++++++++++++++++++ * Use Reference from jujubundlelib as a parameter. * Add list endpoint. 0.1.0 (2015-12-04) ++++++++++++++++++ * Fix for empty macaroon cookie. 0.0.5 (2015-11-20) ++++++++++++++++++ * Expose common-info. * Fix import. 0.0.4 (2015-06-10) ++++++++++++++++++ * Support setting a timeout on charmstore requests. 0.0.3 (2015-05-04) ++++++++++++++++++ * Add type filter to charmstore search. 0.0.2 (2015-04-08) ++++++++++++++++++ * Add series filter to charmstore search. * Handle 407 http error from charmstore as EntityNotFound. * Add simple usage example to README. * Minor changes to HACKING. * Minor fixes. 0.0.1 (2015-03-19) ++++++++++++++++++ * Initial release. theblues-0.2.0/HACKING.rst0000664000175000017500000000642312675041301014424 0ustar bacbac00000000000000======= HACKING ======= Getting Started --------------- Getting the code ~~~~~~~~~~~~~~~~ If you just want to look at the code, you can clone the main repository: :: $ git clone git@github.com:juju/theblues.git If you want to hack on theblues, you should follow the directions below. Setting up a development environment ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Get Started ----------- 1. Fork_ the `theblues` repo on GitHub. .. _Fork: https://github.com/juju/theblues 2. Clone your fork locally:: $ git clone git@github.com:your_name_here/theblues.git 3. Create a branch for local development:: $ git checkout -b name-of-your-bugfix-or-feature Now you can make your changes locally. 4. When you're done making changes, check that your changes pass style and unit tests, including testing other Python 2 and 3:: $ make check 5. Commit your changes and push your branch to GitHub:: $ git add . $ git commit -m "Your detailed description of your changes." $ git push origin name-of-your-bugfix-or-feature 6. Submit a pull request through GitHub. Pull Request Guidelines ----------------------- Before you submit a pull request, check that it meets these guidelines: 1. The pull request should include tests. 2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.rst. 3. The pull request should work for Python 2.7 and 3.4. Tips ---- To run a subset of tests:: $ py.test -s theblues/tests/ -k $name_of_test_or_pattern Useful git aliases ~~~~~~~~~~~~~~~~~~ Assuming you have the main repo as a remote named upstream :: # Update your local develop branch with the latest from the juju remote. # Then make sure to push that back up to your fork on github to keep # everything in sync. sync-upstream = "!f() { git checkout develop && git pull upstream develop && git push origin develop; }; f" # Rebase develop (trunk) into the current feature branch. sync-trunk = rebase develop Hooks ~~~~~ Our test/lint targets are run by CI, but it can be hard to remember to run that before proposing your branch. If you would like to have those run before you push your code to Github, you can add any of those targets to either the `pre-commit` or `pre-push` (git 1.8.2+) hook, like: :: #!/bin/sh if test ! $NO_VERIFY ; then make lint fi Add the above to the file `.git/hooks/pre-commit` or `.git/hooks/pre-push` then run `chmod a+x .git/hooks/`. `lint` is the simplest target and will allow you to commit broken code so long as it passes lint. `check` is the most stringent option that requires passing tests in debug and prod as well. You can then use the command `NO_VERIFY=1 git commit` to commit or `NO_VERIFY=1 git push origin ` to push a branch that will not pass lint. Running the command without the variable will cause lint to prevent the command from succeeding if your branch does not lint. Read more about hooks and how to install them `here `_. Please note that this will only work in environments where the app can build and run. Since the application will not run in OS X, you will have to run your push or commit from vagrant instead. theblues-0.2.0/AUTHORS.rst0000664000175000017500000000052512675041301014502 0ustar bacbac00000000000000=========== Development =========== * Brad Crittenden * Fabrice Matrat * Huw Wilkins * JC Sackett * Jeff Pihach * Madison Scott-Clary * Richard Harding theblues-0.2.0/setup.py0000664000175000017500000000252712675041407014350 0ustar bacbac00000000000000#!/usr/bin/env python import os import sys try: from setuptools import setup except ImportError: from distutils.core import setup if sys.argv[-1] == 'publish': os.system('python setup.py sdist upload') sys.exit() readme = open('README.rst').read() history = open('CHANGELOG.rst').read().replace('.. :changelog:', '') setup( name='theblues', version='0.2.0', description='Python library for using the juju charm store API.', long_description=readme + '\n\n' + history, author='JC Sackett', author_email='jcsackett@canonical.com', url='https://github.com/juju/theblues', packages=[ 'theblues', ], package_dir={'theblues': 'theblues'}, include_package_data=True, install_requires=[ 'requests>=2.1.1', 'jujubundlelib>=0.4.1', ], tests_requires=[ 'httmock==1.2.3', ], zip_safe=False, keywords='theblues', classifiers=[ 'Development Status :: 2 - Pre-Alpha', 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU Lesser General Public License v3 ' + '(LGPLv3)', 'Natural Language :: English', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', ], ) theblues-0.2.0/PKG-INFO0000664000175000017500000000766612675042673013752 0ustar bacbac00000000000000Metadata-Version: 1.1 Name: theblues Version: 0.2.0 Summary: Python library for using the juju charm store API. Home-page: https://github.com/juju/theblues Author: JC Sackett Author-email: jcsackett@canonical.com License: UNKNOWN Description: ============================= theblues ============================= Python library for using the juju charmstore API. Installation ------------ The easiest way to install theblues is via pip:: $ pip install theblues Note that theblues requires python-macaroons (which has its own dependencies), which must be installed from a ppa:: $ sudo add-apt-repository ppa:yellow/ppa -y $ apt-get install libmacaroons0 python-macaroons libsodium13 Without these, theblues cannot communicate with the charmstore. Usage ----- Interacting with the charmstore is pretty simple. To look up an entity on the charmstore (e.g. a charm or bundle):: >>> from theblues.charmstore import CharmStore >>> cs = CharmStore('https://api.jujucharms.com/v4') >>> entity = cs.entity('wordpress') >>> entity['Id'] u'cs:trusty/wordpress-2' Data for an entity is contained in the `Meta` item of the response, matching the json returned from the charmstores:: >>> entity['Meta']['charm-metadata']['Name'] u'wordpress' You can also get files for the entity:: >>> cs.files('wordpress')['hooks/install'] u'https://api.jujucharms.com/v4/wordpress/archive/hooks/install >>> hook = cs.files('wordpress', filename='hooks/install', read_file=True) >>> print hook #!/bin/bash set -xe ... ... juju-log "So, environment is setup. We'll wait for some hooks to fire off before we get all crazy" To see all methods available, refer to the full docs. History ------- 0.2.0 (2016-03-24) ++++++++++++++++++ * Add LGPL3 license. * Add optional channel arguments. * Make deps less strict to work across trusty -> xenial. 0.1.1 (2016-01-25) ++++++++++++++++++ * Use Reference from jujubundlelib as a parameter. * Add list endpoint. 0.1.0 (2015-12-04) ++++++++++++++++++ * Fix for empty macaroon cookie. 0.0.5 (2015-11-20) ++++++++++++++++++ * Expose common-info. * Fix import. 0.0.4 (2015-06-10) ++++++++++++++++++ * Support setting a timeout on charmstore requests. 0.0.3 (2015-05-04) ++++++++++++++++++ * Add type filter to charmstore search. 0.0.2 (2015-04-08) ++++++++++++++++++ * Add series filter to charmstore search. * Handle 407 http error from charmstore as EntityNotFound. * Add simple usage example to README. * Minor changes to HACKING. * Minor fixes. 0.0.1 (2015-03-19) ++++++++++++++++++ * Initial release. Keywords: theblues Platform: UNKNOWN Classifier: Development Status :: 2 - Pre-Alpha Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) Classifier: Natural Language :: English Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 theblues-0.2.0/README.rst0000664000175000017500000000275112675041301014315 0ustar bacbac00000000000000============================= theblues ============================= Python library for using the juju charmstore API. Installation ------------ The easiest way to install theblues is via pip:: $ pip install theblues Note that theblues requires python-macaroons (which has its own dependencies), which must be installed from a ppa:: $ sudo add-apt-repository ppa:yellow/ppa -y $ apt-get install libmacaroons0 python-macaroons libsodium13 Without these, theblues cannot communicate with the charmstore. Usage ----- Interacting with the charmstore is pretty simple. To look up an entity on the charmstore (e.g. a charm or bundle):: >>> from theblues.charmstore import CharmStore >>> cs = CharmStore('https://api.jujucharms.com/v4') >>> entity = cs.entity('wordpress') >>> entity['Id'] u'cs:trusty/wordpress-2' Data for an entity is contained in the `Meta` item of the response, matching the json returned from the charmstores:: >>> entity['Meta']['charm-metadata']['Name'] u'wordpress' You can also get files for the entity:: >>> cs.files('wordpress')['hooks/install'] u'https://api.jujucharms.com/v4/wordpress/archive/hooks/install >>> hook = cs.files('wordpress', filename='hooks/install', read_file=True) >>> print hook #!/bin/bash set -xe ... ... juju-log "So, environment is setup. We'll wait for some hooks to fire off before we get all crazy" To see all methods available, refer to the full docs.