Merge lp:~clint-fewbar/pyjuju/namespace-from-env into lp:pyjuju

Proposed by Clint Byrum
Status: Work in progress
Proposed branch: lp:~clint-fewbar/pyjuju/namespace-from-env
Merge into: lp:pyjuju
Diff against target: 272 lines (+146/-16)
5 files modified
juju/charm/repository.py (+5/-2)
juju/charm/tests/test_url.py (+73/-2)
juju/charm/url.py (+51/-9)
juju/control/deploy.py (+6/-1)
juju/control/tests/test_deploy.py (+11/-2)
To merge this branch: bzr merge lp:~clint-fewbar/pyjuju/namespace-from-env
Reviewer Review Type Date Requested Status
Benjamin Saller (community) Approve
Review via email: mp+94479@code.launchpad.net

Description of the change

Default Charm namespace should be overridable by environment variables.

If JUJU_DEFAULT_NS is specified, juju will infer that namespace.
Otherwise, 'cs:' will still be the default.

https://codereview.appspot.com/5695056/

To post a comment you must log in.
Revision history for this message
Benjamin Saller (bcsaller) wrote :

LGTM, it wasn't clear from the mailing list if you agreed about gustavo's suggesstion of calling the env var JUJU_DEFAULT_NS or not, also (and I'm not 100% clear how to reference this now that we've split the docs off) you should add a note to the existing docs about this option.

review: Approve
Revision history for this message
Kapil Thangavelu (hazmat) wrote :

From the list discussion this should set namespace and series.

469. By Clint Byrum

working with new proposed ability to override series and user as well

470. By Clint Byrum

handle only user being inferred from environment

Revision history for this message
Benjamin Saller (bcsaller) wrote :

Thanks for sticking with this. I've included some feedback.

https://codereview.appspot.com/5695056/diff/3001/juju/charm/tests/test_url.py
File juju/charm/tests/test_url.py (left):

https://codereview.appspot.com/5695056/diff/3001/juju/charm/tests/test_url.py#oldcode126
juju/charm/tests/test_url.py:126:
All the tests assume the env is set to a proper value, test the case
where it is not.

https://codereview.appspot.com/5695056/diff/3001/juju/charm/url.py
File juju/charm/url.py (right):

https://codereview.appspot.com/5695056/diff/3001/juju/charm/url.py#newcode131
juju/charm/url.py:131: default_schema, rest =
os.environ.get("JUJU_DEFAULT_NS","cs:").split(":",1)
I think this module is too low level to be interacting with the
environment like this. The juju/control layer should consume environment
information and can supply information. Those (deploy and upgrade-charm)
use the repository.resolve function which can take additional arguments
for this (and handle errors in a place where we can inform the user they
set the variable incorrectly. Resolve can take the parsed output of the
environment and pass it to infer. juju/control can even use a util
method from here to help parse the argument but that parsing has to be
more solid that what is here now.

Currently a bad value, missing a ":" for example, will result in a
ValueError when trying to unpack on the left-hand-side. Need to be more
defensive than that.

https://codereview.appspot.com/5695056/

471. By Clint Byrum

move JUJU_DEFAULT_NS to deploy command

472. By Clint Byrum

merging with trunk

473. By Clint Byrum

documenting usage of JUJU_DEFAULT_NS in deploy.

474. By Clint Byrum

cleanup, remove improper env setting leftover rom refactoring

475. By Clint Byrum

get more defensive about bad namespaces

Revision history for this message
Kapil Thangavelu (hazmat) wrote :

This code would be much simpler, and easier to reason about its
correctness, if it did all its parsing up front and just used the
variables instead of conditionally redefining them as it went with
incremental parsing of 'rest'.

https://codereview.appspot.com/5695056/diff/8002/juju/charm/url.py
File juju/charm/url.py (right):

https://codereview.appspot.com/5695056/diff/8002/juju/charm/url.py#newcode155
juju/charm/url.py:155: default_infer_series, rest = rest.split('/',1)
untested

https://codereview.appspot.com/5695056/diff/8002/juju/charm/url.py#newcode182
juju/charm/url.py:182: rest = "%s/%s" % (default_infer_series, rest)
untested

https://codereview.appspot.com/5695056/

Revision history for this message
Kapil Thangavelu (hazmat) wrote :

Excerpts from Kapil Thangavelu's message of 2012-03-15 18:07:25 UTC:
> This code would be much simpler, and easier to reason about its
> correctness, if it did all its parsing up front and just used the
> variables instead of conditionally redefining them as it went with
> incremental parsing of 'rest'.
>
>
> https://codereview.appspot.com/5695056/diff/8002/juju/charm/url.py
> File juju/charm/url.py (right):
>
> https://codereview.appspot.com/5695056/diff/8002/juju/charm/url.py#newcode155
> juju/charm/url.py:155: default_infer_series, rest = rest.split('/',1)
> untested
>
> https://codereview.appspot.com/5695056/diff/8002/juju/charm/url.py#newcode182
> juju/charm/url.py:182: rest = "%s/%s" % (default_infer_series, rest)
> untested
>
> https://codereview.appspot.com/5695056/
>

diff to pep8 fixes
http://paste.ubuntu.com/885281/

Revision history for this message
Kapil Thangavelu (hazmat) wrote :

i've agreed with clint to take this over the completion and landing of this branch.

Unmerged revisions

475. By Clint Byrum

get more defensive about bad namespaces

474. By Clint Byrum

cleanup, remove improper env setting leftover rom refactoring

473. By Clint Byrum

documenting usage of JUJU_DEFAULT_NS in deploy.

472. By Clint Byrum

merging with trunk

471. By Clint Byrum

move JUJU_DEFAULT_NS to deploy command

470. By Clint Byrum

handle only user being inferred from environment

469. By Clint Byrum

working with new proposed ability to override series and user as well

468. By Clint Byrum

Pull default namespace from environment.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'juju/charm/repository.py'
--- juju/charm/repository.py 2012-02-16 19:19:42 +0000
+++ juju/charm/repository.py 2012-03-09 22:39:18 +0000
@@ -167,7 +167,7 @@
167 returnValue(info["revision"])167 returnValue(info["revision"])
168168
169169
170def resolve(vague_name, repository_path, default_series):170def resolve(vague_name, repository_path, default_series, default_namespace=None):
171 """Get a Charm and associated identifying information171 """Get a Charm and associated identifying information
172172
173 :param str vague_name: a lazily specified charm name, suitable for use with173 :param str vague_name: a lazily specified charm name, suitable for use with
@@ -181,12 +181,15 @@
181 :param str default_series: the Ubuntu series to insert when `charm_name` is181 :param str default_series: the Ubuntu series to insert when `charm_name` is
182 inadequately specified.182 inadequately specified.
183183
184 :param str default_namespace: partial charm url that is passed to
185 :meth:`CharmURL.infer`
186
184 :return: a tuple of a :class:`juju.charm.url.CharmURL` and a187 :return: a tuple of a :class:`juju.charm.url.CharmURL` and a
185 :class:`juju.charm.base.CharmBase` subclass, which together contain188 :class:`juju.charm.base.CharmBase` subclass, which together contain
186 both the charm's data and all information necessary to specify its189 both the charm's data and all information necessary to specify its
187 source.190 source.
188 """191 """
189 url = CharmURL.infer(vague_name, default_series)192 url = CharmURL.infer(vague_name, default_series, default_namespace)
190 if url.collection.schema == "local":193 if url.collection.schema == "local":
191 repo = LocalCharmRepository(repository_path)194 repo = LocalCharmRepository(repository_path)
192 elif url.collection.schema == "cs":195 elif url.collection.schema == "cs":
193196
=== modified file 'juju/charm/tests/test_url.py'
--- juju/charm/tests/test_url.py 2011-10-06 21:04:13 +0000
+++ juju/charm/tests/test_url.py 2012-03-09 22:39:18 +0000
@@ -98,8 +98,8 @@
98 url3.assert_revision()98 url3.assert_revision()
99 self.assertEquals(str(url3), "cs:foo/bar-999")99 self.assertEquals(str(url3), "cs:foo/bar-999")
100100
101 def assert_infer(self, string, schema, user, series, name, rev):101 def assert_infer(self, string, schema, user, series, name, rev, ns=None):
102 url = CharmURL.infer(string, "default")102 url = CharmURL.infer(string, "default", ns)
103 self.assert_url(url, schema, user, series, name, rev)103 self.assert_url(url, schema, user, series, name, rev)
104104
105 def test_infer(self):105 def test_infer(self):
@@ -124,6 +124,77 @@
124 self.assert_infer(124 self.assert_infer(
125 "local:name-0", "local", None, "default", "name", 0)125 "local:name-0", "local", None, "default", "name", 0)
126126
127 def test_infer_from_default_namespace(self):
128 self.assert_infer(
129 "name", "local", None, "default", "name", None, 'local')
130 self.assert_infer(
131 "name-0", "local", None, "default", "name", 0, 'local')
132 self.assert_infer(
133 "series/name", "local", None, "series", "name", None, 'local')
134 self.assert_infer(
135 "series/name-0", "local", None, "series", "name", 0, 'local')
136 self.assert_infer(
137 "name", "local", None, "default", "name", None, 'local:')
138 self.assert_infer(
139 "name-0", "local", None, "default", "name", 0, 'local:')
140 self.assert_infer(
141 "series/name", "local", None, "series", "name", None, 'local:')
142 self.assert_infer(
143 "series/name-0", "local", None, "series", "name", 0, 'local:')
144
145 def test_infer_series_from_default_namespace(self):
146 self.assert_infer(
147 "name", "cs", None, "envseries", "name", None, 'cs:envseries')
148 self.assert_infer(
149 "name-0", "cs", None, "envseries", "name", 0, 'cs:envseries')
150 self.assert_infer(
151 "series/name", "cs", None, "series", "name", None, 'cs:envseries')
152 self.assert_infer(
153 "series/name-0", "cs", None, "series", "name", 0, 'cs:envseries')
154
155 def test_infer_user_from_default_namespace(self):
156 self.assert_infer(
157 "name", "cs", "foouser", "default", "name", None, 'cs:~foouser')
158 self.assert_infer(
159 "name-0", "cs", "foouser", "default", "name", 0, 'cs:~foouser')
160 self.assert_infer(
161 "series/name", "cs", "foouser", "series", "name", None, 'cs:~foouser')
162 self.assert_infer(
163 "series/name-0", "cs", "foouser", "series", "name", 0, 'cs:~foouser')
164
165 def test_infer_user_and_series_from_environ(self):
166 self.assert_infer(
167 "name", "cs", "foouser", "fooseries", "name", None, 'cs:~foouser/fooseries')
168 self.assert_infer(
169 "name-0", "cs", "foouser", "fooseries", "name", 0, 'cs:~foouser/fooseries')
170 self.assert_infer(
171 "series/name", "cs", "foouser", "series", "name", None, 'cs:~foouser/fooseries')
172 self.assert_infer(
173 "series/name-0", "cs", "foouser", "series", "name", 0, 'cs:~foouser/fooseries')
174
175 def test_infer_bad_namespace(self):
176 # Invalid schema "zyzzyx/"
177 self.assertRaises(
178 CharmURLError, CharmURL.infer, "name","fooseries", 'zyzzyx/:/~foo///')
179 self.assertRaises(
180 CharmURLError, CharmURL.infer, "series/name","fooseries", 'zyzzyx/:/~foo///')
181 self.assertRaises(
182 CharmURLError, CharmURL.infer, "~user/series/name","fooseries", 'zyzzyx/:/~foo///')
183 """ cs:name, does not actually need to be inferred from the
184 default namespace, but the namespace will still trigger problems. """
185 self.assertRaises(
186 CharmURLError, CharmURL.infer, "cs:name","fooseries", 'zyzzyx/:/~foo///')
187 self.assertRaises(
188 CharmURLError, CharmURL.infer, "local:name","fooseries", 'zyzzyx/:/~foo///')
189
190 # Just a user is too ambiguous
191 self.assertRaises(
192 CharmURLError, CharmURL.infer, "name","fooseries", '~someuser')
193 self.assertRaises(
194 CharmURLError, CharmURL.infer, "series/name","fooseries", '~someuser')
195 self.assertRaises(
196 CharmURLError, CharmURL.infer, "~user/series/name","fooseries", '~someuser')
197
127 def test_cannot_infer(self):198 def test_cannot_infer(self):
128 err = self.assertRaises(199 err = self.assertRaises(
129 CharmURLError, CharmURL.infer, "name", "invalid!series")200 CharmURLError, CharmURL.infer, "name", "invalid!series")
130201
=== modified file 'juju/charm/url.py'
--- juju/charm/url.py 2011-10-06 21:04:13 +0000
+++ juju/charm/url.py 2012-03-09 22:39:18 +0000
@@ -1,5 +1,6 @@
1import copy1import copy
2import re2import re
3import os
34
4from juju.charm.errors import CharmURLError5from juju.charm.errors import CharmURLError
56
@@ -7,6 +8,7 @@
7_USER_RE = re.compile("^[a-z0-9][a-zA-Z0-9+.-]+$")8_USER_RE = re.compile("^[a-z0-9][a-zA-Z0-9+.-]+$")
8_SERIES_RE = re.compile("^[a-z]+([a-z-]+[a-z])?$")9_SERIES_RE = re.compile("^[a-z]+([a-z-]+[a-z])?$")
9_NAME_RE = re.compile("^[a-z][a-z0-9]*(-[a-z0-9]*[a-z][a-z0-9]*)*$")10_NAME_RE = re.compile("^[a-z][a-z0-9]*(-[a-z0-9]*[a-z][a-z0-9]*)*$")
11_NAMESPACE_RE = re.compile("^[a-zA-Z]+:\~?[a-zA-Z\-]*/?")
1012
1113
12class CharmCollection(object):14class CharmCollection(object):
@@ -114,7 +116,7 @@
114 return cls(CharmCollection(schema, user, series), name, revision)116 return cls(CharmCollection(schema, user, series), name, revision)
115117
116 @classmethod118 @classmethod
117 def infer(cls, vague_name, default_series):119 def infer(cls, vague_name, default_series, default_namespace=None):
118 """Turn a potentially fuzzy alias into a CharmURL."""120 """Turn a potentially fuzzy alias into a CharmURL."""
119 try:121 try:
120 # it might already be a valid URL string122 # it might already be a valid URL string
@@ -127,19 +129,59 @@
127 raise CharmURLError(129 raise CharmURLError(
128 vague_name, "a URL with a user must specify a schema")130 vague_name, "a URL with a user must specify a schema")
129131
132 if default_namespace is None:
133 default_namespace = 'cs:'
134 else:
135 # schema is the most basic unit, so assume that if no :
136 if ':' not in default_namespace:
137 default_namespace = "%s:" % (default_namespace)
138
139 if not _NAMESPACE_RE .match(default_namespace):
140 raise CharmURLError(default_namespace, "invalid default namespace")
141
142 default_schema, rest = default_namespace.split(":",1)
143
144 # determine what has been defaulted in JUJU_DEFAULT_NS
145 if len(rest) and rest[0] == '~':
146 if '/' in rest:
147 default_infer_user, rest = rest[1:].split('/',1)
148 else:
149 default_infer_user = rest[1:]
150 rest=''
151 else:
152 default_infer_user = None
153
154 if '/' in rest:
155 default_infer_series, rest = rest.split('/',1)
156 elif len(rest):
157 default_infer_series = rest
158 else:
159 default_infer_series = default_series
160
161 # determine what is and is not set in vague_name
130 if ":" in vague_name:162 if ":" in vague_name:
131 schema, rest = vague_name.split(":", 1)163 schema, rest = vague_name.split(":", 1)
132 else:164 else:
133 schema, rest = "cs", vague_name165 schema = default_schema
166 rest = vague_name
167
168 if rest[0] != '~':
169 if default_infer_user is None:
170 if '/' not in rest:
171 rest = "%s/%s" % (default_infer_series, rest)
172 else:
173 if '/' in rest:
174 rest = "~%s/%s" % (default_infer_user, rest)
175 else:
176 rest = "~%s/%s/%s" % (default_infer_user, default_infer_series, rest)
177 else:
178 if '/' in rest:
179 infer_user, rest = rest.split('~')[1].split('/',1)
180 rest = "~%s/%s/%s" % (infer_user, default_infer_series, rest)
181 else:
182 rest = "%s/%s" % (default_infer_series, rest)
134183
135 url_string = "%s:%s" % (schema, rest)184 url_string = "%s:%s" % (schema, rest)
136 parts = rest.split("/")
137 if len(parts) == 1:
138 url_string = "%s:%s/%s" % (schema, default_series, rest)
139 elif len(parts) == 2:
140 if parts[0].startswith("~"):
141 url_string = "%s:%s/%s/%s" % (
142 schema, parts[0], default_series, parts[1])
143185
144 try:186 try:
145 return cls.parse(url_string)187 return cls.parse(url_string)
146188
=== modified file 'juju/control/deploy.py'
--- juju/control/deploy.py 2012-03-09 20:20:20 +0000
+++ juju/control/deploy.py 2012-03-09 22:39:18 +0000
@@ -112,9 +112,14 @@
112 a service from the charm, and get it set to be launched112 a service from the charm, and get it set to be launched
113 on a new machine. If --repository is not specified, it113 on a new machine. If --repository is not specified, it
114 will be taken from the environment variable JUJU_REPOSITORY.114 will be taken from the environment variable JUJU_REPOSITORY.
115 If `charm` has a non-specific URL it will be resolved
116 using the default namespace, which is "cs:". That default can
117 be overridden with the environment variable JUJU_DEFAULT_NS.
115 """118 """
119 default_namespace = os.environ.get('JUJU_DEFAULT_NS')
116 repo, charm_url = resolve(120 repo, charm_url = resolve(
117 charm_name, repository_path, environment.default_series)121 charm_name, repository_path, environment.default_series,
122 default_namespace)
118123
119 log.info("Searching for charm")124 log.info("Searching for charm")
120 charm = yield repo.find(charm_url)125 charm = yield repo.find(charm_url)
121126
=== modified file 'juju/control/tests/test_deploy.py'
--- juju/control/tests/test_deploy.py 2012-03-09 20:13:36 +0000
+++ juju/control/tests/test_deploy.py 2012-03-09 22:39:18 +0000
@@ -210,10 +210,19 @@
210210
211 @inlineCallbacks211 @inlineCallbacks
212 def test_deploy(self):212 def test_deploy(self):
213 yield self._test_deploy('local:sample')
214
215 @inlineCallbacks
216 def test_deploy_namespace_from_environ(self):
217 self.change_environment(JUJU_DEFAULT_NS='local')
218 yield self._test_deploy('sample')
219
220 @inlineCallbacks
221 def _test_deploy(self, charmname):
213 """Create service, and service unit on machine from charm"""222 """Create service, and service unit on machine from charm"""
214 environment = self.config.get("firstenv")223 environment = self.config.get("firstenv")
215 yield deploy.deploy(224 yield deploy.deploy(
216 self.config, environment, self.unbundled_repo_path, "local:sample",225 self.config, environment, self.unbundled_repo_path, charmname,
217 "myblog", logging.getLogger("deploy"), ["cpu=123"])226 "myblog", logging.getLogger("deploy"), ["cpu=123"])
218 topology = yield self.get_topology()227 topology = yield self.get_topology()
219228
@@ -299,7 +308,7 @@
299 repo.type308 repo.type
300 self.mocker.result("store")309 self.mocker.result("store")
301 resolve = self.mocker.replace("juju.control.deploy.resolve")310 resolve = self.mocker.replace("juju.control.deploy.resolve")
302 resolve("cs:sample", None, "series")311 resolve("cs:sample", None, "series", None)
303 self.mocker.result((repo, CharmURL.infer("cs:sample", "series")))312 self.mocker.result((repo, CharmURL.infer("cs:sample", "series")))
304 repo.find(MATCH(lambda x: isinstance(x, CharmURL)))313 repo.find(MATCH(lambda x: isinstance(x, CharmURL)))
305 self.mocker.result(CharmDirectory(self.sample_dir1))314 self.mocker.result(CharmDirectory(self.sample_dir1))

Subscribers

People subscribed via source and target branches

to status/vote changes: