async def connect_juju(ctrl_name=None, model_name=None, endpoint=None, username=None, password=None, cacert=None): controller = Controller(max_frame_size=MAX_FRAME_SIZE) # noqa if endpoint: await controller.connect(endpoint=endpoint, username=username, password=password, cacert=cacert) else: await controller.connect(ctrl_name) if endpoint: model = Model(max_frame_size=MAX_FRAME_SIZE) await model.connect(uuid=model_name, endpoint=endpoint, username=username, password=password, cacert=cacert) elif model_name: model = await controller.get_model(model_name) else: model = Model(max_frame_size=MAX_FRAME_SIZE) # noqa await model.connect() # HACK low unsettable timeout in the model model.charmstore._cs = CharmStore(timeout=60) return controller, model
import collections import gfm import os import re from jujubundlelib import references from theblues.charmstore import CharmStore from theblues.errors import EntityNotFound, ServerError from theblues.terms import Terms cs = CharmStore() terms = Terms("https://api.jujucharms.com/terms/") SEARCH_LIMIT = 400 def search_entities( query, entity_type=None, tags=None, sort=None, series=None, owner=None, promulgated_only=None, ): includes = [ "charm-metadata", "bundle-metadata", "owner", "bundle-unit-count",
from flask import Blueprint, render_template, abort, current_app from theblues.charmstore import CharmStore from pprint import pformat jaasstore = Blueprint('jaasstore', __name__, template_folder='/templates', static_folder='/static') cs = CharmStore("https://api.jujucharms.com/v5") @jaasstore.route('/store') def store(): return render_template('store/store.html') @jaasstore.route('/<entity_name>') def details(entity_name): try: entity = cs.entity(entity_name) entity_formatted = pformat(entity, 1, 120) except Exception: return abort(404, "Entity not found {}".format(entity_name)) return render_template('store/details.html', entity=entity_formatted)
import collections import os import re import markdown from mdx_gfm import GithubFlavoredMarkdownExtension from jujubundlelib import references from theblues.charmstore import CharmStore from theblues.errors import EntityNotFound, ServerError from theblues.terms import Terms cs = CharmStore(timeout=5) terms = Terms("https://api.jujucharms.com/terms/") SEARCH_LIMIT = 400 markdown = markdown.Markdown(extensions=[GithubFlavoredMarkdownExtension()]) def search_entities( query, entity_type=None, tags=None, sort=None, series=None, owner=None, promulgated_only=None, ): includes = [ "charm-metadata", "bundle-metadata",
def setUp(self): self.cs = CharmStore('http://example.com')
class TestCharmStore(TestCase): def entities_response(self, url, request): self.assertEqual( url.geturl(), 'http://example.com/meta/' + 'any?include=id&id=wordpress&id=mysql') return { 'status_code': 200, 'content': b'{"wordpress":"wordpress","mysql":"mysql"}'} def setUp(self): self.cs = CharmStore('http://example.com') def test_init(self): self.assertEqual(self.cs.url, 'http://example.com') def test_entity(self): with HTTMock(entity_200): data = self.cs.charm(SAMPLE_CHARM) self.assertEqual({'Meta': {'charm-metadata': {'exists': True}}}, data) def test_entity_full_id(self): with HTTMock(entity_200): data = self.cs.charm(SAMPLE_CHARM_ID) self.assertEqual({'Meta': {'charm-metadata': {'exists': True}}}, data) def test_entity_reference(self): with HTTMock(entity_200): data = self.cs.charm( references.Reference.from_string(SAMPLE_CHARM)) self.assertEqual({'Meta': {'charm-metadata': {'exists': True}}}, data) def test_entities(self): with HTTMock(self.entities_response): response = self.cs.entities(['wordpress', 'mysql']) self.assertEqual( {u'wordpress': u'wordpress', u'mysql': u'mysql'}, response) def test_entities_reference(self): with HTTMock(self.entities_response): response = self.cs.entities( [references.Reference.from_string('wordpress'), references.Reference.from_string('mysql')]) self.assertEqual( {u'wordpress': u'wordpress', u'mysql': u'mysql'}, response) def test_entities_error(self): with HTTMock(entity_404): with self.assertRaises(EntityNotFound) as cm: self.cs.entities(['not-found']) self.assertEqual( 'http://example.com/meta/any?include=id&id=not-found', cm.exception.args[0] ) def test_charm_error(self): with HTTMock(entity_404): with self.assertRaises(EntityNotFound): self.cs.charm(SAMPLE_CHARM) def test_charm_error_id(self): with HTTMock(entity_404): with self.assertRaises(EntityNotFound): self.cs.charm(SAMPLE_CHARM_ID) def test_charm_error_407(self): with HTTMock(entity_407): with self.assertRaises(EntityNotFound): self.cs.charm(SAMPLE_CHARM) def test_config(self): with HTTMock(config_200): data = self.cs.config(SAMPLE_CHARM) self.assertEqual({'exists': True}, data) def test_config_reference(self): with HTTMock(config_200): data = self.cs.config( references.Reference.from_string(SAMPLE_CHARM)) self.assertEqual({'exists': True}, data) def test_config_error(self): with HTTMock(config_404): with self.assertRaises(EntityNotFound): self.cs.config(SAMPLE_CHARM) def test_archive_url(self): with HTTMock(manifest_200): data = self.cs.archive_url(SAMPLE_CHARM) self.assertEqual(u'http://example.com/precise/mysql-1/archive', data) def test_archive_url_reference(self): with HTTMock(manifest_200): data = self.cs.archive_url( references.Reference.from_string(SAMPLE_CHARM)) self.assertEqual(u'http://example.com/precise/mysql-1/archive', data) def test_file_good(self): with HTTMock(manifest_200): data = self.cs.files(SAMPLE_CHARM) self.assertEqual({ u'icon.svg': u'http://example.com/precise/mysql-1/archive/icon.svg', u'README.md': u'http://example.com/precise/mysql-1/archive/README.md' }, data) def test_file_good_reference(self): with HTTMock(manifest_200): data = self.cs.files( references.Reference.from_string(SAMPLE_CHARM)) self.assertEqual({ u'icon.svg': u'http://example.com/precise/mysql-1/archive/icon.svg', u'README.md': u'http://example.com/precise/mysql-1/archive/README.md' }, data) def test_file_error(self): with HTTMock(manifest_404): with self.assertRaises(EntityNotFound): self.cs.files(SAMPLE_CHARM) def test_file_one_file(self): with HTTMock(manifest_200): data = self.cs.files(SAMPLE_CHARM, filename='README.md') self.assertEqual( u'http://example.com/precise/mysql-1/archive/README.md', data) def test_file_one_file_reference(self): with HTTMock(manifest_200): data = self.cs.files( references.Reference.from_string(SAMPLE_CHARM), filename='README.md') self.assertEqual( u'http://example.com/precise/mysql-1/archive/README.md', data) def test_file_one_file_error(self): with HTTMock(manifest_200): with self.assertRaises(EntityNotFound): self.cs.files(SAMPLE_CHARM, filename='does.not.exist.md') def test_file_read_file(self): with HTTMock(manifest_200): with HTTMock(file_200): data = self.cs.files( SAMPLE_CHARM, filename='README.md', read_file=True) self.assertEqual('This is a file.', data) def test_file_read_file_reference(self): with HTTMock(manifest_200): with HTTMock(file_200): data = self.cs.files( references.Reference.from_string(SAMPLE_CHARM), filename='README.md', read_file=True) self.assertEqual('This is a file.', data) def test_file_read_file_error(self): with HTTMock(manifest_200): with HTTMock(file_404): with self.assertRaises(EntityNotFound): self.cs.files( SAMPLE_CHARM, filename='readme.md', read_file=True) def test_entityId(self): with HTTMock(id_200): charm_id = self.cs.entityId('bar') self.assertEqual('foo/bar', charm_id) def test_entityId_reference(self): with HTTMock(id_200): charm_id = self.cs.entityId( references.Reference.from_string('bar')) self.assertEqual('foo/bar', charm_id) def test_ids_no_charm(self): with HTTMock(id_404): with self.assertRaises(EntityNotFound): self.cs.entityId('baz') def test_search(self): with HTTMock(search_200): results = self.cs.search('foo') self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_list(self): with HTTMock(list_200): results = self.cs.list() self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_escaped(self): with HTTMock(search_200_escaped): results = self.cs.search('&foo') self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_400(self): with patch('theblues.charmstore.logging.error') as log_mocked: with HTTMock(search_400): with self.assertRaises(ServerError) as cm: self.cs.search('foo') self.assertEqual(400, cm.exception.args[0]) log_mocked.assert_called_with( 'Error during request: http://example.com/search?text=foo ' 'status code:(400) message: ' '{"Message": "invalid parameter: user", "Code": "bad request"}') def test_list_400(self): with patch('theblues.charmstore.logging.error') as log_mocked: with HTTMock(search_400): with self.assertRaises(ServerError) as cm: self.cs.list() self.assertEqual(400, cm.exception.args[0]) log_mocked.assert_called_with( 'Error during request: http://example.com/list ' 'status code:(400) message: ' '{"Message": "invalid parameter: user", "Code": "bad request"}') def test_search_limit(self): with HTTMock(search_limit_200): results = self.cs.search('foo', limit=1) self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_autocomplete(self): with HTTMock(search_autocomplete_200): results = self.cs.search( 'foo', autocomplete=True) self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_includes(self): with HTTMock(search_includes_200): results = self.cs.search( 'foo', includes=['archive-size', 'charm-metadata']) self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_list_includes(self): with HTTMock(list_includes_200): results = self.cs.list(includes=['archive-size', 'charm-metadata']) self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_type(self): with HTTMock(search_type_200): results = self.cs.search('foo', doc_type='bundle') self.assertEqual([{'Id': 'cs:bundle/mediawiki-single-7'}], results) def test_list_type(self): with HTTMock(list_type_200): results = self.cs.list(doc_type='bundle') self.assertEqual([{'Id': 'cs:bundle/mediawiki-single-7'}], results) def test_search_tags(self): with HTTMock(search_tags_200): results = self.cs.search('foo', tags='databases') self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_multiple_tags(self): with HTTMock(search_multiple_tags_200): results = self.cs.search('foo', tags=['databases', 'applications']) self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_series(self): with HTTMock(search_series_200): results = self.cs.search('foo', series='precise') self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_list_series(self): with HTTMock(list_series_200): results = self.cs.list(series='precise') self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_multiple_series(self): with HTTMock(search_multiple_series_200): results = self.cs.search('foo', series=['trusty', 'precise']) self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_owner(self): with HTTMock(search_owner_200): results = self.cs.search('', owner='hatch') self.assertEqual([{"Id": "cs:foo/bar-0"}], results) def test_list_owner(self): with HTTMock(search_owner_200): results = self.cs.list(owner='hatch') self.assertEqual([{"Id": "cs:foo/bar-0"}], results) def test_charm_icon_url(self): entity_id = 'mongodb' url = self.cs.charm_icon_url(entity_id) self.assertEqual('http://example.com/mongodb/icon.svg', url) def test_charm_icon_url_reference(self): entity_id = 'mongodb' url = self.cs.charm_icon_url( references.Reference.from_string(entity_id)) self.assertEqual('http://example.com/mongodb/icon.svg', url) def test_charm_icon_ok(self): entity_id = 'precise/mysql-1' with HTTMock(icon_200): icon = self.cs.charm_icon(entity_id) self.assertEqual('icon', icon) def test_charm_icon_ok_reference(self): entity_id = 'precise/mysql-1' with HTTMock(icon_200): icon = self.cs.charm_icon( references.Reference.from_string(entity_id)) self.assertEqual('icon', icon) def test_charm_icon_bad(self): entity_id = 'precise/mysql-1' with HTTMock(icon_404): with self.assertRaises(EntityNotFound): self.cs.charm_icon(entity_id) def test_bundle_visualization_url(self): entity_id = 'mongodb-cluster' url = self.cs.bundle_visualization_url(entity_id) self.assertEqual('http://example.com/mongodb-cluster/diagram.svg', url) def test_bundle_visualization_url_reference(self): entity_id = 'mongodb-cluster' url = self.cs.bundle_visualization_url( references.Reference.from_string(entity_id)) self.assertEqual('http://example.com/mongodb-cluster/diagram.svg', url) def test_bundle_visualization_ok(self): entity_id = 'mongodb-cluster' with HTTMock(diagram_200): diagram = self.cs.bundle_visualization(entity_id) self.assertEqual('diagram', diagram) def test_bundle_visualization_bad(self): entity_id = 'mongodb-cluster' with HTTMock(diagram_404): with self.assertRaises(EntityNotFound): self.cs.bundle_visualization(entity_id) def test_bundle_visualization_reference(self): entity_id = 'mongodb-cluster' with HTTMock(diagram_200): diagram = self.cs.bundle_visualization( references.Reference.from_string(entity_id)) self.assertEqual('diagram', diagram) def test_entity_readme_url(self): entity_id = 'mongodb-cluster' url = self.cs.entity_readme_url(entity_id) self.assertEqual('http://example.com/mongodb-cluster/readme', url) def test_entity_readme_url_reference(self): entity_id = 'mongodb-cluster' url = self.cs.entity_readme_url( references.Reference.from_string(entity_id)) self.assertEqual('http://example.com/mongodb-cluster/readme', url) def test_readme_not_found(self): # A 404 not found error is returned when the readme cannot be found. with HTTMock(readme_404): with self.assertRaises(EntityNotFound): self.cs.entity_readme_content('precise/mysql-1') def test_readme_content(self): with HTTMock(readme_200): content = self.cs.entity_readme_content('precise/mysql-1') self.assertEqual('This is the readme', content) def test_readme_content_reference(self): with HTTMock(readme_200): content = self.cs.entity_readme_content( references.Reference.from_string('precise/mysql-1')) self.assertEqual('This is the readme', content) def test_debug(self): with HTTMock(debug_200): debug_data = self.cs.debug() self.assertEqual('all clear', debug_data['status']) def test_timeout(self): with HTTMock(search_timeout): with self.assertRaises(ServerError) as cm: self.cs.search('foo') message = cm.exception.args[0] self.assertTrue('Request timed out' in message) def test_resource_url(self): entity_id = 'mongodb' url = self.cs.resource_url(entity_id, "myresource", "22") self.assertEqual('http://example.com/mongodb/resource/myresource/22', url)
class TestCharmStore(TestCase): def entities_response(self, url, request): self.assertEqual( url.geturl(), 'http://example.com/meta/' + 'any?include=id&id=wordpress&id=mysql') return { 'status_code': 200, 'content': b'{"wordpress":"wordpress","mysql":"mysql"}'} def setUp(self): self.cs = CharmStore('http://example.com') def test_init(self): self.assertEqual(self.cs.url, 'http://example.com') def test_entity(self): with HTTMock(entity_200): data = self.cs.charm(SAMPLE_CHARM) self.assertEqual({'Meta': {'charm-metadata': {'exists': True}}}, data) def test_entity_full_id(self): with HTTMock(entity_200): data = self.cs.charm(SAMPLE_CHARM_ID) self.assertEqual({'Meta': {'charm-metadata': {'exists': True}}}, data) def test_entity_reference(self): with HTTMock(entity_200): data = self.cs.charm( references.Reference.from_string(SAMPLE_CHARM)) self.assertEqual({'Meta': {'charm-metadata': {'exists': True}}}, data) def test_entities(self): with HTTMock(self.entities_response): response = self.cs.entities(['wordpress', 'mysql']) self.assertEqual( {u'wordpress': u'wordpress', u'mysql': u'mysql'}, response) def test_entities_reference(self): with HTTMock(self.entities_response): response = self.cs.entities( [references.Reference.from_string('wordpress'), references.Reference.from_string('mysql')]) self.assertEqual( {u'wordpress': u'wordpress', u'mysql': u'mysql'}, response) def test_entities_error(self): with HTTMock(entity_404): with self.assertRaises(EntityNotFound) as cm: self.cs.entities(['not-found']) self.assertEqual( 'http://example.com/meta/any?include=id&id=not-found', cm.exception.args[0] ) def test_charm_error(self): with HTTMock(entity_404): with self.assertRaises(EntityNotFound): self.cs.charm(SAMPLE_CHARM) def test_charm_error_id(self): with HTTMock(entity_404): with self.assertRaises(EntityNotFound): self.cs.charm(SAMPLE_CHARM_ID) def test_charm_error_407(self): with HTTMock(entity_407): with self.assertRaises(EntityNotFound): self.cs.charm(SAMPLE_CHARM) def test_config(self): with HTTMock(config_200): data = self.cs.config(SAMPLE_CHARM) self.assertEqual({'exists': True}, data) def test_config_reference(self): with HTTMock(config_200): data = self.cs.config( references.Reference.from_string(SAMPLE_CHARM)) self.assertEqual({'exists': True}, data) def test_config_error(self): with HTTMock(config_404): with self.assertRaises(EntityNotFound): self.cs.config(SAMPLE_CHARM) def test_archive_url(self): with HTTMock(manifest_200): data = self.cs.archive_url(SAMPLE_CHARM) self.assertEqual(u'http://example.com/precise/mysql-1/archive', data) def test_archive_url_reference(self): with HTTMock(manifest_200): data = self.cs.archive_url( references.Reference.from_string(SAMPLE_CHARM)) self.assertEqual(u'http://example.com/precise/mysql-1/archive', data) def test_file_good(self): with HTTMock(manifest_200): data = self.cs.files(SAMPLE_CHARM) self.assertEqual({ u'icon.svg': u'http://example.com/precise/mysql-1/archive/icon.svg', u'README.md': u'http://example.com/precise/mysql-1/archive/README.md' }, data) def test_file_good_reference(self): with HTTMock(manifest_200): data = self.cs.files( references.Reference.from_string(SAMPLE_CHARM)) self.assertEqual({ u'icon.svg': u'http://example.com/precise/mysql-1/archive/icon.svg', u'README.md': u'http://example.com/precise/mysql-1/archive/README.md' }, data) def test_file_error(self): with HTTMock(manifest_404): with self.assertRaises(EntityNotFound): self.cs.files(SAMPLE_CHARM) def test_file_one_file(self): with HTTMock(manifest_200): data = self.cs.files(SAMPLE_CHARM, filename='README.md') self.assertEqual( u'http://example.com/precise/mysql-1/archive/README.md', data) def test_file_one_file_reference(self): with HTTMock(manifest_200): data = self.cs.files( references.Reference.from_string(SAMPLE_CHARM), filename='README.md') self.assertEqual( u'http://example.com/precise/mysql-1/archive/README.md', data) def test_file_one_file_error(self): with HTTMock(manifest_200): with self.assertRaises(EntityNotFound): self.cs.files(SAMPLE_CHARM, filename='does.not.exist.md') def test_file_read_file(self): with HTTMock(manifest_200): with HTTMock(file_200): data = self.cs.files( SAMPLE_CHARM, filename='README.md', read_file=True) self.assertEqual('This is a file.', data) def test_file_read_file_reference(self): with HTTMock(manifest_200): with HTTMock(file_200): data = self.cs.files( references.Reference.from_string(SAMPLE_CHARM), filename='README.md', read_file=True) self.assertEqual('This is a file.', data) def test_file_read_file_error(self): with HTTMock(manifest_200): with HTTMock(file_404): with self.assertRaises(EntityNotFound): self.cs.files( SAMPLE_CHARM, filename='readme.md', read_file=True) def test_entityId(self): with HTTMock(id_200): charm_id = self.cs.entityId('bar') self.assertEqual('foo/bar', charm_id) def test_entityId_reference(self): with HTTMock(id_200): charm_id = self.cs.entityId( references.Reference.from_string('bar')) self.assertEqual('foo/bar', charm_id) def test_ids_no_charm(self): with HTTMock(id_404): with self.assertRaises(EntityNotFound): self.cs.entityId('baz') def test_search(self): with HTTMock(search_200): results = self.cs.search('foo') self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_list(self): with HTTMock(list_200): results = self.cs.list() self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_escaped(self): with HTTMock(search_200_escaped): results = self.cs.search('&foo') self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_400(self): with patch('theblues.charmstore.logging.error') as log_mocked: with HTTMock(search_400): with self.assertRaises(ServerError) as cm: self.cs.search('foo') self.assertEqual(400, cm.exception.args[0]) log_mocked.assert_called_with( 'Error during request: http://example.com/search?text=foo ' 'status code:(400) message: ' '{"Message": "invalid parameter: user", "Code": "bad request"}') def test_list_400(self): with patch('theblues.charmstore.logging.error') as log_mocked: with HTTMock(search_400): with self.assertRaises(ServerError) as cm: self.cs.list() self.assertEqual(400, cm.exception.args[0]) log_mocked.assert_called_with( 'Error during request: http://example.com/list ' 'status code:(400) message: ' '{"Message": "invalid parameter: user", "Code": "bad request"}') def test_search_limit(self): with HTTMock(search_limit_200): results = self.cs.search('foo', limit=1) self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_autocomplete(self): with HTTMock(search_autocomplete_200): results = self.cs.search( 'foo', autocomplete=True) self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_includes(self): with HTTMock(search_includes_200): results = self.cs.search( 'foo', includes=['archive-size', 'charm-metadata']) self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_list_includes(self): with HTTMock(list_includes_200): results = self.cs.list(includes=['archive-size', 'charm-metadata']) self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_type(self): with HTTMock(search_type_200): results = self.cs.search('foo', doc_type='bundle') self.assertEqual([{'Id': 'cs:bundle/mediawiki-single-7'}], results) def test_list_type(self): with HTTMock(list_type_200): results = self.cs.list(doc_type='bundle') self.assertEqual([{'Id': 'cs:bundle/mediawiki-single-7'}], results) def test_search_tags(self): with HTTMock(search_tags_200): results = self.cs.search('foo', tags='databases') self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_multiple_tags(self): with HTTMock(search_multiple_tags_200): results = self.cs.search('foo', tags=['databases', 'applications']) self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_series(self): with HTTMock(search_series_200): results = self.cs.search('foo', series='precise') self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_list_series(self): with HTTMock(list_series_200): results = self.cs.list(series='precise') self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_multiple_series(self): with HTTMock(search_multiple_series_200): results = self.cs.search('foo', series=['trusty', 'precise']) self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_owner(self): with HTTMock(search_owner_200): results = self.cs.search('', owner='hatch') self.assertEqual([{"Id": "cs:foo/bar-0"}], results) def test_list_owner(self): with HTTMock(search_owner_200): results = self.cs.list(owner='hatch') self.assertEqual([{"Id": "cs:foo/bar-0"}], results) def test_charm_icon_url(self): entity_id = 'mongodb' url = self.cs.charm_icon_url(entity_id) self.assertEqual('http://example.com/mongodb/icon.svg', url) def test_charm_icon_url_reference(self): entity_id = 'mongodb' url = self.cs.charm_icon_url( references.Reference.from_string(entity_id)) self.assertEqual('http://example.com/mongodb/icon.svg', url) def test_charm_icon_ok(self): entity_id = 'precise/mysql-1' with HTTMock(icon_200): icon = self.cs.charm_icon(entity_id) self.assertEqual('icon', icon) def test_charm_icon_ok_reference(self): entity_id = 'precise/mysql-1' with HTTMock(icon_200): icon = self.cs.charm_icon( references.Reference.from_string(entity_id)) self.assertEqual('icon', icon) def test_charm_icon_bad(self): entity_id = 'precise/mysql-1' with HTTMock(icon_404): with self.assertRaises(EntityNotFound): self.cs.charm_icon(entity_id) def test_bundle_visualization_url(self): entity_id = 'mongodb-cluster' url = self.cs.bundle_visualization_url(entity_id) self.assertEqual('http://example.com/mongodb-cluster/diagram.svg', url) def test_bundle_visualization_url_reference(self): entity_id = 'mongodb-cluster' url = self.cs.bundle_visualization_url( references.Reference.from_string(entity_id)) self.assertEqual('http://example.com/mongodb-cluster/diagram.svg', url) def test_bundle_visualization_ok(self): entity_id = 'mongodb-cluster' with HTTMock(diagram_200): diagram = self.cs.bundle_visualization(entity_id) self.assertEqual('diagram', diagram) def test_bundle_visualization_bad(self): entity_id = 'mongodb-cluster' with HTTMock(diagram_404): with self.assertRaises(EntityNotFound): self.cs.bundle_visualization(entity_id) def test_bundle_visualization_reference(self): entity_id = 'mongodb-cluster' with HTTMock(diagram_200): diagram = self.cs.bundle_visualization( references.Reference.from_string(entity_id)) self.assertEqual('diagram', diagram) def test_entity_readme_url(self): entity_id = 'mongodb-cluster' url = self.cs.entity_readme_url(entity_id) self.assertEqual('http://example.com/mongodb-cluster/readme', url) def test_entity_readme_url_reference(self): entity_id = 'mongodb-cluster' url = self.cs.entity_readme_url( references.Reference.from_string(entity_id)) self.assertEqual('http://example.com/mongodb-cluster/readme', url) def test_readme_not_found(self): # A 404 not found error is returned when the readme cannot be found. with HTTMock(readme_404): with self.assertRaises(EntityNotFound): self.cs.entity_readme_content('precise/mysql-1') def test_readme_content(self): with HTTMock(readme_200): content = self.cs.entity_readme_content('precise/mysql-1') self.assertEqual('This is the readme', content) def test_readme_content_reference(self): with HTTMock(readme_200): content = self.cs.entity_readme_content( references.Reference.from_string('precise/mysql-1')) self.assertEqual('This is the readme', content) def test_debug(self): with HTTMock(debug_200): debug_data = self.cs.debug() self.assertEqual('all clear', debug_data['status']) def test_fetch_macaroon_successful(self): with HTTMock(fetch_macaroon_200): results = self.cs.fetch_macaroon() self.assertEqual('{"mymacaroon": "something"}', results) def test_fetch_macaroon_not_found(self): with HTTMock(fetch_macaroon_404): with self.assertRaises(EntityNotFound) as cm: self.cs.fetch_macaroon() self.assertEqual(cm.exception.args, ('http://example.com/macaroon',)) def test_search_with_macaroon(self): with HTTMock(search_200_with_macaroon): self.cs.macaroons = "[macaroon1, macaroon2]" results = self.cs.search('foo') self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_list_with_macaroon(self): with HTTMock(search_200_with_macaroon): self.cs.macaroons = "[macaroon1, macaroon2]" results = self.cs.list() self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_timeout(self): with HTTMock(search_timeout): with self.assertRaises(ServerError) as cm: self.cs.search('foo') message = cm.exception.args[0] self.assertTrue('Request timed out' in message) def test_resource_url(self): entity_id = 'mongodb' url = self.cs.resource_url(entity_id, "myresource", "22") self.assertEqual('http://example.com/mongodb/resource/myresource/22', url)
# Copyright (c) 2015 Canonical Ltd. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ Charm utilities Simple wrapper around theblues charmstore client Api for the charmstore: https://github.com/juju/charmstore/blob/v4/docs/API.md """ from theblues.charmstore import CharmStore cs = CharmStore('https://api.jujucharms.com/v4')
class TestCharmStore(TestCase): def entities_response(self, url, request): self.assertEqual( url.geturl(), 'http://example.com/meta/' + 'any?include=id&id=wordpress&id=mysql') return { 'status_code': 200, 'content': b'{"wordpress":"wordpress","mysql":"mysql"}' } def setUp(self): self.cs = CharmStore('http://example.com') def test_init(self): self.assertEqual(self.cs.url, 'http://example.com') def test_entity(self): tests = [{ 'about': 'default includes', 'want_query': ('include=bundle-machine-count&include=bundle-metadata&' 'include=bundle-unit-count&include=charm-actions&' 'include=charm-config&include=charm-metadata&' 'include=common-info&include=extra-info&include=owner&' 'include=published&include=resources&' 'include=supported-series&include=terms&include=stats'), }, { 'about': 'custom includes', 'kwargs': { 'includes': ['foo', 'bar'] }, 'want_query': 'include=foo&include=bar&include=stats', }, { 'about': 'custom includes and no stats', 'kwargs': { 'includes': ['foo'], 'include_stats': False }, 'want_query': 'include=foo', }, { 'about': 'custom includes and get files', 'kwargs': { 'includes': [], 'get_files': True }, 'want_query': 'include=manifest&include=stats', }, { 'about': 'default includes, no stats, with files', 'kwargs': { 'get_files': True, 'include_stats': False }, 'want_query': ('include=bundle-machine-count&include=bundle-metadata&' 'include=bundle-unit-count&include=charm-actions&' 'include=charm-config&include=charm-metadata&' 'include=common-info&include=extra-info&include=owner&' 'include=published&include=resources&' 'include=supported-series&include=terms&include=manifest'), }] for test in tests: # TODO(frankban): use subtests here when switching to Python 3. logging.debug(test['about']) self.check_entity(test.get('kwargs', {}), test.get('want_query', '')) def check_entity(self, kwargs, want_query): @urlmatch(path=ID_PATH) def handler(url, request): self.assertEqual(request.method, 'GET') self.assertEqual(url.path, '/precise/mysql-1/meta/any') self.assertEqual(url.query, want_query) return {'status_code': 200, 'content': {'result': 'foo'}} with HTTMock(handler): data = self.cs.entity(SAMPLE_CHARM_ID, **kwargs) self.assertEqual(data, {'result': 'foo'}) def test_charm(self): with HTTMock(entity_200): data = self.cs.charm(SAMPLE_CHARM) self.assertEqual({'Meta': {'charm-metadata': {'exists': True}}}, data) def test_charm_full_id(self): with HTTMock(entity_200): data = self.cs.charm(SAMPLE_CHARM_ID) self.assertEqual({'Meta': {'charm-metadata': {'exists': True}}}, data) def test_charm_reference(self): with HTTMock(entity_200): data = self.cs.charm( references.Reference.from_string(SAMPLE_CHARM)) self.assertEqual({'Meta': {'charm-metadata': {'exists': True}}}, data) def test_entities(self): with HTTMock(self.entities_response): response = self.cs.entities(['wordpress', 'mysql']) self.assertEqual({ u'wordpress': u'wordpress', u'mysql': u'mysql' }, response) def test_entities_reference(self): with HTTMock(self.entities_response): response = self.cs.entities([ references.Reference.from_string('wordpress'), references.Reference.from_string('mysql') ]) self.assertEqual({ u'wordpress': u'wordpress', u'mysql': u'mysql' }, response) def test_entities_error(self): with HTTMock(entity_404): with self.assertRaises(EntityNotFound) as cm: self.cs.entities(['not-found']) self.assertEqual( 'http://example.com/meta/any?include=id&id=not-found', cm.exception.args[0]) def test_charm_error(self): with HTTMock(entity_404): with self.assertRaises(EntityNotFound): self.cs.charm(SAMPLE_CHARM) def test_charm_error_id(self): with HTTMock(entity_404): with self.assertRaises(EntityNotFound): self.cs.charm(SAMPLE_CHARM_ID) def test_charm_error_407(self): with HTTMock(entity_407): with self.assertRaises(EntityNotFound): self.cs.charm(SAMPLE_CHARM) def test_config(self): with HTTMock(config_200): data = self.cs.config(SAMPLE_CHARM) self.assertEqual({'exists': True}, data) def test_config_reference(self): with HTTMock(config_200): data = self.cs.config( references.Reference.from_string(SAMPLE_CHARM)) self.assertEqual({'exists': True}, data) def test_config_error(self): with HTTMock(config_404): with self.assertRaises(EntityNotFound): self.cs.config(SAMPLE_CHARM) def test_archive_url(self): with HTTMock(manifest_200): data = self.cs.archive_url(SAMPLE_CHARM) self.assertEqual(u'http://example.com/precise/mysql-1/archive', data) def test_archive_url_reference(self): with HTTMock(manifest_200): data = self.cs.archive_url( references.Reference.from_string(SAMPLE_CHARM)) self.assertEqual(u'http://example.com/precise/mysql-1/archive', data) def test_file_good(self): with HTTMock(manifest_200): data = self.cs.files(SAMPLE_CHARM) self.assertEqual( { u'icon.svg': u'http://example.com/precise/mysql-1/archive/icon.svg', u'README.md': u'http://example.com/precise/mysql-1/archive/README.md' }, data) def test_file_good_reference(self): with HTTMock(manifest_200): data = self.cs.files( references.Reference.from_string(SAMPLE_CHARM)) self.assertEqual( { u'icon.svg': u'http://example.com/precise/mysql-1/archive/icon.svg', u'README.md': u'http://example.com/precise/mysql-1/archive/README.md' }, data) def test_file_error(self): with HTTMock(manifest_404): with self.assertRaises(EntityNotFound): self.cs.files(SAMPLE_CHARM) def test_file_one_file(self): with HTTMock(manifest_200): data = self.cs.files(SAMPLE_CHARM, filename='README.md') self.assertEqual( u'http://example.com/precise/mysql-1/archive/README.md', data) def test_file_one_file_reference(self): with HTTMock(manifest_200): data = self.cs.files( references.Reference.from_string(SAMPLE_CHARM), filename='README.md') self.assertEqual( u'http://example.com/precise/mysql-1/archive/README.md', data) def test_file_one_file_error(self): with HTTMock(manifest_200): with self.assertRaises(EntityNotFound): self.cs.files(SAMPLE_CHARM, filename='does.not.exist.md') def test_file_read_file(self): with HTTMock(manifest_200): with HTTMock(file_200): data = self.cs.files(SAMPLE_CHARM, filename='README.md', read_file=True) self.assertEqual('This is a file.', data) def test_file_read_file_reference(self): with HTTMock(manifest_200): with HTTMock(file_200): data = self.cs.files( references.Reference.from_string(SAMPLE_CHARM), filename='README.md', read_file=True) self.assertEqual('This is a file.', data) def test_file_read_file_error(self): with HTTMock(manifest_200): with HTTMock(file_404): with self.assertRaises(EntityNotFound): self.cs.files(SAMPLE_CHARM, filename='readme.md', read_file=True) def test_entityId(self): with HTTMock(id_200): charm_id = self.cs.entityId('bar') self.assertEqual('foo/bar', charm_id) def test_entityId_reference(self): with HTTMock(id_200): charm_id = self.cs.entityId( references.Reference.from_string('bar')) self.assertEqual('foo/bar', charm_id) def test_ids_no_charm(self): with HTTMock(id_404): with self.assertRaises(EntityNotFound): self.cs.entityId('baz') def test_search(self): with HTTMock(search_200): results = self.cs.search('foo') self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_list(self): with HTTMock(list_200): results = self.cs.list() self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_escaped(self): with HTTMock(search_200_escaped): results = self.cs.search('&foo') self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_400(self): with patch('theblues.charmstore.logging.error') as log_mocked: with HTTMock(search_400): with self.assertRaises(ServerError) as cm: self.cs.search('foo') self.assertEqual(400, cm.exception.args[0]) log_mocked.assert_called_with( 'Error during request: http://example.com/search?text=foo ' 'status code:(400) message: ' '{"Message": "invalid parameter: user", "Code": "bad request"}') def test_list_400(self): with patch('theblues.charmstore.logging.error') as log_mocked: with HTTMock(search_400): with self.assertRaises(ServerError) as cm: self.cs.list() self.assertEqual(400, cm.exception.args[0]) log_mocked.assert_called_with( 'Error during request: http://example.com/list ' 'status code:(400) message: ' '{"Message": "invalid parameter: user", "Code": "bad request"}') def test_search_limit(self): with HTTMock(search_limit_200): results = self.cs.search('foo', limit=1) self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_autocomplete(self): with HTTMock(search_autocomplete_200): results = self.cs.search('foo', autocomplete=True) self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_includes(self): with HTTMock(search_includes_200): results = self.cs.search( 'foo', includes=['archive-size', 'charm-metadata']) self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_list_includes(self): with HTTMock(list_includes_200): results = self.cs.list(includes=['archive-size', 'charm-metadata']) self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_type(self): with HTTMock(search_type_200): results = self.cs.search('foo', doc_type='bundle') self.assertEqual([{'Id': 'cs:bundle/mediawiki-single-7'}], results) def test_list_type(self): with HTTMock(list_type_200): results = self.cs.list(doc_type='bundle') self.assertEqual([{'Id': 'cs:bundle/mediawiki-single-7'}], results) def test_search_tags(self): with HTTMock(search_tags_200): results = self.cs.search('foo', tags='databases') self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_multiple_tags(self): with HTTMock(search_multiple_tags_200): results = self.cs.search('foo', tags=['databases', 'applications']) self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_series(self): with HTTMock(search_series_200): results = self.cs.search('foo', series='precise') self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_list_series(self): with HTTMock(list_series_200): results = self.cs.list(series='precise') self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_multiple_series(self): with HTTMock(search_multiple_series_200): results = self.cs.search('foo', series=['trusty', 'precise']) self.assertEqual([{'Id': 'cs:foo/bar-0'}], results) def test_search_owner(self): with HTTMock(search_owner_200): results = self.cs.search('', owner='hatch') self.assertEqual([{"Id": "cs:foo/bar-0"}], results) def test_list_owner(self): with HTTMock(search_owner_200): results = self.cs.list(owner='hatch') self.assertEqual([{"Id": "cs:foo/bar-0"}], results) def test_charm_icon_url(self): entity_id = 'mongodb' url = self.cs.charm_icon_url(entity_id) self.assertEqual('http://example.com/mongodb/icon.svg', url) def test_charm_icon_url_reference(self): entity_id = 'mongodb' url = self.cs.charm_icon_url( references.Reference.from_string(entity_id)) self.assertEqual('http://example.com/mongodb/icon.svg', url) def test_charm_icon_ok(self): entity_id = 'precise/mysql-1' with HTTMock(icon_200): icon = self.cs.charm_icon(entity_id) self.assertEqual('icon', icon) def test_charm_icon_ok_reference(self): entity_id = 'precise/mysql-1' with HTTMock(icon_200): icon = self.cs.charm_icon( references.Reference.from_string(entity_id)) self.assertEqual('icon', icon) def test_charm_icon_bad(self): entity_id = 'precise/mysql-1' with HTTMock(icon_404): with self.assertRaises(EntityNotFound): self.cs.charm_icon(entity_id) def test_bundle_visualization_url(self): entity_id = 'mongodb-cluster' url = self.cs.bundle_visualization_url(entity_id) self.assertEqual('http://example.com/mongodb-cluster/diagram.svg', url) def test_bundle_visualization_url_reference(self): entity_id = 'mongodb-cluster' url = self.cs.bundle_visualization_url( references.Reference.from_string(entity_id)) self.assertEqual('http://example.com/mongodb-cluster/diagram.svg', url) def test_bundle_visualization_ok(self): entity_id = 'mongodb-cluster' with HTTMock(diagram_200): diagram = self.cs.bundle_visualization(entity_id) self.assertEqual('diagram', diagram) def test_bundle_visualization_bad(self): entity_id = 'mongodb-cluster' with HTTMock(diagram_404): with self.assertRaises(EntityNotFound): self.cs.bundle_visualization(entity_id) def test_bundle_visualization_reference(self): entity_id = 'mongodb-cluster' with HTTMock(diagram_200): diagram = self.cs.bundle_visualization( references.Reference.from_string(entity_id)) self.assertEqual('diagram', diagram) def test_entity_readme_url(self): entity_id = 'mongodb-cluster' url = self.cs.entity_readme_url(entity_id) self.assertEqual('http://example.com/mongodb-cluster/readme', url) def test_entity_readme_url_reference(self): entity_id = 'mongodb-cluster' url = self.cs.entity_readme_url( references.Reference.from_string(entity_id)) self.assertEqual('http://example.com/mongodb-cluster/readme', url) def test_readme_not_found(self): # A 404 not found error is returned when the readme cannot be found. with HTTMock(readme_404): with self.assertRaises(EntityNotFound): self.cs.entity_readme_content('precise/mysql-1') def test_readme_content(self): with HTTMock(readme_200): content = self.cs.entity_readme_content('precise/mysql-1') self.assertEqual('This is the readme', content) def test_readme_content_reference(self): with HTTMock(readme_200): content = self.cs.entity_readme_content( references.Reference.from_string('precise/mysql-1')) self.assertEqual('This is the readme', content) def test_debug(self): with HTTMock(debug_200): debug_data = self.cs.debug() self.assertEqual('all clear', debug_data['status']) def test_timeout(self): with HTTMock(search_timeout): with self.assertRaises(ServerError) as cm: self.cs.search('foo') message = cm.exception.args[0] self.assertTrue('Request timed out' in message) def test_resource_url(self): entity_id = 'mongodb' url = self.cs.resource_url(entity_id, "myresource", "22") self.assertEqual('http://example.com/mongodb/resource/myresource/22', url)
def __init__(self): self.cs = CharmStore('https://api.jujucharms.com/v4') self.charm_search = None self.result = None
class Query: def __init__(self): self.cs = CharmStore('https://api.jujucharms.com/v4') self.charm_search = None self.result = None @classmethod def is_fuzzy(cls, charm): glob_chars = set('~*') if any((c in glob_chars) for c in charm): return True return False @classmethod def valid_filter(cls, charm): fail_glob_chars = set('.?[]') if any((c in fail_glob_chars) for c in charm): return False return True def _save_charm_search(self, charm): """ Saves the charm search query, sanitizing any wildcards """ charm = charm.translate({ord(i): None for i in '~*'}) self.charm_search = charm def filter_non_name_matches(self, result): """ The search query doesnt return just results with charm_search in the name, so we filter that out """ self.result = [item for item in result if self.charm_search in item['Id']] def search(self, charm, promulgated=True): """ Searches for charm Eg: https://api.jujucharms.com/charmstore/v4/search?text=nova&autocomplete=1&limit=100 Only blessed ones: https://api.jujucharms.com/charmstore/v4/search?text=nova&autocomplete=1&limit=100&owner= Arguments: charm: name of service """ try: self._save_charm_search(charm) self.result = self.cs.search(charm, autocomplete=True, promulgated_only=promulgated, limit=25) except EntityNotFound as e: raise QueryError(e) def get(self, charm): """ Returns single entry for charm """ try: self._save_charm_search(charm) self.result = dict(Id=self.cs.entityId(charm)) except EntityNotFound as e: raise QueryError(e) def render(self): """ Renders charm results Example: juju search nova Trusty nova-cloud-controller nova-compute Precise nova-compute User contributed nova-compute-vmware """ if not self.result: raise QueryError( "Must search for a charm before attempting to render it.") if not isinstance(self.result, list): self.result = [self.result] series_map = { 'trusty': [], 'precise': [], 'namespaced': [], } self.filter_non_name_matches(self.result) for entity in self.result: _id = entity['Id'] item = _id if entity['Id'].startswith('cs:'): item = entity['Id'][3:] charm = self.cs.entity(item) try: meta = charm['Meta']['charm-metadata'] except KeyError: # Probably a bundle continue stat = charm['Meta']['stats'] dl = stat['ArchiveDownloadCount'] summary = meta['Summary'] if Query.is_fuzzy(item): series_map['namespaced'].append( (_id, dl, summary) ) elif 'trusty' in item: series_map['trusty'].append( (_id, dl, summary) ) elif 'precise' in item: series_map['precise'].append( (_id, dl, summary) ) valid_series = None for series in sorted(series_map.keys(), reverse=True): if len(series_map[series]) == 0: continue valid_series = series print(series.capitalize()) print("") for _id, dl, summary in series_map[series]: print(" {}".format(_id)) print("") if valid_series: item = series_map[valid_series][0] print("Example:") print("") print(" juju deploy {}".format(item[0])) print("") print("Get additional information:") print("") print(" juju info {}".format(item[0])) print("")