Merge lp:~fwereade/pyjuju/check-latest-formulas into lp:pyjuju
- check-latest-formulas
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Gustavo Niemeyer | ||||
Approved revision: | 386 | ||||
Merged at revision: | 387 | ||||
Proposed branch: | lp:~fwereade/pyjuju/check-latest-formulas | ||||
Merge into: | lp:pyjuju | ||||
Prerequisite: | lp:~fwereade/pyjuju/use-remote-formulas | ||||
Diff against target: |
799 lines (+377/-225) 6 files modified
juju/charm/repository.py (+63/-33) juju/charm/tests/test_repository.py (+301/-187) juju/charm/tests/test_url.py (+1/-0) juju/charm/url.py (+4/-0) juju/control/deploy.py (+4/-3) juju/control/upgrade_charm.py (+4/-2) |
||||
To merge this branch: | bzr merge lp:~fwereade/pyjuju/check-latest-formulas | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gustavo Niemeyer | Approve | ||
Review via email:
|
Commit message
Description of the change
Use the /latest?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
William Reade (fwereade) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
William Reade (fwereade) wrote : | # |
Technically, I suppose, I'm demanding that a charm-url-
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Gustavo Niemeyer (niemeyer) wrote : | # |
[1]
+ def latest(self, charm_url):
+ d = self.find(
+ d.addCallback(
+ return d
Eventually the method signature should be closer to the API in
the spec, since we don't want to do a query for every single
charm we have to find out the revision for.
I'm fine to move this forward as an initial implementation, though.
[2]
+ d.addCallback(
This seems clearer, avoids the import, and is shorter:
d.addCallba
[3]
+ url = "%s/%s?charms=%s" % (self.url_base, query, charm_url)
charm_url has to be url-escaped here.
[4]
+ def find(self, charm_url):
+ revision = yield self.latest(
+ charm_url = charm_url.
+ charm = yield self._get_
Why is this hardcoding getting the latest formula at all times?
If charm_url has a revision, there's no reason not to respect it
and proceed exactly the same way, I think.
Also, the original version of this function worked with a single
roundtrip to the server. This one does three of them (one for
the revision, one for the charm, and another one for the sha256).
I think we should tweak the server API so we can get both the
revision and the sha256 at the same time. Will think a bit about
this and post back later today.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
William Reade (fwereade) wrote : | # |
Thanks :).
> [1]
>
> + def latest(self, charm_url):
> + d = self.find(
> + d.addCallback(
> + return d
>
> Eventually the method signature should be closer to the API in
> the spec, since we don't want to do a query for every single
> charm we have to find out the revision for.
>
> I'm fine to move this forward as an initial implementation, though.
Agree, but am trying to avoid speculative generality in the plastic bits.
> [2]
>
> + d.addCallback(
>
> This seems clearer, avoids the import, and is shorter:
>
> d.addCallback(
Awww, ok.
> [3]
>
> + url = "%s/%s?charms=%s" % (self.url_base, query, charm_url)
>
> charm_url has to be url-escaped here.
* fwereade hangs head in shame
> [4]
>
> + def find(self, charm_url):
> + revision = yield self.latest(
> + charm_url = charm_url.
> + charm = yield self._get_
>
> Why is this hardcoding getting the latest formula at all times?
>
> If charm_url has a revision, there's no reason not to respect it
> and proceed exactly the same way, I think.
Sounds good, I'll fix it.
> Also, the original version of this function worked with a single
> roundtrip to the server. This one does three of them (one for
> the revision, one for the charm, and another one for the sha256).
> I think we should tweak the server API so we can get both the
> revision and the sha256 at the same time. Will think a bit about
> this and post back later today.
A combined method in the server API would be great, we'd just need one roundtrip on cache hits and two on misses. I'll go ahead assuming one exists and make sure we agree what it should be called before next MP :).
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Gustavo Niemeyer (niemeyer) wrote : | # |
[4]
So, here is an idea to sort out the multiple-request problem: let's
replace the /latest entry by one called /charm-info that is called
the same way (/charm-
like this instead:
{charm_url: {"sha256": s, "revision": n}, ...}
How does that sound?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
William Reade (fwereade) wrote : | # |
> [4]
>
> So, here is an idea to sort out the multiple-request problem: let's
> replace the /latest entry by one called /charm-info that is called
> the same way (/charm-
> like this instead:
>
> {charm_url: {"sha256": s, "revision": n}, ...}
>
> How does that sound?
Perfect.
- 386. By William Reade
-
merge trunk
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Gustavo Niemeyer (niemeyer) wrote : | # |
This is looking great. LGTM, considering just a couple of minors:
[5]
+ try:
+ data = yield getPage(url)
+ returnValue(
+ except (Error, KeyError, ValueError, TypeError):
+ raise CharmNotFound(
Hmmm.. do we really want TypeError and ValueError in this list?
My gut feeling when looking at this is that they would be bugs,
but maybe you have something else in mind.
[6]
+ assert charm.metadata.
+ assert charm.get_sha256() == info["sha256"], "bad bundle hash"
The first seems like a good fit for an assertion, but the bottom one
is a plausible runtime error that deserves a proper exception and
a nice error message to help the user.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
William Reade (fwereade) wrote : | # |
[5]
They're intended to guard against bugs in the charm store, really, but I take your point. Hmm. It would certainly be clearer if I assumed that CS will either give me what I asked for, or Error out; can't guard against everything, after all, and the above is indeed potentially masking local bugs. Yep, I'm convinced.
[6]
Sounds good. I'll make sure I delete the bad cache entry as well, so there's some chance of a retry actually working.
- 387. By William Reade
-
address review points
- 388. By William Reade
-
merge trunk
Preview Diff
1 | === modified file 'juju/charm/repository.py' | |||
2 | --- juju/charm/repository.py 2011-10-03 20:04:45 +0000 | |||
3 | +++ juju/charm/repository.py 2011-10-05 10:00:46 +0000 | |||
4 | @@ -1,9 +1,11 @@ | |||
5 | 1 | import json | ||
6 | 1 | import os | 2 | import os |
7 | 2 | import tempfile | 3 | import tempfile |
8 | 4 | import urllib | ||
9 | 3 | import yaml | 5 | import yaml |
10 | 4 | 6 | ||
11 | 5 | from twisted.internet.defer import fail, inlineCallbacks, returnValue, succeed | 7 | from twisted.internet.defer import fail, inlineCallbacks, returnValue, succeed |
13 | 6 | from twisted.web.client import downloadPage | 8 | from twisted.web.client import downloadPage, getPage |
14 | 7 | from twisted.web.error import Error | 9 | from twisted.web.error import Error |
15 | 8 | 10 | ||
16 | 9 | from juju.charm.provider import get_charm_from_path | 11 | from juju.charm.provider import get_charm_from_path |
17 | @@ -21,6 +23,11 @@ | |||
18 | 21 | pass | 23 | pass |
19 | 22 | 24 | ||
20 | 23 | 25 | ||
21 | 26 | def _cache_key(charm_url): | ||
22 | 27 | charm_url.assert_revision() | ||
23 | 28 | return under.quote("%s.charm" % charm_url) | ||
24 | 29 | |||
25 | 30 | |||
26 | 24 | class LocalCharmRepository(object): | 31 | class LocalCharmRepository(object): |
27 | 25 | """Charm repository in a local directory.""" | 32 | """Charm repository in a local directory.""" |
28 | 26 | 33 | ||
29 | @@ -50,11 +57,12 @@ | |||
30 | 50 | recent one (greatest revision) will be returned. | 57 | recent one (greatest revision) will be returned. |
31 | 51 | """ | 58 | """ |
32 | 52 | assert charm_url.collection.schema == "local", "schema mismatch" | 59 | assert charm_url.collection.schema == "local", "schema mismatch" |
33 | 53 | assert charm_url.revision is None, "find-by-revision not supported yet" | ||
34 | 54 | 60 | ||
35 | 55 | latest = None | 61 | latest = None |
36 | 56 | for charm in self._collection(charm_url.collection): | 62 | for charm in self._collection(charm_url.collection): |
37 | 57 | if charm.metadata.name == charm_url.name: | 63 | if charm.metadata.name == charm_url.name: |
38 | 64 | if charm.metadata.revision == charm_url.revision: | ||
39 | 65 | return succeed(charm) | ||
40 | 58 | if (latest is None or | 66 | if (latest is None or |
41 | 59 | latest.metadata.revision < charm.metadata.revision): | 67 | latest.metadata.revision < charm.metadata.revision): |
42 | 60 | latest = charm | 68 | latest = charm |
43 | @@ -64,6 +72,11 @@ | |||
44 | 64 | 72 | ||
45 | 65 | return succeed(latest) | 73 | return succeed(latest) |
46 | 66 | 74 | ||
47 | 75 | def latest(self, charm_url): | ||
48 | 76 | d = self.find(charm_url.with_revision(None)) | ||
49 | 77 | d.addCallback(lambda c: c.metadata.revision) | ||
50 | 78 | return d | ||
51 | 79 | |||
52 | 67 | 80 | ||
53 | 68 | class RemoteCharmRepository(object): | 81 | class RemoteCharmRepository(object): |
54 | 69 | 82 | ||
55 | @@ -75,42 +88,60 @@ | |||
56 | 75 | self.cache_path = cache_path | 88 | self.cache_path = cache_path |
57 | 76 | 89 | ||
58 | 77 | @inlineCallbacks | 90 | @inlineCallbacks |
60 | 78 | def _download(self, url): | 91 | def _get_info(self, charm_url): |
61 | 92 | url = "%s/charm-info?charms=%s" % ( | ||
62 | 93 | self.url_base, urllib.quote(str(charm_url))) | ||
63 | 94 | try: | ||
64 | 95 | data = yield getPage(url) | ||
65 | 96 | returnValue(json.loads(data)[str(charm_url)]) | ||
66 | 97 | except Error: | ||
67 | 98 | raise CharmNotFound(self.url_base, charm_url) | ||
68 | 99 | |||
69 | 100 | @inlineCallbacks | ||
70 | 101 | def _download(self, charm_url, cache_path): | ||
71 | 102 | url = "%s/charm/%s" % (self.url_base, urllib.quote(charm_url.path)) | ||
72 | 79 | downloads = os.path.join(self.cache_path, "downloads") | 103 | downloads = os.path.join(self.cache_path, "downloads") |
73 | 80 | _makedirs(downloads) | 104 | _makedirs(downloads) |
74 | 81 | f = tempfile.NamedTemporaryFile( | 105 | f = tempfile.NamedTemporaryFile( |
76 | 82 | prefix=under.quote(url), suffix=".charm", dir=downloads, | 106 | prefix=_cache_key(charm_url), suffix=".part", dir=downloads, |
77 | 83 | delete=False) | 107 | delete=False) |
78 | 84 | f.close() | 108 | f.close() |
100 | 85 | completed_path = f.name | 109 | downloading_path = f.name |
80 | 86 | downloading_path = completed_path + ".part" | ||
81 | 87 | yield downloadPage(url, downloading_path) | ||
82 | 88 | os.rename(downloading_path, completed_path) | ||
83 | 89 | returnValue(completed_path) | ||
84 | 90 | |||
85 | 91 | def _cache(self, charm_url_base, temp_charm_path): | ||
86 | 92 | temp_charm = get_charm_from_path(temp_charm_path) | ||
87 | 93 | revision = temp_charm.metadata.revision | ||
88 | 94 | charm_key = under.quote( | ||
89 | 95 | "%s.charm" % (charm_url_base.with_revision(revision))) | ||
90 | 96 | charm_path = os.path.join(self.cache_path, charm_key) | ||
91 | 97 | _makedirs(self.cache_path) | ||
92 | 98 | os.rename(temp_charm_path, charm_path) | ||
93 | 99 | return get_charm_from_path(charm_path) | ||
94 | 100 | |||
95 | 101 | @inlineCallbacks | ||
96 | 102 | def find(self, charm_url): | ||
97 | 103 | assert charm_url.revision is None, "find-by-revision not supported yet" | ||
98 | 104 | _, path = str(charm_url).split(":", 1) | ||
99 | 105 | url = "%s/charm/%s" % (self.url_base, path) | ||
101 | 106 | try: | 110 | try: |
103 | 107 | temp_charm_path = yield self._download(url) | 111 | yield downloadPage(url, downloading_path) |
104 | 108 | except Error: | 112 | except Error: |
105 | 109 | raise CharmNotFound(self.url_base, charm_url) | 113 | raise CharmNotFound(self.url_base, charm_url) |
110 | 110 | returnValue(self._cache(charm_url, temp_charm_path)) | 114 | os.rename(downloading_path, cache_path) |
111 | 111 | 115 | ||
112 | 112 | 116 | @inlineCallbacks | |
113 | 113 | @inlineCallbacks | 117 | def find(self, charm_url): |
114 | 118 | info = yield self._get_info(charm_url) | ||
115 | 119 | revision = info["revision"] | ||
116 | 120 | if charm_url.revision is None: | ||
117 | 121 | charm_url = charm_url.with_revision(revision) | ||
118 | 122 | else: | ||
119 | 123 | assert revision == charm_url.revision, "bad url revision" | ||
120 | 124 | |||
121 | 125 | cache_path = os.path.join(self.cache_path, _cache_key(charm_url)) | ||
122 | 126 | cached = os.path.exists(cache_path) | ||
123 | 127 | if not cached: | ||
124 | 128 | yield self._download(charm_url, cache_path) | ||
125 | 129 | charm = get_charm_from_path(cache_path) | ||
126 | 130 | |||
127 | 131 | assert charm.metadata.revision == revision, "bad charm revision" | ||
128 | 132 | if charm.get_sha256() != info["sha256"]: | ||
129 | 133 | os.remove(cache_path) | ||
130 | 134 | name = "%s (%s)" % ( | ||
131 | 135 | charm_url, "cached" if cached else "downloaded") | ||
132 | 136 | raise CharmError(name, "SHA256 mismatch") | ||
133 | 137 | returnValue(charm) | ||
134 | 138 | |||
135 | 139 | @inlineCallbacks | ||
136 | 140 | def latest(self, charm_url): | ||
137 | 141 | info = yield self._get_info(charm_url.with_revision(None)) | ||
138 | 142 | returnValue(info["revision"]) | ||
139 | 143 | |||
140 | 144 | |||
141 | 114 | def resolve(vague_name, repository_path, default_series): | 145 | def resolve(vague_name, repository_path, default_series): |
142 | 115 | """Get a Charm and associated identifying information | 146 | """Get a Charm and associated identifying information |
143 | 116 | 147 | ||
144 | @@ -125,7 +156,7 @@ | |||
145 | 125 | :param str default_series: the Ubuntu series to insert when `charm_name` is | 156 | :param str default_series: the Ubuntu series to insert when `charm_name` is |
146 | 126 | inadequately specified. | 157 | inadequately specified. |
147 | 127 | 158 | ||
149 | 128 | :return: a tuple of a :class:`CharmURL` and a | 159 | :return: a tuple of a :class:`juju.charm.url.CharmURL` and a |
150 | 129 | :class:`juju.charm.base.CharmBase` subclass, which together contain | 160 | :class:`juju.charm.base.CharmBase` subclass, which together contain |
151 | 130 | both the charm's data and all information necessary to specify its | 161 | both the charm's data and all information necessary to specify its |
152 | 131 | source. | 162 | source. |
153 | @@ -135,5 +166,4 @@ | |||
154 | 135 | repo = LocalCharmRepository(repository_path) | 166 | repo = LocalCharmRepository(repository_path) |
155 | 136 | elif url.collection.schema == "cs": | 167 | elif url.collection.schema == "cs": |
156 | 137 | repo = RemoteCharmRepository("https://store.juju.ubuntu.com") | 168 | repo = RemoteCharmRepository("https://store.juju.ubuntu.com") |
159 | 138 | charm = yield repo.find(url) | 169 | return repo, url |
158 | 139 | returnValue((url, charm)) | ||
160 | 140 | 170 | ||
161 | === modified file 'juju/charm/tests/test_repository.py' | |||
162 | --- juju/charm/tests/test_repository.py 2011-09-30 10:32:01 +0000 | |||
163 | +++ juju/charm/tests/test_repository.py 2011-10-05 10:00:46 +0000 | |||
164 | @@ -1,3 +1,4 @@ | |||
165 | 1 | import json | ||
166 | 1 | import os | 2 | import os |
167 | 2 | import inspect | 3 | import inspect |
168 | 3 | import shutil | 4 | import shutil |
169 | @@ -12,6 +13,7 @@ | |||
170 | 12 | from juju.charm.repository import ( | 13 | from juju.charm.repository import ( |
171 | 13 | LocalCharmRepository, RemoteCharmRepository, resolve) | 14 | LocalCharmRepository, RemoteCharmRepository, resolve) |
172 | 14 | from juju.charm.url import CharmURL | 15 | from juju.charm.url import CharmURL |
173 | 16 | from juju.errors import CharmError | ||
174 | 15 | from juju.lib import under | 17 | from juju.lib import under |
175 | 16 | 18 | ||
176 | 17 | from juju.charm import tests | 19 | from juju.charm import tests |
177 | @@ -61,25 +63,37 @@ | |||
178 | 61 | return CharmURL.parse("local:series/" + name) | 63 | return CharmURL.parse("local:series/" + name) |
179 | 62 | 64 | ||
180 | 63 | @inlineCallbacks | 65 | @inlineCallbacks |
187 | 64 | def test_find_charm_by_name_which_is_unbundled(self): | 66 | def assert_not_there(self, name): |
188 | 65 | charm = yield self.repository1.find(self.charm_url("sample")) | 67 | url = self.charm_url(name) |
189 | 66 | self.assertTrue(charm) | 68 | msg = "Charm 'local:series/%s' not found in repository %s" % ( |
190 | 67 | self.assertTrue(charm.metadata) | 69 | name, self.unbundled_repo_path) |
191 | 68 | 70 | err = yield self.assertFailure( | |
192 | 69 | def test_find_charm_ignores_unknown_files(self): | 71 | self.repository1.find(url), CharmNotFound) |
193 | 72 | self.assertEquals(str(err), msg) | ||
194 | 73 | err = yield self.assertFailure( | ||
195 | 74 | self.repository1.latest(url), CharmNotFound) | ||
196 | 75 | self.assertEquals(str(err), msg) | ||
197 | 76 | |||
198 | 77 | def test_find_inappropriate_url(self): | ||
199 | 78 | url = CharmURL.parse("cs:foo/bar") | ||
200 | 79 | err = self.assertRaises(AssertionError, self.repository1.find, url) | ||
201 | 80 | self.assertEquals(str(err), "schema mismatch") | ||
202 | 81 | |||
203 | 82 | def test_completely_missing(self): | ||
204 | 83 | return self.assert_not_there("zebra") | ||
205 | 84 | |||
206 | 85 | def test_unkown_files_ignored(self): | ||
207 | 70 | self.makeFile( | 86 | self.makeFile( |
208 | 71 | "Foobar", | 87 | "Foobar", |
209 | 72 | path=os.path.join(self.repository1.path, "series", "zebra")) | 88 | path=os.path.join(self.repository1.path, "series", "zebra")) |
212 | 73 | return self.assertFailure( | 89 | return self.assert_not_there("zebra") |
211 | 74 | self.repository1.find(self.charm_url("zebra")), CharmNotFound) | ||
213 | 75 | 90 | ||
215 | 76 | def test_find_charm_ignores_unknown_directories(self): | 91 | def test_unknown_directories_ignored(self): |
216 | 77 | self.makeDir( | 92 | self.makeDir( |
217 | 78 | path=os.path.join(self.repository1.path, "series", "zebra")) | 93 | path=os.path.join(self.repository1.path, "series", "zebra")) |
220 | 79 | return self.assertFailure( | 94 | return self.assert_not_there("zebra") |
219 | 80 | self.repository1.find(self.charm_url("zebra")), CharmNotFound) | ||
221 | 81 | 95 | ||
223 | 82 | def test_find_charm_ignores_broken_charms(self): | 96 | def test_broken_charms_ignored(self): |
224 | 83 | charm_path = self.makeDir( | 97 | charm_path = self.makeDir( |
225 | 84 | path=os.path.join(self.repository1.path, "series", "zebra")) | 98 | path=os.path.join(self.repository1.path, "series", "zebra")) |
226 | 85 | fh = open(os.path.join(charm_path, "metadata.yaml"), "w+") | 99 | fh = open(os.path.join(charm_path, "metadata.yaml"), "w+") |
227 | @@ -90,45 +104,27 @@ | |||
228 | 90 | revision: 0 | 104 | revision: 0 |
229 | 91 | summary: hola""") | 105 | summary: hola""") |
230 | 92 | fh.close() | 106 | fh.close() |
270 | 93 | 107 | return self.assert_not_there("zebra") | |
271 | 94 | return self.assertFailure( | 108 | |
272 | 95 | self.repository1.find(self.charm_url("zebra")), CharmNotFound) | 109 | def assert_there(self, name, repo, revision, latest_revision=None): |
273 | 96 | 110 | url = self.charm_url(name) | |
274 | 97 | @inlineCallbacks | 111 | charm = yield repo.find(url) |
275 | 98 | def test_find_charm_by_name_which_is_bundled(self): | 112 | self.assertEquals(charm.metadata.revision, revision) |
276 | 99 | charm = yield self.repository2.find(self.charm_url("sample")) | 113 | latest = yield repo.latest(url) |
277 | 100 | self.assertTrue(charm) | 114 | self.assertEquals(latest, latest_revision or revision) |
278 | 101 | self.assertTrue(charm.metadata) | 115 | |
279 | 102 | 116 | def test_success_unbundled(self): | |
280 | 103 | @inlineCallbacks | 117 | return self.assert_there("sample", self.repository1, 2) |
281 | 104 | def test_find_charm_by_name_fails_with_bad_series(self): | 118 | return self.assert_there("sample-2", self.repository1, 2) |
282 | 105 | error = yield self.assertFailure( | 119 | |
283 | 106 | self.repository1.find(CharmURL.parse("local:missing/charm")), | 120 | def test_success_bundled(self): |
284 | 107 | CharmNotFound) | 121 | return self.assert_there("sample", self.repository2, 2) |
285 | 108 | self.assertEquals( | 122 | return self.assert_there("sample-2", self.repository2, 2) |
286 | 109 | str(error), | 123 | |
287 | 110 | "Charm 'local:missing/charm' not found in repository %s" | 124 | @inlineCallbacks |
288 | 111 | % self.unbundled_repo_path) | 125 | def test_no_revision_gets_latest(self): |
289 | 112 | 126 | yield self.assert_there("sample", self.repository1, 2) | |
290 | 113 | def test_find_charm_by_name_fails(self): | 127 | |
252 | 114 | d = self.assertFailure( | ||
253 | 115 | self.repository1.find(self.charm_url("missing")), CharmNotFound) | ||
254 | 116 | |||
255 | 117 | def verify(error): | ||
256 | 118 | self.assertEquals( | ||
257 | 119 | str(error), | ||
258 | 120 | "Charm 'local:series/missing' not found in repository %s" | ||
259 | 121 | % self.unbundled_repo_path) | ||
260 | 122 | d.addCallback(verify) | ||
261 | 123 | return d | ||
262 | 124 | |||
263 | 125 | @inlineCallbacks | ||
264 | 126 | def test_find_charm_with_multiple_versions_returns_latest(self): | ||
265 | 127 | # Copy the repository out of the codebase, so that we can hack it. | ||
266 | 128 | charm = yield self.repository1.find(self.charm_url("sample")) | ||
267 | 129 | self.assertEquals(charm.metadata.revision, 2) | ||
268 | 130 | |||
269 | 131 | # Invert the "latest" logic, to ensure it's not just a coincidence. | ||
291 | 132 | file = open(os.path.join( | 128 | file = open(os.path.join( |
292 | 133 | self.repository1.path, "series/old/metadata.yaml"), "rw+") | 129 | self.repository1.path, "series/old/metadata.yaml"), "rw+") |
293 | 134 | data = yaml.load(file.read()) | 130 | data = yaml.load(file.read()) |
294 | @@ -137,18 +133,8 @@ | |||
295 | 137 | file.write(yaml.dump(data)) | 133 | file.write(yaml.dump(data)) |
296 | 138 | file.close() | 134 | file.close() |
297 | 139 | 135 | ||
310 | 140 | charm = yield self.repository1.find(self.charm_url("sample")) | 136 | yield self.assert_there("sample", self.repository1, 3) |
311 | 141 | self.assertEquals(charm.metadata.revision, 3) | 137 | yield self.assert_there("sample-2", self.repository1, 2, 3) |
300 | 142 | |||
301 | 143 | def test_find_inappropriate_url(self): | ||
302 | 144 | url = CharmURL.parse("cs:foo/bar") | ||
303 | 145 | err = self.assertRaises(AssertionError, self.repository1.find, url) | ||
304 | 146 | self.assertEquals(str(err), "schema mismatch") | ||
305 | 147 | |||
306 | 148 | def test_find_with_revision(self): | ||
307 | 149 | url = CharmURL.parse("local:foo/bar-1") | ||
308 | 150 | err = self.assertRaises(AssertionError, self.repository1.find, url) | ||
309 | 151 | self.assertEquals(str(err), "find-by-revision not supported yet") | ||
312 | 152 | 138 | ||
313 | 153 | 139 | ||
314 | 154 | class RemoteRepositoryTest(RepositoryTestBase): | 140 | class RemoteRepositoryTest(RepositoryTestBase): |
315 | @@ -157,142 +143,270 @@ | |||
316 | 157 | super(RemoteRepositoryTest, self).setUp() | 143 | super(RemoteRepositoryTest, self).setUp() |
317 | 158 | self.cache_path = os.path.join( | 144 | self.cache_path = os.path.join( |
318 | 159 | tempfile.mkdtemp(), "notexistyet") | 145 | tempfile.mkdtemp(), "notexistyet") |
319 | 146 | self.download_path = os.path.join(self.cache_path, "downloads") | ||
320 | 160 | 147 | ||
321 | 161 | def delete(): | 148 | def delete(): |
322 | 162 | if os.path.exists(self.cache_path): | 149 | if os.path.exists(self.cache_path): |
323 | 163 | shutil.rmtree(self.cache_path) | 150 | shutil.rmtree(self.cache_path) |
324 | 164 | self.addCleanup(delete) | 151 | self.addCleanup(delete) |
325 | 165 | 152 | ||
326 | 153 | self.charm = CharmDirectory( | ||
327 | 154 | os.path.join(self.unbundled_repo_path, "series", "dummy")) | ||
328 | 155 | with open(self.charm.as_bundle().path, "rb") as f: | ||
329 | 156 | self.bundle_data = f.read() | ||
330 | 157 | self.sha256 = self.charm.as_bundle().get_sha256() | ||
331 | 158 | self.getPage = self.mocker.replace("twisted.web.client.getPage") | ||
332 | 159 | self.downloadPage = self.mocker.replace( | ||
333 | 160 | "twisted.web.client.downloadPage") | ||
334 | 161 | |||
335 | 166 | def repo(self, url_base): | 162 | def repo(self, url_base): |
336 | 167 | return RemoteCharmRepository(url_base, self.cache_path) | 163 | return RemoteCharmRepository(url_base, self.cache_path) |
337 | 168 | 164 | ||
354 | 169 | @inlineCallbacks | 165 | def cache_location(self, url_str, revision): |
355 | 170 | def assert_find(self, dns_name, url_str, expect_url): | 166 | charm_url = CharmURL.parse(url_str) |
356 | 171 | src_charm = CharmDirectory( | 167 | cache_key = under.quote( |
357 | 172 | os.path.join(self.unbundled_repo_path, "series", "dummy")) | 168 | "%s.charm" % (charm_url.with_revision(revision))) |
358 | 173 | with open(src_charm.as_bundle().path, "rb") as f: | 169 | return os.path.join(self.cache_path, cache_key) |
359 | 174 | bundle_data = f.read() | 170 | |
360 | 175 | 171 | def charm_info(self, url_str, revision): | |
361 | 176 | download_dir = os.path.join(self.cache_path, "downloads") | 172 | return json.dumps({ |
362 | 177 | 173 | url_str: {"revision": revision, "sha256": self.sha256}}) | |
363 | 178 | downloadPage = self.mocker.replace("twisted.web.client.downloadPage") | 174 | |
364 | 179 | downloadPage(expect_url, ANY) | 175 | def mock_charm_info(self, url, result): |
365 | 180 | 176 | self.getPage(url) | |
366 | 181 | def download(_, temp_path): | 177 | self.mocker.result(result) |
367 | 182 | self.assertTrue(temp_path.startswith(download_dir)) | 178 | |
368 | 183 | with open(temp_path, "wb") as f: | 179 | def mock_download(self, url, error=None): |
369 | 184 | f.write(bundle_data) | 180 | self.downloadPage(url, ANY) |
370 | 181 | if error: | ||
371 | 182 | return self.mocker.result(fail(error)) | ||
372 | 183 | |||
373 | 184 | def download(_, path): | ||
374 | 185 | self.assertTrue(path.startswith(self.download_path)) | ||
375 | 186 | with open(path, "wb") as f: | ||
376 | 187 | f.write(self.bundle_data) | ||
377 | 185 | return succeed(None) | 188 | return succeed(None) |
378 | 186 | self.mocker.call(download) | 189 | self.mocker.call(download) |
429 | 187 | self.mocker.replay() | 190 | |
430 | 188 | 191 | @inlineCallbacks | |
431 | 189 | repo = self.repo(dns_name) | 192 | def assert_find_uncached(self, dns_name, url_str, info_url, find_url): |
432 | 190 | charm = yield repo.find(CharmURL.parse(url_str)) | 193 | self.mock_charm_info(info_url, succeed(self.charm_info(url_str, 1))) |
433 | 191 | self.assertEquals(charm.get_sha256(), src_charm.get_sha256()) | 194 | self.mock_download(find_url) |
434 | 192 | self.assertEquals(os.listdir(download_dir), []) | 195 | self.mocker.replay() |
435 | 193 | expect_name = "%s-%s.charm" % ( | 196 | |
436 | 194 | under.quote(url_str), charm.metadata.revision) | 197 | repo = self.repo(dns_name) |
437 | 195 | self.assertEquals( | 198 | charm = yield repo.find(CharmURL.parse(url_str)) |
438 | 196 | charm.path, os.path.join(self.cache_path, expect_name)) | 199 | self.assertEquals(charm.get_sha256(), self.sha256) |
439 | 197 | 200 | self.assertEquals(charm.path, self.cache_location(url_str, 1)) | |
440 | 198 | def test_find_plain(self): | 201 | self.assertEquals(os.listdir(self.download_path), []) |
441 | 199 | return self.assert_find( | 202 | |
442 | 200 | "https://somewhe.re", "cs:series/name", | 203 | @inlineCallbacks |
443 | 201 | "https://somewhe.re/charm/series/name") | 204 | def assert_find_cached(self, dns_name, url_str, info_url): |
444 | 202 | 205 | os.makedirs(self.cache_path) | |
445 | 203 | def test_find_user(self): | 206 | cache_location = self.cache_location(url_str, 1) |
446 | 204 | return self.assert_find( | 207 | shutil.copy(self.charm.as_bundle().path, cache_location) |
447 | 205 | "https://somewhereel.se", "cs:~user/series/name", | 208 | |
448 | 206 | "https://somewhereel.se/charm/~user/series/name") | 209 | self.mock_charm_info(info_url, succeed(self.charm_info(url_str, 1))) |
449 | 207 | 210 | self.mocker.replay() | |
450 | 208 | def test_cant_find(self): | 211 | |
451 | 209 | downloadPage = self.mocker.replace("twisted.web.client.downloadPage") | 212 | repo = self.repo(dns_name) |
452 | 210 | downloadPage("https://anoth.er/charm/series/name", ANY) | 213 | charm = yield repo.find(CharmURL.parse(url_str)) |
453 | 211 | self.mocker.result(fail(Error("500"))) | 214 | self.assertEquals(charm.get_sha256(), self.sha256) |
454 | 212 | self.mocker.replay() | 215 | self.assertEquals(charm.path, cache_location) |
455 | 213 | 216 | ||
456 | 214 | repo = RemoteCharmRepository("https://anoth.er") | 217 | def assert_find_error(self, dns_name, url_str, err_type, message): |
457 | 215 | d = self.assertFailure( | 218 | self.mocker.replay() |
458 | 216 | repo.find(CharmURL.parse("cs:series/name")), | 219 | repo = self.repo(dns_name) |
459 | 217 | CharmNotFound) | 220 | d = self.assertFailure(repo.find(CharmURL.parse(url_str)), err_type) |
460 | 218 | 221 | ||
461 | 219 | def verify(error): | 222 | def verify(error): |
462 | 220 | self.assertEquals( | 223 | self.assertEquals(str(error), message) |
463 | 221 | str(error), | 224 | d.addCallback(verify) |
464 | 222 | "Charm 'cs:series/name' not found in repository " | 225 | return d |
465 | 223 | "https://anoth.er") | 226 | |
466 | 224 | d.addCallback(verify) | 227 | @inlineCallbacks |
467 | 225 | return d | 228 | def assert_latest(self, dns_name, url_str, revision): |
468 | 226 | 229 | self.mocker.replay() | |
469 | 227 | def test_revision(self): | 230 | repo = self.repo(dns_name) |
470 | 228 | repo = RemoteCharmRepository("whatev.er") | 231 | result = yield repo.latest(CharmURL.parse(url_str)) |
471 | 229 | d = self.assertFailure( | 232 | self.assertEquals(result, revision) |
472 | 230 | repo.find(CharmURL.parse("cs:series/name-1")), | 233 | |
473 | 231 | AssertionError) | 234 | def assert_latest_error(self, dns_name, url_str, err_type, message): |
474 | 232 | 235 | self.mocker.replay() | |
475 | 233 | def verify(error): | 236 | repo = self.repo(dns_name) |
476 | 234 | self.assertEquals(str(error), "find-by-revision not supported yet") | 237 | d = self.assertFailure(repo.latest(CharmURL.parse(url_str)), err_type) |
477 | 235 | d.addCallback(verify) | 238 | |
478 | 236 | return d | 239 | def verify(error): |
479 | 240 | self.assertEquals(str(error), message) | ||
480 | 241 | d.addCallback(verify) | ||
481 | 242 | return d | ||
482 | 243 | |||
483 | 244 | def test_find_plain_uncached(self): | ||
484 | 245 | return self.assert_find_uncached( | ||
485 | 246 | "https://somewhe.re", "cs:series/name", | ||
486 | 247 | "https://somewhe.re/charm-info?charms=cs%3Aseries/name", | ||
487 | 248 | "https://somewhe.re/charm/series/name-1") | ||
488 | 249 | |||
489 | 250 | def test_find_revision_uncached(self): | ||
490 | 251 | return self.assert_find_uncached( | ||
491 | 252 | "https://somewhe.re", "cs:series/name-1", | ||
492 | 253 | "https://somewhe.re/charm-info?charms=cs%3Aseries/name-1", | ||
493 | 254 | "https://somewhe.re/charm/series/name-1") | ||
494 | 255 | |||
495 | 256 | def test_find_user_uncached(self): | ||
496 | 257 | return self.assert_find_uncached( | ||
497 | 258 | "https://somewhereel.se", "cs:~user/srs/name", | ||
498 | 259 | "https://somewhereel.se/charm-info?charms=cs%3A%7Euser/srs/name", | ||
499 | 260 | "https://somewhereel.se/charm/%7Euser/srs/name-1") | ||
500 | 261 | |||
501 | 262 | def test_find_plain_cached(self): | ||
502 | 263 | return self.assert_find_cached( | ||
503 | 264 | "https://somewhe.re", "cs:series/name", | ||
504 | 265 | "https://somewhe.re/charm-info?charms=cs%3Aseries/name") | ||
505 | 266 | |||
506 | 267 | def test_find_revision_cached(self): | ||
507 | 268 | return self.assert_find_cached( | ||
508 | 269 | "https://somewhe.re", "cs:series/name-1", | ||
509 | 270 | "https://somewhe.re/charm-info?charms=cs%3Aseries/name-1") | ||
510 | 271 | |||
511 | 272 | def test_find_user_cached(self): | ||
512 | 273 | return self.assert_find_cached( | ||
513 | 274 | "https://somewhereel.se", "cs:~user/srs/name", | ||
514 | 275 | "https://somewhereel.se/charm-info?charms=cs%3A%7Euser/srs/name") | ||
515 | 276 | |||
516 | 277 | def test_find_info_error(self): | ||
517 | 278 | self.mock_charm_info( | ||
518 | 279 | "https://anoth.er/charm-info?charms=cs%3Aseries/name", | ||
519 | 280 | fail(Error("500"))) | ||
520 | 281 | return self.assert_find_error( | ||
521 | 282 | "https://anoth.er", "cs:series/name", CharmNotFound, | ||
522 | 283 | "Charm 'cs:series/name' not found in repository https://anoth.er") | ||
523 | 284 | |||
524 | 285 | def test_find_info_bad_revision(self): | ||
525 | 286 | self.mock_charm_info( | ||
526 | 287 | "https://anoth.er/charm-info?charms=cs%3Aseries/name-99", | ||
527 | 288 | succeed(self.charm_info("cs:series/name-99", 1))) | ||
528 | 289 | return self.assert_find_error( | ||
529 | 290 | "https://anoth.er", "cs:series/name-99", AssertionError, | ||
530 | 291 | "bad url revision") | ||
531 | 292 | |||
532 | 293 | def test_find_download_error(self): | ||
533 | 294 | self.mock_charm_info( | ||
534 | 295 | "https://anoth.er/charm-info?charms=cs%3Aseries/name", | ||
535 | 296 | succeed(json.dumps({"cs:series/name": {"revision": 123}}))) | ||
536 | 297 | self.mock_download( | ||
537 | 298 | "https://anoth.er/charm/series/name-123", Error("999")) | ||
538 | 299 | return self.assert_find_error( | ||
539 | 300 | "https://anoth.er", "cs:series/name", CharmNotFound, | ||
540 | 301 | "Charm 'cs:series/name-123' not found in repository " | ||
541 | 302 | "https://anoth.er") | ||
542 | 303 | |||
543 | 304 | def test_find_charm_revision_mismatch(self): | ||
544 | 305 | self.mock_charm_info( | ||
545 | 306 | "https://anoth.er/charm-info?charms=cs%3Aseries/name", | ||
546 | 307 | succeed(json.dumps({"cs:series/name": {"revision": 99}}))) | ||
547 | 308 | self.mock_download("https://anoth.er/charm/series/name-99") | ||
548 | 309 | return self.assert_find_error( | ||
549 | 310 | "https://anoth.er", "cs:series/name", AssertionError, | ||
550 | 311 | "bad charm revision") | ||
551 | 312 | |||
552 | 313 | @inlineCallbacks | ||
553 | 314 | def test_find_downloaded_hash_mismatch(self): | ||
554 | 315 | cache_location = self.cache_location("cs:series/name-1", 1) | ||
555 | 316 | self.mock_charm_info( | ||
556 | 317 | "https://anoth.er/charm-info?charms=cs%3Aseries/name", | ||
557 | 318 | succeed(json.dumps( | ||
558 | 319 | {"cs:series/name": {"revision": 1, "sha256": "NO YUO"}}))) | ||
559 | 320 | self.mock_download("https://anoth.er/charm/series/name-1") | ||
560 | 321 | yield self.assert_find_error( | ||
561 | 322 | "https://anoth.er", "cs:series/name", CharmError, | ||
562 | 323 | "Error processing 'cs:series/name-1 (downloaded)': SHA256 " | ||
563 | 324 | "mismatch") | ||
564 | 325 | self.assertFalse(os.path.exists(cache_location)) | ||
565 | 326 | |||
566 | 327 | @inlineCallbacks | ||
567 | 328 | def test_find_cached_hash_mismatch(self): | ||
568 | 329 | os.makedirs(self.cache_path) | ||
569 | 330 | cache_location = self.cache_location("cs:series/name-1", 1) | ||
570 | 331 | shutil.copy(self.charm.as_bundle().path, cache_location) | ||
571 | 332 | |||
572 | 333 | self.mock_charm_info( | ||
573 | 334 | "https://anoth.er/charm-info?charms=cs%3Aseries/name", | ||
574 | 335 | succeed(json.dumps( | ||
575 | 336 | {"cs:series/name": {"revision": 1, "sha256": "NO YUO"}}))) | ||
576 | 337 | yield self.assert_find_error( | ||
577 | 338 | "https://anoth.er", "cs:series/name", CharmError, | ||
578 | 339 | "Error processing 'cs:series/name-1 (cached)': SHA256 mismatch") | ||
579 | 340 | self.assertFalse(os.path.exists(cache_location)) | ||
580 | 341 | |||
581 | 342 | def test_latest_plain(self): | ||
582 | 343 | self.mock_charm_info( | ||
583 | 344 | "https://somewhe.re/charm-info?charms=cs%3Afoo/bar", | ||
584 | 345 | succeed(self.charm_info("cs:foo/bar", 99))) | ||
585 | 346 | return self.assert_latest("https://somewhe.re", "cs:foo/bar", 99) | ||
586 | 347 | |||
587 | 348 | def test_latest_user(self): | ||
588 | 349 | self.mock_charm_info( | ||
589 | 350 | "https://somewhereel.se/charm-info?charms=cs%3A%7Efee/foo/bar", | ||
590 | 351 | succeed(self.charm_info("cs:~fee/foo/bar", 123))) | ||
591 | 352 | return self.assert_latest( | ||
592 | 353 | "https://somewhereel.se", "cs:~fee/foo/bar", 123) | ||
593 | 354 | |||
594 | 355 | def test_latest_revision(self): | ||
595 | 356 | self.mock_charm_info( | ||
596 | 357 | "https://somewhereel.se/charm-info?charms=cs%3A%7Efee/foo/bar", | ||
597 | 358 | succeed(self.charm_info("cs:~fee/foo/bar", 123))) | ||
598 | 359 | return self.assert_latest( | ||
599 | 360 | "https://somewhereel.se", "cs:~fee/foo/bar-99", 123) | ||
600 | 361 | |||
601 | 362 | def test_latest_error(self): | ||
602 | 363 | self.mock_charm_info( | ||
603 | 364 | "https://andanoth.er/charm-info?charms=cs%3A%7Eblib/blab/blob", | ||
604 | 365 | fail(Error("404"))) | ||
605 | 366 | return self.assert_latest_error( | ||
606 | 367 | "https://andanoth.er", "cs:~blib/blab/blob", CharmNotFound, | ||
607 | 368 | "Charm 'cs:~blib/blab/blob' not found in repository " | ||
608 | 369 | "https://andanoth.er") | ||
609 | 237 | 370 | ||
610 | 238 | 371 | ||
611 | 239 | class ResolveTest(RepositoryTestBase): | 372 | class ResolveTest(RepositoryTestBase): |
612 | 240 | 373 | ||
671 | 241 | @inlineCallbacks | 374 | def assert_resolve_local(self, vague, default, expect): |
672 | 242 | def test_resolve_local_with_collection(self): | 375 | repo, url = resolve(vague, "/some/path", default) |
673 | 243 | url, charm = yield resolve( | 376 | self.assertEquals(str(url), expect) |
674 | 244 | "local:series/sample", self.unbundled_repo_path, "series") | 377 | self.assertTrue(isinstance(repo, LocalCharmRepository)) |
675 | 245 | self.assertEquals(str(url), "local:series/sample") | 378 | self.assertEquals(repo.path, "/some/path") |
676 | 246 | self.assertEquals(charm.metadata.revision, 2) | 379 | |
677 | 247 | 380 | def test_resolve_local(self): | |
678 | 248 | @inlineCallbacks | 381 | self.assert_resolve_local( |
679 | 249 | def test_resolve_local_no_collection(self): | 382 | "local:series/sample", "default", "local:series/sample") |
680 | 250 | url, charm = yield resolve( | 383 | self.assert_resolve_local( |
681 | 251 | "local:sample", self.unbundled_repo_path, "series") | 384 | "local:sample", "default", "local:default/sample") |
682 | 252 | self.assertEquals(str(url), "local:series/sample") | 385 | |
683 | 253 | self.assertEquals(charm.metadata.revision, 2) | 386 | def assert_resolve_remote(self, vague, default, expect): |
684 | 254 | 387 | repo, url = resolve(vague, None, default) | |
685 | 255 | @inlineCallbacks | 388 | self.assertEquals(str(url), expect) |
686 | 256 | def test_resolve_local_alternative_collection(self): | 389 | self.assertTrue(isinstance(repo, RemoteCharmRepository)) |
687 | 257 | url, charm = yield resolve( | 390 | self.assertEquals(repo.url_base, "https://store.juju.ubuntu.com") |
688 | 258 | "local:series/sample", self.unbundled_repo_path, "otherseries") | 391 | |
689 | 259 | self.assertEquals(str(url), "local:series/sample") | 392 | def test_resolve_remote(self): |
690 | 260 | self.assertEquals(charm.metadata.revision, 2) | 393 | self.assert_resolve_remote( |
691 | 261 | 394 | "sample", "default", "cs:default/sample") | |
692 | 262 | def assert_not_found(self, name, default_series): | 395 | self.assert_resolve_remote( |
693 | 263 | downloadPage = self.mocker.replace("twisted.web.client.downloadPage") | 396 | "series/sample", "default", "cs:series/sample") |
694 | 264 | downloadPage("https://store.juju.ubuntu.com/charm/series/whatever", ANY) | 397 | self.assert_resolve_remote( |
695 | 265 | self.mocker.result(fail(Error("404"))) | 398 | "cs:sample", "default", "cs:default/sample") |
696 | 266 | self.mocker.replay() | 399 | self.assert_resolve_remote( |
697 | 267 | 400 | "cs:series/sample", "default", "cs:series/sample") | |
698 | 268 | d = self.assertFailure( | 401 | self.assert_resolve_remote( |
699 | 269 | resolve(name, None, default_series), CharmNotFound) | 402 | "cs:~user/sample", "default", "cs:~user/default/sample") |
700 | 270 | 403 | self.assert_resolve_remote( | |
701 | 271 | def verify(error): | 404 | "cs:~user/series/sample", "default", "cs:~user/series/sample") |
702 | 272 | self.assertEquals( | 405 | |
703 | 273 | str(error), | 406 | def test_resolve_nonsense(self): |
704 | 274 | "Charm 'cs:series/whatever' not found in repository " | 407 | error = self.assertRaises( |
705 | 275 | "https://store.juju.ubuntu.com") | 408 | CharmURLError, resolve, "blah:whatever", None, "series") |
706 | 276 | d.addCallback(verify) | 409 | self.assertEquals( |
707 | 277 | return d | 410 | str(error), |
708 | 278 | 411 | "Bad charm URL 'blah:series/whatever': invalid schema (URL " | |
709 | 279 | def test_resolve_minimal(self): | 412 | "inferred from 'blah:whatever')") |
652 | 280 | return self.assert_not_found("whatever", "series") | ||
653 | 281 | |||
654 | 282 | def test_resolve_no_root(self): | ||
655 | 283 | return self.assert_not_found("series/whatever", "series") | ||
656 | 284 | |||
657 | 285 | def test_resolve_with_root(self): | ||
658 | 286 | return self.assert_not_found("cs:series/whatever", "series") | ||
659 | 287 | |||
660 | 288 | def test_resolve_nonsense_root(self): | ||
661 | 289 | d = self.assertFailure( | ||
662 | 290 | resolve("blah:whatever", None, "series"), CharmURLError) | ||
663 | 291 | |||
664 | 292 | def verify(error): | ||
665 | 293 | self.assertEquals( | ||
666 | 294 | str(error), | ||
667 | 295 | "Bad charm URL 'blah:series/whatever': invalid schema (URL " | ||
668 | 296 | "inferred from 'blah:whatever')") | ||
669 | 297 | d.addCallback(verify) | ||
670 | 298 | return d | ||
710 | 299 | 413 | ||
711 | === modified file 'juju/charm/tests/test_url.py' | |||
712 | --- juju/charm/tests/test_url.py 2011-09-29 03:07:26 +0000 | |||
713 | +++ juju/charm/tests/test_url.py 2011-10-05 10:00:46 +0000 | |||
714 | @@ -29,6 +29,7 @@ | |||
715 | 29 | url = CharmURL.parse(string) | 29 | url = CharmURL.parse(string) |
716 | 30 | self.assert_url(url, schema, user, series, name, rev) | 30 | self.assert_url(url, schema, user, series, name, rev) |
717 | 31 | self.assertEquals(str(url), string) | 31 | self.assertEquals(str(url), string) |
718 | 32 | self.assertEquals(url.path, string.split(":", 1)[1]) | ||
719 | 32 | 33 | ||
720 | 33 | def test_parse(self): | 34 | def test_parse(self): |
721 | 34 | self.assert_parse( | 35 | self.assert_parse( |
722 | 35 | 36 | ||
723 | === modified file 'juju/charm/url.py' | |||
724 | --- juju/charm/url.py 2011-09-29 03:07:26 +0000 | |||
725 | +++ juju/charm/url.py 2011-10-05 10:00:46 +0000 | |||
726 | @@ -56,6 +56,10 @@ | |||
727 | 56 | return "%s/%s" % (self.collection, self.name) | 56 | return "%s/%s" % (self.collection, self.name) |
728 | 57 | return "%s/%s-%s" % (self.collection, self.name, self.revision) | 57 | return "%s/%s-%s" % (self.collection, self.name, self.revision) |
729 | 58 | 58 | ||
730 | 59 | @property | ||
731 | 60 | def path(self): | ||
732 | 61 | return str(self).split(":", 1)[1] | ||
733 | 62 | |||
734 | 59 | def with_revision(self, revision): | 63 | def with_revision(self, revision): |
735 | 60 | other = copy.deepcopy(self) | 64 | other = copy.deepcopy(self) |
736 | 61 | other.revision = revision | 65 | other.revision = revision |
737 | 62 | 66 | ||
738 | === modified file 'juju/control/deploy.py' | |||
739 | --- juju/control/deploy.py 2011-09-30 09:48:25 +0000 | |||
740 | +++ juju/control/deploy.py 2011-10-05 10:00:46 +0000 | |||
741 | @@ -81,12 +81,11 @@ | |||
742 | 81 | service_name, log, config_file=None): | 81 | service_name, log, config_file=None): |
743 | 82 | """Deploy a charm within an environment. | 82 | """Deploy a charm within an environment. |
744 | 83 | 83 | ||
745 | 84 | |||
746 | 85 | This will publish the charm to the environment, creating | 84 | This will publish the charm to the environment, creating |
747 | 86 | a service from the charm, and get it set to be launched | 85 | a service from the charm, and get it set to be launched |
748 | 87 | on a new machine. | 86 | on a new machine. |
749 | 88 | """ | 87 | """ |
751 | 89 | charm_url, charm = yield resolve( | 88 | repo, charm_url = resolve( |
752 | 90 | charm_name, repository_path, environment.default_series) | 89 | charm_name, repository_path, environment.default_series) |
753 | 91 | 90 | ||
754 | 92 | # Validate config options prior to deployment attempt | 91 | # Validate config options prior to deployment attempt |
755 | @@ -95,6 +94,9 @@ | |||
756 | 95 | if config_file: | 94 | if config_file: |
757 | 96 | service_options = parse_config_options(config_file, service_name) | 95 | service_options = parse_config_options(config_file, service_name) |
758 | 97 | 96 | ||
759 | 97 | charm = yield repo.find(charm_url) | ||
760 | 98 | charm_id = str(charm_url.with_revision(charm.metadata.revision)) | ||
761 | 99 | |||
762 | 98 | provider = environment.get_machine_provider() | 100 | provider = environment.get_machine_provider() |
763 | 99 | placement_policy = provider.get_placement_policy() | 101 | placement_policy = provider.get_placement_policy() |
764 | 100 | client = yield provider.connect() | 102 | client = yield provider.connect() |
765 | @@ -108,7 +110,6 @@ | |||
766 | 108 | 110 | ||
767 | 109 | # Publish the charm to juju | 111 | # Publish the charm to juju |
768 | 110 | publisher = CharmPublisher(client, storage) | 112 | publisher = CharmPublisher(client, storage) |
769 | 111 | charm_id = str(charm_url.with_revision(charm.metadata.revision)) | ||
770 | 112 | yield publisher.add_charm(charm_id, charm) | 113 | yield publisher.add_charm(charm_id, charm) |
771 | 113 | result = yield publisher.publish() | 114 | result = yield publisher.publish() |
772 | 114 | 115 | ||
773 | 115 | 116 | ||
774 | === modified file 'juju/control/upgrade_charm.py' | |||
775 | --- juju/control/upgrade_charm.py 2011-09-29 03:07:26 +0000 | |||
776 | +++ juju/control/upgrade_charm.py 2011-10-05 10:00:46 +0000 | |||
777 | @@ -64,11 +64,12 @@ | |||
778 | 64 | 64 | ||
779 | 65 | old_charm_url = CharmURL.parse(old_charm_id) | 65 | old_charm_url = CharmURL.parse(old_charm_id) |
780 | 66 | old_charm_url.assert_revision() | 66 | old_charm_url.assert_revision() |
782 | 67 | charm_url, charm = yield resolve( | 67 | repo, charm_url = resolve( |
783 | 68 | str(old_charm_url.with_revision(None)), | 68 | str(old_charm_url.with_revision(None)), |
784 | 69 | repository_path, | 69 | repository_path, |
785 | 70 | environment.default_series) | 70 | environment.default_series) |
787 | 71 | new_charm_url = charm_url.with_revision(charm.metadata.revision) | 71 | new_charm_url = charm_url.with_revision( |
788 | 72 | (yield repo.latest(charm_url))) | ||
789 | 72 | new_charm_id = str(new_charm_url) | 73 | new_charm_id = str(new_charm_url) |
790 | 73 | 74 | ||
791 | 74 | # Verify its newer than what's deployed | 75 | # Verify its newer than what's deployed |
792 | @@ -86,6 +87,7 @@ | |||
793 | 86 | # Publish the new charm | 87 | # Publish the new charm |
794 | 87 | storage = provider.get_file_storage() | 88 | storage = provider.get_file_storage() |
795 | 88 | publisher = CharmPublisher(client, storage) | 89 | publisher = CharmPublisher(client, storage) |
796 | 90 | charm = yield repo.find(charm_url) | ||
797 | 89 | yield publisher.add_charm(new_charm_id, charm) | 91 | yield publisher.add_charm(new_charm_id, charm) |
798 | 90 | result = yield publisher.publish() | 92 | result = yield publisher.publish() |
799 | 91 | charm_state = result[0] | 93 | charm_state = result[0] |
Based on request for a cache dir in https:/ /code.launchpad .net/~fwereade/ juju/use- remote- formulas/ +merge/ 77323, this branch now actually uses the cache. It assumes that a charm store implementation will guarantee that a charm-url- with-revision uniquely identifies a given charm, but I think that's a reasonable thing to demand ;-).