Merge lp:~frankban/juju-quickstart/jujubundlelib into lp:juju-quickstart

Proposed by Francesco Banconi
Status: Merged
Merged at revision: 125
Proposed branch: lp:~frankban/juju-quickstart/jujubundlelib
Merge into: lp:juju-quickstart
Diff against target: 908 lines (+27/-639)
13 files modified
quickstart/app.py (+1/-1)
quickstart/charmstore.py (+3/-3)
quickstart/jujutools.py (+5/-4)
quickstart/manage.py (+2/-1)
quickstart/models/bundles.py (+4/-3)
quickstart/models/references.py (+0/-215)
quickstart/tests/models/test_bundles.py (+2/-4)
quickstart/tests/models/test_references.py (+0/-400)
quickstart/tests/test_app.py (+2/-4)
quickstart/tests/test_charmstore.py (+2/-2)
quickstart/tests/test_jujutools.py (+1/-1)
quickstart/tests/test_manage.py (+1/-1)
tox.ini (+4/-0)
To merge this branch: bzr merge lp:~frankban/juju-quickstart/jujubundlelib
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+256906@code.launchpad.net

Description of the change

Introduce jujubundlelib dependency.

Use the Reference model defined there.

https://codereview.appspot.com/228460043/

To post a comment you must log in.
Revision history for this message
Francesco Banconi (frankban) wrote :

Reviewers: mp+256906_code.launchpad.net,

Message:
Please take a look.

Description:
Introduce jujubundlelib dependency.

Use the Reference model defined there.

https://code.launchpad.net/~frankban/juju-quickstart/jujubundlelib/+merge/256906

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/228460043/

Affected files (+29, -639 lines):
   A [revision details]
   M quickstart/app.py
   M quickstart/charmstore.py
   M quickstart/jujutools.py
   M quickstart/manage.py
   M quickstart/models/bundles.py
   D quickstart/models/references.py
   M quickstart/tests/models/test_bundles.py
   D quickstart/tests/models/test_references.py
   M quickstart/tests/test_app.py
   M quickstart/tests/test_charmstore.py
   M quickstart/tests/test_jujutools.py
   M quickstart/tests/test_manage.py
   M tox.ini

Revision history for this message
Madison Scott-Clary (makyo) wrote :

LGTM, thanks for the work - we'll need to update tox.ini with versions
as tasks shake out from jujubundlelib, but that's fine.

https://codereview.appspot.com/228460043/

Revision history for this message
Francesco Banconi (frankban) wrote :

On 2015/04/22 15:34:12, matthew.scott wrote:
> LGTM, thanks for the work - we'll need to update tox.ini with versions
as tasks
> shake out from jujubundlelib, but that's fine.

Indeed, I'll update the bundlelib version in follow up branches.
Thanks for the review Madison!

https://codereview.appspot.com/228460043/

Revision history for this message
Francesco Banconi (frankban) wrote :

*** Submitted:

Introduce jujubundlelib dependency.

Use the Reference model defined there.

R=matthew.scott
CC=
https://codereview.appspot.com/228460043

https://codereview.appspot.com/228460043/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'quickstart/app.py'
--- quickstart/app.py 2015-03-09 17:50:28 +0000
+++ quickstart/app.py 2015-04-21 10:32:19 +0000
@@ -411,7 +411,7 @@
411 Return a tuple including the following values:411 Return a tuple including the following values:
412 - charm_ref: the entity reference of the charm that will be used to412 - charm_ref: the entity reference of the charm that will be used to
413 deploy the service, as an instance of413 deploy the service, as an instance of
414 "quickstart.models.references.Reference";414 "jujubundlelib.references.Reference";
415 - machine: the machine where to deploy to (e.g. "0") or None if a new415 - machine: the machine where to deploy to (e.g. "0") or None if a new
416 machine must be created;416 machine must be created;
417 - service_data: the service info as returned by the mega-watcher for417 - service_data: the service info as returned by the mega-watcher for
418418
=== modified file 'quickstart/charmstore.py'
--- quickstart/charmstore.py 2015-03-10 10:18:58 +0000
+++ quickstart/charmstore.py 2015-04-21 10:32:19 +0000
@@ -59,7 +59,7 @@
59 """Retrieve the charm store contents for the given reference and path.59 """Retrieve the charm store contents for the given reference and path.
6060
61 The reference argument identifies a charm or bundle entity and must be an61 The reference argument identifies a charm or bundle entity and must be an
62 instance of "quickstart.models.references.Reference".62 instance of "jujubundlelib.references.Reference".
6363
64 For instance, to retrieve the hash of a charm reference, use the following:64 For instance, to retrieve the hash of a charm reference, use the following:
6565
@@ -101,7 +101,7 @@
101101
102 The bundle data is returned as a YAML decoded value.102 The bundle data is returned as a YAML decoded value.
103 The reference argument identifies a bundle entity and must be an instance103 The reference argument identifies a bundle entity and must be an instance
104 of "quickstart.models.references.Reference".104 of "jujubundlelib.references.Reference".
105105
106 Raise a ValueError if the returned content is not a valid YAML, or if the106 Raise a ValueError if the returned content is not a valid YAML, or if the
107 given reference does not represent a bundle.107 given reference does not represent a bundle.
@@ -119,7 +119,7 @@
119 The bundle data is returned as a YAML decoded value and represents the119 The bundle data is returned as a YAML decoded value and represents the
120 legacy bundle with a top level bundle name node.120 legacy bundle with a top level bundle name node.
121 The reference argument identifies a bundle entity and must be an instance121 The reference argument identifies a bundle entity and must be an instance
122 of "quickstart.models.references.Reference".122 of "jujubundlelib.references.Reference".
123123
124 Raise a ValueError if the returned content is not a valid YAML, or if the124 Raise a ValueError if the returned content is not a valid YAML, or if the
125 given reference does not represent a bundle.125 given reference does not represent a bundle.
126126
=== modified file 'quickstart/jujutools.py'
--- quickstart/jujutools.py 2015-02-26 11:02:57 +0000
+++ quickstart/jujutools.py 2015-04-21 10:32:19 +0000
@@ -23,11 +23,12 @@
2323
24import logging24import logging
2525
26from jujubundlelib import references
27
26from quickstart import (28from quickstart import (
27 serializers,29 serializers,
28 settings,30 settings,
29)31)
30from quickstart.models import references
3132
3233
33def get_api_url(34def get_api_url(
@@ -40,8 +41,8 @@
40 Optionally receive a prefix to be used in the path.41 Optionally receive a prefix to be used in the path.
4142
42 Optionally also receive the Juju GUI charm reference as an instance of43 Optionally also receive the Juju GUI charm reference as an instance of
43 "quickstart.models.references.Reference". If provided, the function checks44 "jujubundlelib.references.Reference". If provided, the function checks that
44 that the corresponding Juju GUI charm supports the new Juju API endpoint.45 the corresponding Juju GUI charm supports the new Juju API endpoint.
45 If not supported, the old endpoint is returned.46 If not supported, the old endpoint is returned.
4647
47 The environment UUID can be None, in which case the old-style API URL48 The environment UUID can be None, in which case the old-style API URL
@@ -104,7 +105,7 @@
104 Print (to stdout or to logs) info and warnings about the charm URL.105 Print (to stdout or to logs) info and warnings about the charm URL.
105106
106 Return the parsed charm reference object as an instance of107 Return the parsed charm reference object as an instance of
107 "quickstart.models.references.Reference".108 "jujubundlelib.references.Reference".
108 """109 """
109 print('charm URL: {}'.format(charm_url))110 print('charm URL: {}'.format(charm_url))
110 ref = references.Reference.from_fully_qualified_url(charm_url)111 ref = references.Reference.from_fully_qualified_url(charm_url)
111112
=== modified file 'quickstart/manage.py'
--- quickstart/manage.py 2015-03-09 18:53:52 +0000
+++ quickstart/manage.py 2015-04-21 10:32:19 +0000
@@ -28,6 +28,8 @@
28import sys28import sys
29import webbrowser29import webbrowser
3030
31from jujubundlelib import references
32
31import quickstart33import quickstart
32from quickstart import (34from quickstart import (
33 app,35 app,
@@ -45,7 +47,6 @@
45 bundles,47 bundles,
46 envs,48 envs,
47 jenv,49 jenv,
48 references,
49)50)
5051
5152
5253
=== modified file 'quickstart/models/bundles.py'
--- quickstart/models/bundles.py 2015-03-10 13:13:42 +0000
+++ quickstart/models/bundles.py 2015-04-21 10:32:19 +0000
@@ -22,7 +22,7 @@
2222
23Published bundles are identified by a charm store id and by the corresponding23Published bundles are identified by a charm store id and by the corresponding
24URL in jujucharms.com, just like regular charms. The reference object in24URL in jujucharms.com, just like regular charms. The reference object in
25"quickstart.models.references.Reference" can be used to identify a bundle.25"jujubundlelib.references.Reference" can be used to identify a bundle.
2626
27In this module, the Bundle class represents a bundle that may or may not have27In this module, the Bundle class represents a bundle that may or may not have
28a specific reference id. For instance, a reference is not set on a bundle if28a specific reference id. For instance, a reference is not set on a bundle if
@@ -46,13 +46,14 @@
46import os46import os
47import re47import re
4848
49from jujubundlelib import references
50
49from quickstart import (51from quickstart import (
50 charmstore,52 charmstore,
51 netutils,53 netutils,
52 serializers,54 serializers,
53 settings,55 settings,
54)56)
55from quickstart.models import references
5657
5758
58class Bundle(object):59class Bundle(object):
@@ -63,7 +64,7 @@
6364
64 The data argument is the bundle YAML decoded content.65 The data argument is the bundle YAML decoded content.
65 An optional entity reference can be provided as an instance of66 An optional entity reference can be provided as an instance of
66 "quickstart.models.references.Reference".67 "jujubundlelib.references.Reference".
67 """68 """
68 self.data = data69 self.data = data
69 self.reference = reference70 self.reference = reference
7071
=== removed file 'quickstart/models/references.py'
--- quickstart/models/references.py 2015-03-06 17:55:40 +0000
+++ quickstart/models/references.py 1970-01-01 00:00:00 +0000
@@ -1,215 +0,0 @@
1# This file is part of the Juju Quickstart Plugin, which lets users set up a
2# Juju environment in very few steps (https://launchpad.net/juju-quickstart).
3# Copyright (C) 2013 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it under
6# the terms of the GNU Affero General Public License version 3, as published by
7# the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but WITHOUT
10# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
11# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12# Affero General Public License for more details.
13#
14# You should have received a copy of the GNU Affero General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17"""Juju Quickstart charm and bundle references management."""
18
19from __future__ import unicode_literals
20
21import re
22
23from quickstart import settings
24
25
26# The following regular expressions are the same used in juju-core: see
27# http://bazaar.launchpad.net/~go-bot/juju-core/trunk/view/head:/charm/url.go.
28USER_PATTERN = r'[a-z0-9][a-zA-Z0-9+.-]+'
29SERIES_PATTERN = r'[a-z]+(?:[a-z-]+[a-z])?'
30NAME_PATTERN = r'[a-z][a-z0-9]*(?:-[a-z0-9]*[a-z][a-z0-9]*)*'
31
32# Define the callables used to check if entity reference components are valid.
33_valid_user = re.compile(r'^{}$'.format(USER_PATTERN)).match
34_valid_series = re.compile(r'^{}$'.format(SERIES_PATTERN)).match
35_valid_name = re.compile(r'^{}$'.format(NAME_PATTERN)).match
36
37# Compile the regular expression used to parse new jujucharms entity URLs.
38_jujucharms_url_expression = re.compile(r"""
39 ^ # Beginning of the line.
40 (?:
41 (?:{jujucharms})? # Optional jujucharms.com URL.
42 |
43 /? # Optional leading slash.
44 )?
45 (?:u/({user_pattern})/)? # Optional user name.
46 ({name_pattern}) # Bundle name.
47 (?:/({series_pattern}))? # Optional series.
48 (?:/(\d+))? # Optional bundle revision number.
49 /? # Optional trailing slash.
50 $ # End of the line.
51""".format(
52 jujucharms=settings.JUJUCHARMS_URL,
53 name_pattern=NAME_PATTERN,
54 series_pattern=SERIES_PATTERN,
55 user_pattern=USER_PATTERN,
56), re.VERBOSE)
57
58
59class Reference(object):
60 """Represent a charm or bundle URL reference."""
61
62 def __init__(self, schema, user, series, name, revision):
63 """Initialize the reference. Receives the URL fragments."""
64 self.schema = schema
65 self.user = user
66 self.series = series
67 self.name = name
68 if revision is not None:
69 revision = int(revision)
70 self.revision = revision
71 # XXX frankban 2015-02-26: remove the following attribute when
72 # switching to the new bundle format, and when we have a better way
73 # to increase bundle deployments count.
74 self.charmworld_id = None
75
76 @classmethod
77 def from_fully_qualified_url(cls, url):
78 """Given an entity URL as a string, create and return a Reference.
79
80 Fully qualified URLs represent the regular entity reference
81 representation in Juju, e.g.: "cs:`~who/vivid/django-42" or
82 "local:bundle/wordpress-0".
83
84 Raise a ValueError if the provided value is not a valid and fully
85 qualified URL, also including the schema and the revision.
86 """
87 return cls(*_parse_fully_qualified_url(url))
88
89 @classmethod
90 def from_jujucharms_url(cls, url):
91 """Create and return a Reference from the given jujucharms.com URL.
92
93 These are the preferred way to refer to a charm or bundle in Juju
94 Quickstart. They basically look like the URL paths in jujucharms.com,
95 e.g. "u/who/django", "mediawiki/42" or just "mediawiki". The full HTTP
96 URL can be also provided, for instance "https://jujucharms.com/django".
97
98 Raise a ValueError if the provided URL is not valid.
99 """
100 match = _jujucharms_url_expression.match(url)
101 if match is None:
102 msg = 'invalid bundle URL: {}'.format(url)
103 raise ValueError(msg.encode('utf-8'))
104 user, name, series, revision = match.groups()
105 return cls('cs', user, series or 'bundle', name, revision)
106
107 def __str__(self):
108 """The string representation of a reference is its URL string."""
109 return self.__unicode__().encode('utf-8')
110
111 def __unicode__(self):
112 """The unicode representation of a reference is its URL string."""
113 return self.id()
114
115 def __repr__(self):
116 return b'<Reference: {}>'.format(bytes(self))
117
118 def __eq__(self, other):
119 """Two refs are equal if they have the same string representation."""
120 return isinstance(other, self.__class__) and self.id() == other.id()
121
122 def path(self):
123 """Return the reference as a string without the schema."""
124 user_part = '~{}/'.format(self.user) if self.user else ''
125 revision_part = ''
126 if self.revision is not None:
127 revision_part = '-{}'.format(self.revision)
128 return '{}{}/{}{}'.format(
129 user_part, self.series, self.name, revision_part)
130
131 def id(self):
132 """Return the reference URL as a string."""
133 return '{}:{}'.format(self.schema, self.path())
134
135 def jujucharms_id(self):
136 """Return the identifier of this reference in jujucharms.com."""
137 user_part = 'u/{}/'.format(self.user) if self.user else ''
138 series_part = '' if self.is_bundle() else '/{}'.format(self.series)
139 revision_part = ''
140 if self.revision is not None:
141 revision_part = '/{}'.format(self.revision)
142 return '{}{}{}{}'.format(
143 user_part, self.name, series_part, revision_part)
144
145 def jujucharms_url(self):
146 """Return the URL where this entity lives in jujucharms.com."""
147 return settings.JUJUCHARMS_URL + self.jujucharms_id()
148
149 def is_bundle(self):
150 """Report whether this reference refers to a bundle entity."""
151 return self.series == 'bundle'
152
153 def is_local(self):
154 """Return True if this refers to a local entity, False otherwise."""
155 return self.schema == 'local'
156
157
158def _parse_fully_qualified_url(url):
159 """Parse the given charm or bundle URL, provided as a string.
160
161 Return a tuple containing the entity reference fragments: schema, user,
162 series, name and revision. Each fragment is a string except revision (int).
163
164 Raise a ValueError with a descriptive message if the given URL is not a
165 valid and fully qualified entity URL.
166 """
167 # Retrieve the schema.
168 try:
169 schema, remaining = url.split(':', 1)
170 except ValueError:
171 msg = 'URL has no schema: {}'.format(url)
172 raise ValueError(msg.encode('utf-8'))
173 if schema not in ('cs', 'local'):
174 msg = 'URL has invalid schema: {}'.format(schema)
175 raise ValueError(msg.encode('utf-8'))
176 # Retrieve the optional user, the series, name and revision.
177 parts = remaining.split('/')
178 parts_length = len(parts)
179 if parts_length == 3:
180 user, series, name_revision = parts
181 if not user.startswith('~'):
182 msg = 'URL has invalid user name form: {}'.format(user)
183 raise ValueError(msg.encode('utf-8'))
184 user = user[1:]
185 if not _valid_user(user):
186 msg = 'URL has invalid user name: {}'.format(user)
187 raise ValueError(msg.encode('utf-8'))
188 if schema == 'local':
189 msg = 'local entity URL with user name: {}'.format(url)
190 raise ValueError(msg.encode('utf-8'))
191 elif parts_length == 2:
192 user = ''
193 series, name_revision = parts
194 else:
195 msg = 'URL has invalid form: {}'.format(url)
196 raise ValueError(msg.encode('utf-8'))
197 # Validate the series.
198 if not _valid_series(series):
199 msg = 'URL has invalid series: {}'.format(series)
200 raise ValueError(msg.encode('utf-8'))
201 # Validate name and revision.
202 try:
203 name, revision = name_revision.rsplit('-', 1)
204 except ValueError:
205 msg = 'URL has no revision: {}'.format(url)
206 raise ValueError(msg.encode('utf-8'))
207 if not _valid_name(name):
208 msg = 'URL has invalid name: {}'.format(name)
209 raise ValueError(msg.encode('utf-8'))
210 try:
211 revision = int(revision)
212 except ValueError:
213 msg = 'URL has invalid revision: {}'.format(revision)
214 raise ValueError(msg.encode('utf-8'))
215 return schema, user, series, name, revision
2160
=== modified file 'quickstart/tests/models/test_bundles.py'
--- quickstart/tests/models/test_bundles.py 2015-03-10 13:13:42 +0000
+++ quickstart/tests/models/test_bundles.py 2015-04-21 10:32:19 +0000
@@ -21,6 +21,7 @@
21import json21import json
22import unittest22import unittest
2323
24from jujubundlelib import references
24import mock25import mock
25import yaml26import yaml
2627
@@ -28,10 +29,7 @@
28 netutils,29 netutils,
29 settings,30 settings,
30)31)
31from quickstart.models import (32from quickstart.models import bundles
32 bundles,
33 references,
34)
35from quickstart.tests import helpers33from quickstart.tests import helpers
3634
3735
3836
=== removed file 'quickstart/tests/models/test_references.py'
--- quickstart/tests/models/test_references.py 2015-03-09 17:50:28 +0000
+++ quickstart/tests/models/test_references.py 1970-01-01 00:00:00 +0000
@@ -1,400 +0,0 @@
1# This file is part of the Juju Quickstart Plugin, which lets users set up a
2# Juju environment in very few steps (https://launchpad.net/juju-quickstart).
3# Copyright (C) 2013 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it under
6# the terms of the GNU Affero General Public License version 3, as published by
7# the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but WITHOUT
10# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
11# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12# Affero General Public License for more details.
13#
14# You should have received a copy of the GNU Affero General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17"""Tests for the Juju Quickstart charm and bundle references management."""
18
19from __future__ import unicode_literals
20
21import unittest
22
23from quickstart import settings
24from quickstart.models import references
25from quickstart.tests import helpers
26
27
28def make_reference(
29 schema='cs', user='myuser', series='precise', name='juju-gui',
30 revision=42):
31 """Create and return a Reference instance."""
32 return references.Reference(schema, user, series, name, revision)
33
34
35class TestReference(unittest.TestCase):
36
37 def test_attributes(self):
38 # All reference attributes are correctly stored.
39 ref = make_reference()
40 self.assertEqual('cs', ref.schema)
41 self.assertEqual('myuser', ref.user)
42 self.assertEqual('precise', ref.series)
43 self.assertEqual('juju-gui', ref.name)
44 self.assertEqual(42, ref.revision)
45
46 def test_revision_as_string(self):
47 # The reference revision is converted to an int.
48 ref = make_reference(revision='47')
49 self.assertEqual(47, ref.revision)
50
51 def test_string(self):
52 # The string representation of a reference is its URL.
53 tests = (
54 (make_reference(),
55 'cs:~myuser/precise/juju-gui-42'),
56 (make_reference(schema='local'),
57 'local:~myuser/precise/juju-gui-42'),
58 (make_reference(user=''),
59 'cs:precise/juju-gui-42'),
60 (make_reference(user='dalek', revision=None, series='bundle'),
61 'cs:~dalek/bundle/juju-gui'),
62 (make_reference(name='django', series='vivid', revision=0),
63 'cs:~myuser/vivid/django-0'),
64 (make_reference(user='', revision=None),
65 'cs:precise/juju-gui'),
66 )
67 for ref, expected_value in tests:
68 self.assertEqual(expected_value, bytes(ref))
69
70 def test_repr(self):
71 # A reference is correctly represented.
72 tests = (
73 (make_reference(),
74 '<Reference: cs:~myuser/precise/juju-gui-42>'),
75 (make_reference(schema='local'),
76 '<Reference: local:~myuser/precise/juju-gui-42>'),
77 (make_reference(user=''),
78 '<Reference: cs:precise/juju-gui-42>'),
79 (make_reference(user='dalek', revision=None, series='bundle'),
80 '<Reference: cs:~dalek/bundle/juju-gui>'),
81 (make_reference(name='django', series='vivid', revision=0),
82 '<Reference: cs:~myuser/vivid/django-0>'),
83 (make_reference(user='', revision=None),
84 '<Reference: cs:precise/juju-gui>'),
85 )
86 for ref, expected_value in tests:
87 self.assertEqual(expected_value, repr(ref))
88
89 def test_path(self):
90 # The reference path is properly returned as a URL string without the
91 # schema.
92 tests = (
93 (make_reference(),
94 '~myuser/precise/juju-gui-42'),
95 (make_reference(schema='local'),
96 '~myuser/precise/juju-gui-42'),
97 (make_reference(user=''),
98 'precise/juju-gui-42'),
99 (make_reference(user='dalek', revision=None, series='bundle'),
100 '~dalek/bundle/juju-gui'),
101 (make_reference(name='django', series='vivid', revision=0),
102 '~myuser/vivid/django-0'),
103 (make_reference(user='', revision=None),
104 'precise/juju-gui'),
105 )
106 for ref, expected_value in tests:
107 self.assertEqual(expected_value, ref.path())
108
109 def test_id(self):
110 # The reference id is correctly returned.
111 tests = (
112 (make_reference(),
113 'cs:~myuser/precise/juju-gui-42'),
114 (make_reference(schema='local'),
115 'local:~myuser/precise/juju-gui-42'),
116 (make_reference(user=''),
117 'cs:precise/juju-gui-42'),
118 (make_reference(user='dalek', revision=None, series='bundle'),
119 'cs:~dalek/bundle/juju-gui'),
120 (make_reference(name='django', series='vivid', revision=0),
121 'cs:~myuser/vivid/django-0'),
122 (make_reference(user='', revision=None),
123 'cs:precise/juju-gui'),
124 )
125 for ref, expected_value in tests:
126 self.assertEqual(expected_value, ref.id())
127
128 def test_jujucharms_id(self):
129 # It is possible to return the reference identifier in jujucharms.com.
130 tests = (
131 (make_reference(),
132 'u/myuser/juju-gui/precise/42'),
133 (make_reference(schema='local'),
134 'u/myuser/juju-gui/precise/42'),
135 (make_reference(user=''),
136 'juju-gui/precise/42'),
137 (make_reference(user='dalek', revision=None, series='bundle'),
138 'u/dalek/juju-gui'),
139 (make_reference(name='django', series='vivid', revision=0),
140 'u/myuser/django/vivid/0'),
141 (make_reference(user='', revision=None),
142 'juju-gui/precise'),
143 (make_reference(user='', series='bundle', revision=None),
144 'juju-gui'),
145 )
146 for ref, expected_value in tests:
147 self.assertEqual(expected_value, ref.jujucharms_id())
148
149 def test_jujucharms_url(self):
150 # The corresponding charm or bundle page in jujucharms.com is correctly
151 # returned.
152 tests = (
153 (make_reference(),
154 'u/myuser/juju-gui/precise/42'),
155 (make_reference(schema='local'),
156 'u/myuser/juju-gui/precise/42'),
157 (make_reference(user=''),
158 'juju-gui/precise/42'),
159 (make_reference(user='dalek', revision=None, series='bundle'),
160 'u/dalek/juju-gui'),
161 (make_reference(name='django', series='vivid', revision=0),
162 'u/myuser/django/vivid/0'),
163 (make_reference(user='', revision=None),
164 'juju-gui/precise'),
165 (make_reference(user='', series='bundle', revision=None),
166 'juju-gui'),
167 )
168 for ref, expected_value in tests:
169 expected_url = settings.JUJUCHARMS_URL + expected_value
170 self.assertEqual(expected_url, ref.jujucharms_url())
171
172 def test_charm_entity(self):
173 # The is_bundle method returns False for charm references.
174 ref = make_reference(series='vivid')
175 self.assertFalse(ref.is_bundle())
176
177 def test_bundle_entity(self):
178 # The is_bundle method returns True for bundle references.
179 ref = make_reference(series='bundle')
180 self.assertTrue(ref.is_bundle())
181
182 def test_charm_store_entity(self):
183 # The is_local method returns False for charm store references.
184 ref = make_reference(schema='cs')
185 self.assertFalse(ref.is_local())
186
187 def test_local_entity(self):
188 # The is_local method returns True for local references.
189 ref = make_reference(schema='local')
190 self.assertTrue(ref.is_local())
191
192 def test_equality(self):
193 # Two references are equal if they have the same URL.
194 self.assertEqual(make_reference(), make_reference())
195 self.assertEqual(make_reference(user=''), make_reference(user=''))
196 self.assertEqual(
197 make_reference(revision=None), make_reference(revision=None))
198
199 def test_equality_different_references(self):
200 # Two references with different attributes are not equal.
201 tests = (
202 (make_reference(schema='cs'),
203 make_reference(schema='local')),
204 (make_reference(user=''),
205 make_reference(user='who')),
206 (make_reference(series='trusty'),
207 make_reference(series='vivid')),
208 (make_reference(name='django'),
209 make_reference(name='rails')),
210 (make_reference(revision=0),
211 make_reference(revision=1)),
212 (make_reference(revision=None),
213 make_reference(revision=42)),
214 )
215 for ref1, ref2 in tests:
216 self.assertNotEqual(ref1, ref2)
217
218 def test_equality_different_types(self):
219 # A reference never equals a non-reference object.
220 self.assertNotEqual(make_reference(), 42)
221 self.assertNotEqual(make_reference(), True)
222 self.assertNotEqual(make_reference(), 'oranges')
223
224 def test_charmworld_id(self):
225 # By default, the reference id in charmworld is set to None.
226 # XXX frankban 2015-02-26: remove this test once we get rid of the
227 # charmworld id concept.
228 ref = make_reference()
229 self.assertIsNone(ref.charmworld_id)
230
231
232class TestReferenceFromFullyQualifiedUrl(
233 helpers.ValueErrorTestsMixin, unittest.TestCase):
234
235 def test_no_schema_error(self):
236 # A ValueError is raised if the URL schema is missing.
237 expected_error = 'URL has no schema: precise/juju-gui'
238 with self.assert_value_error(expected_error):
239 references.Reference.from_fully_qualified_url('precise/juju-gui')
240
241 def test_invalid_schema_error(self):
242 # A ValueError is raised if the URL schema is not valid.
243 expected_error = 'URL has invalid schema: http'
244 with self.assert_value_error(expected_error):
245 references.Reference.from_fully_qualified_url(
246 'http:precise/juju-gui')
247
248 def test_invalid_user_form_error(self):
249 # A ValueError is raised if the user form is not valid.
250 expected_error = 'URL has invalid user name form: jean-luc'
251 with self.assert_value_error(expected_error):
252 references.Reference.from_fully_qualified_url(
253 'cs:jean-luc/precise/juju-gui')
254
255 def test_invalid_user_name_error(self):
256 # A ValueError is raised if the user name is not valid.
257 expected_error = 'URL has invalid user name: jean:luc'
258 with self.assert_value_error(expected_error):
259 references.Reference.from_fully_qualified_url(
260 'cs:~jean:luc/precise/juju-gui')
261
262 def test_local_user_name_error(self):
263 # A ValueError is raised if a user is specified on a local entity.
264 expected_error = (
265 'local entity URL with user name: '
266 'local:~jean-luc/precise/juju-gui')
267 with self.assert_value_error(expected_error):
268 references.Reference.from_fully_qualified_url(
269 'local:~jean-luc/precise/juju-gui')
270
271 def test_invalid_form_error(self):
272 # A ValueError is raised if the URL is not valid.
273 expected_error = 'URL has invalid form: cs:~user/series/name/what-?'
274 with self.assert_value_error(expected_error):
275 references.Reference.from_fully_qualified_url(
276 'cs:~user/series/name/what-?')
277
278 def test_invalid_series_error(self):
279 # A ValueError is raised if the series is not valid.
280 expected_error = 'URL has invalid series: boo!'
281 with self.assert_value_error(expected_error):
282 references.Reference.from_fully_qualified_url(
283 'cs:boo!/juju-gui-42')
284
285 def test_no_revision_error(self):
286 # A ValueError is raised if the entity revision is missing.
287 expected_error = 'URL has no revision: cs:series/name'
288 with self.assert_value_error(expected_error):
289 references.Reference.from_fully_qualified_url('cs:series/name')
290
291 def test_invalid_revision_error(self):
292 # A ValueError is raised if the charm or bundle revision is not valid.
293 expected_error = 'URL has invalid revision: revision'
294 with self.assert_value_error(expected_error):
295 references.Reference.from_fully_qualified_url(
296 'cs:series/name-revision')
297
298 def test_invalid_name_error(self):
299 # A ValueError is raised if the entity name is not valid.
300 expected_error = 'URL has invalid name: not:valid'
301 with self.assert_value_error(expected_error):
302 references.Reference.from_fully_qualified_url(
303 'cs:precise/not:valid-42')
304
305 def test_success(self):
306 # References are correctly instantiated by parsing the fully qualified
307 # URL.
308 tests = (
309 ('cs:~myuser/precise/juju-gui-42',
310 make_reference()),
311 ('cs:trusty/juju-gui-42',
312 make_reference(user='', series='trusty')),
313 ('local:precise/juju-gui-42',
314 make_reference(schema='local', user='')),
315 )
316 for url, expected_ref in tests:
317 ref = references.Reference.from_fully_qualified_url(url)
318 self.assertEqual(expected_ref, ref)
319
320
321class TestReferenceFromJujucharmsUrl(
322 helpers.ValueErrorTestsMixin, unittest.TestCase):
323
324 def test_invalid_form(self):
325 # A ValueError is raised if the URL is not valid.
326 expected_error = 'invalid bundle URL: bad wolf'
327 with self.assert_value_error(expected_error):
328 references.Reference.from_jujucharms_url('bad wolf')
329
330 def test_success(self):
331 # A reference is correctly created from a jujucharms.com identifier or
332 # complete URL.
333 tests = (
334 # Check with both user and revision.
335 ('u/myuser/mediawiki/42',
336 make_reference(series='bundle', name='mediawiki')),
337 ('/u/myuser/mediawiki/42',
338 make_reference(series='bundle', name='mediawiki')),
339 ('u/myuser/django-scalable/42/',
340 make_reference(series='bundle', name='django-scalable')),
341 ('{}u/myuser/mediawiki/42'.format(settings.JUJUCHARMS_URL),
342 make_reference(series='bundle', name='mediawiki')),
343 ('{}u/myuser/mediawiki/42/'.format(settings.JUJUCHARMS_URL),
344 make_reference(series='bundle', name='mediawiki')),
345
346 # Check without revision.
347 ('u/myuser/mediawiki',
348 make_reference(series='bundle', name='mediawiki', revision=None)),
349 ('/u/myuser/wordpress',
350 make_reference(series='bundle', name='wordpress', revision=None)),
351 ('u/myuser/mediawiki/',
352 make_reference(series='bundle', name='mediawiki', revision=None)),
353 ('{}u/myuser/django'.format(settings.JUJUCHARMS_URL),
354 make_reference(series='bundle', name='django', revision=None)),
355 ('{}u/myuser/mediawiki/'.format(settings.JUJUCHARMS_URL),
356 make_reference(series='bundle', name='mediawiki', revision=None)),
357
358 # Check without the user.
359 ('rails-single/42',
360 make_reference(user='', series='bundle', name='rails-single')),
361 ('/mediawiki/42',
362 make_reference(user='', series='bundle', name='mediawiki')),
363 ('rails-scalable/42/',
364 make_reference(user='', series='bundle', name='rails-scalable')),
365 ('{}mediawiki/42'.format(settings.JUJUCHARMS_URL),
366 make_reference(user='', series='bundle', name='mediawiki')),
367 ('{}django/42/'.format(settings.JUJUCHARMS_URL),
368 make_reference(user='', series='bundle', name='django')),
369
370 # Check without user and revision.
371 ('mediawiki',
372 make_reference(user='', series='bundle', name='mediawiki',
373 revision=None)),
374 ('/wordpress',
375 make_reference(user='', series='bundle', name='wordpress',
376 revision=None)),
377 ('mediawiki/',
378 make_reference(user='', series='bundle', name='mediawiki',
379 revision=None)),
380 ('{}django'.format(settings.JUJUCHARMS_URL),
381 make_reference(user='', series='bundle', name='django',
382 revision=None)),
383 ('{}mediawiki/'.format(settings.JUJUCHARMS_URL),
384 make_reference(user='', series='bundle', name='mediawiki',
385 revision=None)),
386
387 # Check charm entities.
388 ('mediawiki/trusty/0',
389 make_reference(user='', series='trusty', name='mediawiki',
390 revision=0)),
391 ('/wordpress/precise',
392 make_reference(user='', series='precise', name='wordpress',
393 revision=None)),
394 ('u/who/rails/vivid',
395 make_reference(user='who', series='vivid', name='rails',
396 revision=None)),
397 )
398 for url, expected_ref in tests:
399 ref = references.Reference.from_jujucharms_url(url)
400 self.assertEqual(expected_ref, ref)
4010
=== modified file 'quickstart/tests/test_app.py'
--- quickstart/tests/test_app.py 2015-03-10 11:08:00 +0000
+++ quickstart/tests/test_app.py 2015-04-21 10:32:19 +0000
@@ -23,6 +23,7 @@
23import os23import os
24import unittest24import unittest
2525
26from jujubundlelib import references
26import jujuclient27import jujuclient
27import mock28import mock
28import yaml29import yaml
@@ -32,10 +33,7 @@
32 platform_support,33 platform_support,
33 settings,34 settings,
34)35)
35from quickstart.models import (36from quickstart.models import bundles
36 bundles,
37 references,
38)
39from quickstart.tests import helpers37from quickstart.tests import helpers
4038
4139
4240
=== modified file 'quickstart/tests/test_charmstore.py'
--- quickstart/tests/test_charmstore.py 2015-03-10 10:18:58 +0000
+++ quickstart/tests/test_charmstore.py 2015-04-21 10:32:19 +0000
@@ -18,16 +18,16 @@
1818
19from __future__ import unicode_literals19from __future__ import unicode_literals
2020
21import json
21import unittest22import unittest
2223
23import json24from jujubundlelib import references
2425
25from quickstart import (26from quickstart import (
26 charmstore,27 charmstore,
27 netutils,28 netutils,
28 settings,29 settings,
29)30)
30from quickstart.models import references
31from quickstart.tests import helpers31from quickstart.tests import helpers
3232
3333
3434
=== modified file 'quickstart/tests/test_jujutools.py'
--- quickstart/tests/test_jujutools.py 2015-02-26 18:57:25 +0000
+++ quickstart/tests/test_jujutools.py 2015-04-21 10:32:19 +0000
@@ -20,11 +20,11 @@
2020
21import unittest21import unittest
2222
23from jujubundlelib import references
23import mock24import mock
24import yaml25import yaml
2526
26from quickstart import jujutools27from quickstart import jujutools
27from quickstart.models import references
28from quickstart.tests import helpers28from quickstart.tests import helpers
2929
3030
3131
=== modified file 'quickstart/tests/test_manage.py'
--- quickstart/tests/test_manage.py 2015-02-26 19:12:17 +0000
+++ quickstart/tests/test_manage.py 2015-04-21 10:32:19 +0000
@@ -26,6 +26,7 @@
26import StringIO as io26import StringIO as io
27import unittest27import unittest
2828
29from jujubundlelib import references
29import mock30import mock
30import yaml31import yaml
3132
@@ -42,7 +43,6 @@
42 bundles,43 bundles,
43 envs,44 envs,
44 jenv,45 jenv,
45 references,
46)46)
47from quickstart.tests import helpers47from quickstart.tests import helpers
4848
4949
=== modified file 'tox.ini'
--- tox.ini 2015-02-27 10:30:20 +0000
+++ tox.ini 2015-04-21 10:32:19 +0000
@@ -72,6 +72,7 @@
72 # See https://launchpad.net/~juju/+archive/ubuntu/stable.72 # See https://launchpad.net/~juju/+archive/ubuntu/stable.
73 websocket-client==0.18.073 websocket-client==0.18.0
74 jujuclient==0.50.174 jujuclient==0.50.1
75 jujubundlelib==0.1.1
75 urwid==1.2.176 urwid==1.2.1
76 # The distribution PyYAML requirement is used in this case.77 # The distribution PyYAML requirement is used in this case.
7778
@@ -81,6 +82,7 @@
81 # Ubuntu 14.04 (trusty) distro dependencies.82 # Ubuntu 14.04 (trusty) distro dependencies.
82 websocket-client==0.12.083 websocket-client==0.12.0
83 jujuclient==0.17.584 jujuclient==0.17.5
85 jujubundlelib==0.1.1
84 PyYAML==3.1086 PyYAML==3.10
85 urwid==1.1.187 urwid==1.1.1
8688
@@ -90,6 +92,7 @@
90 # Ubuntu 14.10 (utopic) distro dependencies.92 # Ubuntu 14.10 (utopic) distro dependencies.
91 websocket-client==0.12.093 websocket-client==0.12.0
92 jujuclient==0.17.594 jujuclient==0.17.5
95 jujubundlelib==0.1.1
93 PyYAML==3.1196 PyYAML==3.11
94 urwid==1.2.197 urwid==1.2.1
9598
@@ -99,6 +102,7 @@
99 # Ubuntu 15.04 (vivid) distro dependencies.102 # Ubuntu 15.04 (vivid) distro dependencies.
100 websocket-client==0.18.0103 websocket-client==0.18.0
101 jujuclient==0.18.5104 jujuclient==0.18.5
105 jujubundlelib==0.1.1
102 PyYAML==3.11106 PyYAML==3.11
103 urwid==1.2.1107 urwid==1.2.1
104108

Subscribers

People subscribed via source and target branches