Merge lp:~gary/charms/precise/juju-gui/update into lp:charms/juju-gui
- Precise Pangolin (12.04)
- update
- Merge into trunk
Proposed by
Gary Poster
Status: | Merged |
---|---|
Merged at revision: | 67 |
Proposed branch: | lp:~gary/charms/precise/juju-gui/update |
Merge into: | lp:charms/juju-gui |
Diff against target: |
2940 lines (+2185/-234) 31 files modified
.bzrignore (+1/-0) .lbox (+1/-0) .lbox.check (+3/-0) COPYING (+661/-0) Dependencies.md (+9/-2) HACKING.md (+70/-82) Makefile (+63/-0) README.md (+10/-1) config.yaml (+44/-1) config/config.js.template (+4/-1) copyright (+3/-3) hooks/backend.py (+21/-1) hooks/bootstrap_utils.py (+16/-0) hooks/config-changed (+15/-2) hooks/install (+16/-0) hooks/start (+16/-0) hooks/stop (+16/-0) hooks/utils.py (+40/-9) hooks/web-relation-joined (+16/-0) metadata.yaml (+16/-0) revision (+1/-1) tests/00-setup (+36/-0) tests/10-unit.test (+21/-3) tests/20-functional.test (+61/-99) tests/deploy.py (+80/-0) tests/helpers.py (+185/-0) tests/requirements.pip (+28/-0) tests/test_backends.py (+24/-6) tests/test_deploy.py (+196/-0) tests/test_helpers.py (+397/-0) tests/test_utils.py (+115/-23) |
To merge this branch: | bzr merge lp:~gary/charms/precise/juju-gui/update |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gary Poster (community) | Approve | ||
Review via email: mp+175407@code.launchpad.net |
Commit message
Merge work from ~juju-gui. This adds support for a number of GUI options, license file changes, and new test infrastructure.
Description of the change
Merge work from ~juju-gui. This adds support for a number of GUI options, license file changes, and new test infrastructure.
To post a comment you must log in.
Revision history for this message
Gary Poster (gary) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2013-05-02 09:49:51 +0000 |
3 | +++ .bzrignore 2013-07-17 21:35:43 +0000 |
4 | @@ -2,3 +2,4 @@ |
5 | tags |
6 | exec.d/* |
7 | npm-cache.tgz |
8 | +tests/.venv |
9 | |
10 | === added file '.lbox' |
11 | --- .lbox 1970-01-01 00:00:00 +0000 |
12 | +++ .lbox 2013-07-17 21:35:43 +0000 |
13 | @@ -0,0 +1,1 @@ |
14 | +propose -cr -for lp:~juju-gui/charms/precise/juju-gui/trunk |
15 | |
16 | === added file '.lbox.check' |
17 | --- .lbox.check 1970-01-01 00:00:00 +0000 |
18 | +++ .lbox.check 2013-07-17 21:35:43 +0000 |
19 | @@ -0,0 +1,3 @@ |
20 | +#!/bin/sh |
21 | + |
22 | +make lint unittest |
23 | |
24 | === added file 'COPYING' |
25 | --- COPYING 1970-01-01 00:00:00 +0000 |
26 | +++ COPYING 2013-07-17 21:35:43 +0000 |
27 | @@ -0,0 +1,661 @@ |
28 | + GNU AFFERO GENERAL PUBLIC LICENSE |
29 | + Version 3, 19 November 2007 |
30 | + |
31 | + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> |
32 | + Everyone is permitted to copy and distribute verbatim copies |
33 | + of this license document, but changing it is not allowed. |
34 | + |
35 | + Preamble |
36 | + |
37 | + The GNU Affero General Public License is a free, copyleft license for |
38 | +software and other kinds of works, specifically designed to ensure |
39 | +cooperation with the community in the case of network server software. |
40 | + |
41 | + The licenses for most software and other practical works are designed |
42 | +to take away your freedom to share and change the works. By contrast, |
43 | +our General Public Licenses are intended to guarantee your freedom to |
44 | +share and change all versions of a program--to make sure it remains free |
45 | +software for all its users. |
46 | + |
47 | + When we speak of free software, we are referring to freedom, not |
48 | +price. Our General Public Licenses are designed to make sure that you |
49 | +have the freedom to distribute copies of free software (and charge for |
50 | +them if you wish), that you receive source code or can get it if you |
51 | +want it, that you can change the software or use pieces of it in new |
52 | +free programs, and that you know you can do these things. |
53 | + |
54 | + Developers that use our General Public Licenses protect your rights |
55 | +with two steps: (1) assert copyright on the software, and (2) offer |
56 | +you this License which gives you legal permission to copy, distribute |
57 | +and/or modify the software. |
58 | + |
59 | + A secondary benefit of defending all users' freedom is that |
60 | +improvements made in alternate versions of the program, if they |
61 | +receive widespread use, become available for other developers to |
62 | +incorporate. Many developers of free software are heartened and |
63 | +encouraged by the resulting cooperation. However, in the case of |
64 | +software used on network servers, this result may fail to come about. |
65 | +The GNU General Public License permits making a modified version and |
66 | +letting the public access it on a server without ever releasing its |
67 | +source code to the public. |
68 | + |
69 | + The GNU Affero General Public License is designed specifically to |
70 | +ensure that, in such cases, the modified source code becomes available |
71 | +to the community. It requires the operator of a network server to |
72 | +provide the source code of the modified version running there to the |
73 | +users of that server. Therefore, public use of a modified version, on |
74 | +a publicly accessible server, gives the public access to the source |
75 | +code of the modified version. |
76 | + |
77 | + An older license, called the Affero General Public License and |
78 | +published by Affero, was designed to accomplish similar goals. This is |
79 | +a different license, not a version of the Affero GPL, but Affero has |
80 | +released a new version of the Affero GPL which permits relicensing under |
81 | +this license. |
82 | + |
83 | + The precise terms and conditions for copying, distribution and |
84 | +modification follow. |
85 | + |
86 | + TERMS AND CONDITIONS |
87 | + |
88 | + 0. Definitions. |
89 | + |
90 | + "This License" refers to version 3 of the GNU Affero General Public License. |
91 | + |
92 | + "Copyright" also means copyright-like laws that apply to other kinds of |
93 | +works, such as semiconductor masks. |
94 | + |
95 | + "The Program" refers to any copyrightable work licensed under this |
96 | +License. Each licensee is addressed as "you". "Licensees" and |
97 | +"recipients" may be individuals or organizations. |
98 | + |
99 | + To "modify" a work means to copy from or adapt all or part of the work |
100 | +in a fashion requiring copyright permission, other than the making of an |
101 | +exact copy. The resulting work is called a "modified version" of the |
102 | +earlier work or a work "based on" the earlier work. |
103 | + |
104 | + A "covered work" means either the unmodified Program or a work based |
105 | +on the Program. |
106 | + |
107 | + To "propagate" a work means to do anything with it that, without |
108 | +permission, would make you directly or secondarily liable for |
109 | +infringement under applicable copyright law, except executing it on a |
110 | +computer or modifying a private copy. Propagation includes copying, |
111 | +distribution (with or without modification), making available to the |
112 | +public, and in some countries other activities as well. |
113 | + |
114 | + To "convey" a work means any kind of propagation that enables other |
115 | +parties to make or receive copies. Mere interaction with a user through |
116 | +a computer network, with no transfer of a copy, is not conveying. |
117 | + |
118 | + An interactive user interface displays "Appropriate Legal Notices" |
119 | +to the extent that it includes a convenient and prominently visible |
120 | +feature that (1) displays an appropriate copyright notice, and (2) |
121 | +tells the user that there is no warranty for the work (except to the |
122 | +extent that warranties are provided), that licensees may convey the |
123 | +work under this License, and how to view a copy of this License. If |
124 | +the interface presents a list of user commands or options, such as a |
125 | +menu, a prominent item in the list meets this criterion. |
126 | + |
127 | + 1. Source Code. |
128 | + |
129 | + The "source code" for a work means the preferred form of the work |
130 | +for making modifications to it. "Object code" means any non-source |
131 | +form of a work. |
132 | + |
133 | + A "Standard Interface" means an interface that either is an official |
134 | +standard defined by a recognized standards body, or, in the case of |
135 | +interfaces specified for a particular programming language, one that |
136 | +is widely used among developers working in that language. |
137 | + |
138 | + The "System Libraries" of an executable work include anything, other |
139 | +than the work as a whole, that (a) is included in the normal form of |
140 | +packaging a Major Component, but which is not part of that Major |
141 | +Component, and (b) serves only to enable use of the work with that |
142 | +Major Component, or to implement a Standard Interface for which an |
143 | +implementation is available to the public in source code form. A |
144 | +"Major Component", in this context, means a major essential component |
145 | +(kernel, window system, and so on) of the specific operating system |
146 | +(if any) on which the executable work runs, or a compiler used to |
147 | +produce the work, or an object code interpreter used to run it. |
148 | + |
149 | + The "Corresponding Source" for a work in object code form means all |
150 | +the source code needed to generate, install, and (for an executable |
151 | +work) run the object code and to modify the work, including scripts to |
152 | +control those activities. However, it does not include the work's |
153 | +System Libraries, or general-purpose tools or generally available free |
154 | +programs which are used unmodified in performing those activities but |
155 | +which are not part of the work. For example, Corresponding Source |
156 | +includes interface definition files associated with source files for |
157 | +the work, and the source code for shared libraries and dynamically |
158 | +linked subprograms that the work is specifically designed to require, |
159 | +such as by intimate data communication or control flow between those |
160 | +subprograms and other parts of the work. |
161 | + |
162 | + The Corresponding Source need not include anything that users |
163 | +can regenerate automatically from other parts of the Corresponding |
164 | +Source. |
165 | + |
166 | + The Corresponding Source for a work in source code form is that |
167 | +same work. |
168 | + |
169 | + 2. Basic Permissions. |
170 | + |
171 | + All rights granted under this License are granted for the term of |
172 | +copyright on the Program, and are irrevocable provided the stated |
173 | +conditions are met. This License explicitly affirms your unlimited |
174 | +permission to run the unmodified Program. The output from running a |
175 | +covered work is covered by this License only if the output, given its |
176 | +content, constitutes a covered work. This License acknowledges your |
177 | +rights of fair use or other equivalent, as provided by copyright law. |
178 | + |
179 | + You may make, run and propagate covered works that you do not |
180 | +convey, without conditions so long as your license otherwise remains |
181 | +in force. You may convey covered works to others for the sole purpose |
182 | +of having them make modifications exclusively for you, or provide you |
183 | +with facilities for running those works, provided that you comply with |
184 | +the terms of this License in conveying all material for which you do |
185 | +not control copyright. Those thus making or running the covered works |
186 | +for you must do so exclusively on your behalf, under your direction |
187 | +and control, on terms that prohibit them from making any copies of |
188 | +your copyrighted material outside their relationship with you. |
189 | + |
190 | + Conveying under any other circumstances is permitted solely under |
191 | +the conditions stated below. Sublicensing is not allowed; section 10 |
192 | +makes it unnecessary. |
193 | + |
194 | + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. |
195 | + |
196 | + No covered work shall be deemed part of an effective technological |
197 | +measure under any applicable law fulfilling obligations under article |
198 | +11 of the WIPO copyright treaty adopted on 20 December 1996, or |
199 | +similar laws prohibiting or restricting circumvention of such |
200 | +measures. |
201 | + |
202 | + When you convey a covered work, you waive any legal power to forbid |
203 | +circumvention of technological measures to the extent such circumvention |
204 | +is effected by exercising rights under this License with respect to |
205 | +the covered work, and you disclaim any intention to limit operation or |
206 | +modification of the work as a means of enforcing, against the work's |
207 | +users, your or third parties' legal rights to forbid circumvention of |
208 | +technological measures. |
209 | + |
210 | + 4. Conveying Verbatim Copies. |
211 | + |
212 | + You may convey verbatim copies of the Program's source code as you |
213 | +receive it, in any medium, provided that you conspicuously and |
214 | +appropriately publish on each copy an appropriate copyright notice; |
215 | +keep intact all notices stating that this License and any |
216 | +non-permissive terms added in accord with section 7 apply to the code; |
217 | +keep intact all notices of the absence of any warranty; and give all |
218 | +recipients a copy of this License along with the Program. |
219 | + |
220 | + You may charge any price or no price for each copy that you convey, |
221 | +and you may offer support or warranty protection for a fee. |
222 | + |
223 | + 5. Conveying Modified Source Versions. |
224 | + |
225 | + You may convey a work based on the Program, or the modifications to |
226 | +produce it from the Program, in the form of source code under the |
227 | +terms of section 4, provided that you also meet all of these conditions: |
228 | + |
229 | + a) The work must carry prominent notices stating that you modified |
230 | + it, and giving a relevant date. |
231 | + |
232 | + b) The work must carry prominent notices stating that it is |
233 | + released under this License and any conditions added under section |
234 | + 7. This requirement modifies the requirement in section 4 to |
235 | + "keep intact all notices". |
236 | + |
237 | + c) You must license the entire work, as a whole, under this |
238 | + License to anyone who comes into possession of a copy. This |
239 | + License will therefore apply, along with any applicable section 7 |
240 | + additional terms, to the whole of the work, and all its parts, |
241 | + regardless of how they are packaged. This License gives no |
242 | + permission to license the work in any other way, but it does not |
243 | + invalidate such permission if you have separately received it. |
244 | + |
245 | + d) If the work has interactive user interfaces, each must display |
246 | + Appropriate Legal Notices; however, if the Program has interactive |
247 | + interfaces that do not display Appropriate Legal Notices, your |
248 | + work need not make them do so. |
249 | + |
250 | + A compilation of a covered work with other separate and independent |
251 | +works, which are not by their nature extensions of the covered work, |
252 | +and which are not combined with it such as to form a larger program, |
253 | +in or on a volume of a storage or distribution medium, is called an |
254 | +"aggregate" if the compilation and its resulting copyright are not |
255 | +used to limit the access or legal rights of the compilation's users |
256 | +beyond what the individual works permit. Inclusion of a covered work |
257 | +in an aggregate does not cause this License to apply to the other |
258 | +parts of the aggregate. |
259 | + |
260 | + 6. Conveying Non-Source Forms. |
261 | + |
262 | + You may convey a covered work in object code form under the terms |
263 | +of sections 4 and 5, provided that you also convey the |
264 | +machine-readable Corresponding Source under the terms of this License, |
265 | +in one of these ways: |
266 | + |
267 | + a) Convey the object code in, or embodied in, a physical product |
268 | + (including a physical distribution medium), accompanied by the |
269 | + Corresponding Source fixed on a durable physical medium |
270 | + customarily used for software interchange. |
271 | + |
272 | + b) Convey the object code in, or embodied in, a physical product |
273 | + (including a physical distribution medium), accompanied by a |
274 | + written offer, valid for at least three years and valid for as |
275 | + long as you offer spare parts or customer support for that product |
276 | + model, to give anyone who possesses the object code either (1) a |
277 | + copy of the Corresponding Source for all the software in the |
278 | + product that is covered by this License, on a durable physical |
279 | + medium customarily used for software interchange, for a price no |
280 | + more than your reasonable cost of physically performing this |
281 | + conveying of source, or (2) access to copy the |
282 | + Corresponding Source from a network server at no charge. |
283 | + |
284 | + c) Convey individual copies of the object code with a copy of the |
285 | + written offer to provide the Corresponding Source. This |
286 | + alternative is allowed only occasionally and noncommercially, and |
287 | + only if you received the object code with such an offer, in accord |
288 | + with subsection 6b. |
289 | + |
290 | + d) Convey the object code by offering access from a designated |
291 | + place (gratis or for a charge), and offer equivalent access to the |
292 | + Corresponding Source in the same way through the same place at no |
293 | + further charge. You need not require recipients to copy the |
294 | + Corresponding Source along with the object code. If the place to |
295 | + copy the object code is a network server, the Corresponding Source |
296 | + may be on a different server (operated by you or a third party) |
297 | + that supports equivalent copying facilities, provided you maintain |
298 | + clear directions next to the object code saying where to find the |
299 | + Corresponding Source. Regardless of what server hosts the |
300 | + Corresponding Source, you remain obligated to ensure that it is |
301 | + available for as long as needed to satisfy these requirements. |
302 | + |
303 | + e) Convey the object code using peer-to-peer transmission, provided |
304 | + you inform other peers where the object code and Corresponding |
305 | + Source of the work are being offered to the general public at no |
306 | + charge under subsection 6d. |
307 | + |
308 | + A separable portion of the object code, whose source code is excluded |
309 | +from the Corresponding Source as a System Library, need not be |
310 | +included in conveying the object code work. |
311 | + |
312 | + A "User Product" is either (1) a "consumer product", which means any |
313 | +tangible personal property which is normally used for personal, family, |
314 | +or household purposes, or (2) anything designed or sold for incorporation |
315 | +into a dwelling. In determining whether a product is a consumer product, |
316 | +doubtful cases shall be resolved in favor of coverage. For a particular |
317 | +product received by a particular user, "normally used" refers to a |
318 | +typical or common use of that class of product, regardless of the status |
319 | +of the particular user or of the way in which the particular user |
320 | +actually uses, or expects or is expected to use, the product. A product |
321 | +is a consumer product regardless of whether the product has substantial |
322 | +commercial, industrial or non-consumer uses, unless such uses represent |
323 | +the only significant mode of use of the product. |
324 | + |
325 | + "Installation Information" for a User Product means any methods, |
326 | +procedures, authorization keys, or other information required to install |
327 | +and execute modified versions of a covered work in that User Product from |
328 | +a modified version of its Corresponding Source. The information must |
329 | +suffice to ensure that the continued functioning of the modified object |
330 | +code is in no case prevented or interfered with solely because |
331 | +modification has been made. |
332 | + |
333 | + If you convey an object code work under this section in, or with, or |
334 | +specifically for use in, a User Product, and the conveying occurs as |
335 | +part of a transaction in which the right of possession and use of the |
336 | +User Product is transferred to the recipient in perpetuity or for a |
337 | +fixed term (regardless of how the transaction is characterized), the |
338 | +Corresponding Source conveyed under this section must be accompanied |
339 | +by the Installation Information. But this requirement does not apply |
340 | +if neither you nor any third party retains the ability to install |
341 | +modified object code on the User Product (for example, the work has |
342 | +been installed in ROM). |
343 | + |
344 | + The requirement to provide Installation Information does not include a |
345 | +requirement to continue to provide support service, warranty, or updates |
346 | +for a work that has been modified or installed by the recipient, or for |
347 | +the User Product in which it has been modified or installed. Access to a |
348 | +network may be denied when the modification itself materially and |
349 | +adversely affects the operation of the network or violates the rules and |
350 | +protocols for communication across the network. |
351 | + |
352 | + Corresponding Source conveyed, and Installation Information provided, |
353 | +in accord with this section must be in a format that is publicly |
354 | +documented (and with an implementation available to the public in |
355 | +source code form), and must require no special password or key for |
356 | +unpacking, reading or copying. |
357 | + |
358 | + 7. Additional Terms. |
359 | + |
360 | + "Additional permissions" are terms that supplement the terms of this |
361 | +License by making exceptions from one or more of its conditions. |
362 | +Additional permissions that are applicable to the entire Program shall |
363 | +be treated as though they were included in this License, to the extent |
364 | +that they are valid under applicable law. If additional permissions |
365 | +apply only to part of the Program, that part may be used separately |
366 | +under those permissions, but the entire Program remains governed by |
367 | +this License without regard to the additional permissions. |
368 | + |
369 | + When you convey a copy of a covered work, you may at your option |
370 | +remove any additional permissions from that copy, or from any part of |
371 | +it. (Additional permissions may be written to require their own |
372 | +removal in certain cases when you modify the work.) You may place |
373 | +additional permissions on material, added by you to a covered work, |
374 | +for which you have or can give appropriate copyright permission. |
375 | + |
376 | + Notwithstanding any other provision of this License, for material you |
377 | +add to a covered work, you may (if authorized by the copyright holders of |
378 | +that material) supplement the terms of this License with terms: |
379 | + |
380 | + a) Disclaiming warranty or limiting liability differently from the |
381 | + terms of sections 15 and 16 of this License; or |
382 | + |
383 | + b) Requiring preservation of specified reasonable legal notices or |
384 | + author attributions in that material or in the Appropriate Legal |
385 | + Notices displayed by works containing it; or |
386 | + |
387 | + c) Prohibiting misrepresentation of the origin of that material, or |
388 | + requiring that modified versions of such material be marked in |
389 | + reasonable ways as different from the original version; or |
390 | + |
391 | + d) Limiting the use for publicity purposes of names of licensors or |
392 | + authors of the material; or |
393 | + |
394 | + e) Declining to grant rights under trademark law for use of some |
395 | + trade names, trademarks, or service marks; or |
396 | + |
397 | + f) Requiring indemnification of licensors and authors of that |
398 | + material by anyone who conveys the material (or modified versions of |
399 | + it) with contractual assumptions of liability to the recipient, for |
400 | + any liability that these contractual assumptions directly impose on |
401 | + those licensors and authors. |
402 | + |
403 | + All other non-permissive additional terms are considered "further |
404 | +restrictions" within the meaning of section 10. If the Program as you |
405 | +received it, or any part of it, contains a notice stating that it is |
406 | +governed by this License along with a term that is a further |
407 | +restriction, you may remove that term. If a license document contains |
408 | +a further restriction but permits relicensing or conveying under this |
409 | +License, you may add to a covered work material governed by the terms |
410 | +of that license document, provided that the further restriction does |
411 | +not survive such relicensing or conveying. |
412 | + |
413 | + If you add terms to a covered work in accord with this section, you |
414 | +must place, in the relevant source files, a statement of the |
415 | +additional terms that apply to those files, or a notice indicating |
416 | +where to find the applicable terms. |
417 | + |
418 | + Additional terms, permissive or non-permissive, may be stated in the |
419 | +form of a separately written license, or stated as exceptions; |
420 | +the above requirements apply either way. |
421 | + |
422 | + 8. Termination. |
423 | + |
424 | + You may not propagate or modify a covered work except as expressly |
425 | +provided under this License. Any attempt otherwise to propagate or |
426 | +modify it is void, and will automatically terminate your rights under |
427 | +this License (including any patent licenses granted under the third |
428 | +paragraph of section 11). |
429 | + |
430 | + However, if you cease all violation of this License, then your |
431 | +license from a particular copyright holder is reinstated (a) |
432 | +provisionally, unless and until the copyright holder explicitly and |
433 | +finally terminates your license, and (b) permanently, if the copyright |
434 | +holder fails to notify you of the violation by some reasonable means |
435 | +prior to 60 days after the cessation. |
436 | + |
437 | + Moreover, your license from a particular copyright holder is |
438 | +reinstated permanently if the copyright holder notifies you of the |
439 | +violation by some reasonable means, this is the first time you have |
440 | +received notice of violation of this License (for any work) from that |
441 | +copyright holder, and you cure the violation prior to 30 days after |
442 | +your receipt of the notice. |
443 | + |
444 | + Termination of your rights under this section does not terminate the |
445 | +licenses of parties who have received copies or rights from you under |
446 | +this License. If your rights have been terminated and not permanently |
447 | +reinstated, you do not qualify to receive new licenses for the same |
448 | +material under section 10. |
449 | + |
450 | + 9. Acceptance Not Required for Having Copies. |
451 | + |
452 | + You are not required to accept this License in order to receive or |
453 | +run a copy of the Program. Ancillary propagation of a covered work |
454 | +occurring solely as a consequence of using peer-to-peer transmission |
455 | +to receive a copy likewise does not require acceptance. However, |
456 | +nothing other than this License grants you permission to propagate or |
457 | +modify any covered work. These actions infringe copyright if you do |
458 | +not accept this License. Therefore, by modifying or propagating a |
459 | +covered work, you indicate your acceptance of this License to do so. |
460 | + |
461 | + 10. Automatic Licensing of Downstream Recipients. |
462 | + |
463 | + Each time you convey a covered work, the recipient automatically |
464 | +receives a license from the original licensors, to run, modify and |
465 | +propagate that work, subject to this License. You are not responsible |
466 | +for enforcing compliance by third parties with this License. |
467 | + |
468 | + An "entity transaction" is a transaction transferring control of an |
469 | +organization, or substantially all assets of one, or subdividing an |
470 | +organization, or merging organizations. If propagation of a covered |
471 | +work results from an entity transaction, each party to that |
472 | +transaction who receives a copy of the work also receives whatever |
473 | +licenses to the work the party's predecessor in interest had or could |
474 | +give under the previous paragraph, plus a right to possession of the |
475 | +Corresponding Source of the work from the predecessor in interest, if |
476 | +the predecessor has it or can get it with reasonable efforts. |
477 | + |
478 | + You may not impose any further restrictions on the exercise of the |
479 | +rights granted or affirmed under this License. For example, you may |
480 | +not impose a license fee, royalty, or other charge for exercise of |
481 | +rights granted under this License, and you may not initiate litigation |
482 | +(including a cross-claim or counterclaim in a lawsuit) alleging that |
483 | +any patent claim is infringed by making, using, selling, offering for |
484 | +sale, or importing the Program or any portion of it. |
485 | + |
486 | + 11. Patents. |
487 | + |
488 | + A "contributor" is a copyright holder who authorizes use under this |
489 | +License of the Program or a work on which the Program is based. The |
490 | +work thus licensed is called the contributor's "contributor version". |
491 | + |
492 | + A contributor's "essential patent claims" are all patent claims |
493 | +owned or controlled by the contributor, whether already acquired or |
494 | +hereafter acquired, that would be infringed by some manner, permitted |
495 | +by this License, of making, using, or selling its contributor version, |
496 | +but do not include claims that would be infringed only as a |
497 | +consequence of further modification of the contributor version. For |
498 | +purposes of this definition, "control" includes the right to grant |
499 | +patent sublicenses in a manner consistent with the requirements of |
500 | +this License. |
501 | + |
502 | + Each contributor grants you a non-exclusive, worldwide, royalty-free |
503 | +patent license under the contributor's essential patent claims, to |
504 | +make, use, sell, offer for sale, import and otherwise run, modify and |
505 | +propagate the contents of its contributor version. |
506 | + |
507 | + In the following three paragraphs, a "patent license" is any express |
508 | +agreement or commitment, however denominated, not to enforce a patent |
509 | +(such as an express permission to practice a patent or covenant not to |
510 | +sue for patent infringement). To "grant" such a patent license to a |
511 | +party means to make such an agreement or commitment not to enforce a |
512 | +patent against the party. |
513 | + |
514 | + If you convey a covered work, knowingly relying on a patent license, |
515 | +and the Corresponding Source of the work is not available for anyone |
516 | +to copy, free of charge and under the terms of this License, through a |
517 | +publicly available network server or other readily accessible means, |
518 | +then you must either (1) cause the Corresponding Source to be so |
519 | +available, or (2) arrange to deprive yourself of the benefit of the |
520 | +patent license for this particular work, or (3) arrange, in a manner |
521 | +consistent with the requirements of this License, to extend the patent |
522 | +license to downstream recipients. "Knowingly relying" means you have |
523 | +actual knowledge that, but for the patent license, your conveying the |
524 | +covered work in a country, or your recipient's use of the covered work |
525 | +in a country, would infringe one or more identifiable patents in that |
526 | +country that you have reason to believe are valid. |
527 | + |
528 | + If, pursuant to or in connection with a single transaction or |
529 | +arrangement, you convey, or propagate by procuring conveyance of, a |
530 | +covered work, and grant a patent license to some of the parties |
531 | +receiving the covered work authorizing them to use, propagate, modify |
532 | +or convey a specific copy of the covered work, then the patent license |
533 | +you grant is automatically extended to all recipients of the covered |
534 | +work and works based on it. |
535 | + |
536 | + A patent license is "discriminatory" if it does not include within |
537 | +the scope of its coverage, prohibits the exercise of, or is |
538 | +conditioned on the non-exercise of one or more of the rights that are |
539 | +specifically granted under this License. You may not convey a covered |
540 | +work if you are a party to an arrangement with a third party that is |
541 | +in the business of distributing software, under which you make payment |
542 | +to the third party based on the extent of your activity of conveying |
543 | +the work, and under which the third party grants, to any of the |
544 | +parties who would receive the covered work from you, a discriminatory |
545 | +patent license (a) in connection with copies of the covered work |
546 | +conveyed by you (or copies made from those copies), or (b) primarily |
547 | +for and in connection with specific products or compilations that |
548 | +contain the covered work, unless you entered into that arrangement, |
549 | +or that patent license was granted, prior to 28 March 2007. |
550 | + |
551 | + Nothing in this License shall be construed as excluding or limiting |
552 | +any implied license or other defenses to infringement that may |
553 | +otherwise be available to you under applicable patent law. |
554 | + |
555 | + 12. No Surrender of Others' Freedom. |
556 | + |
557 | + If conditions are imposed on you (whether by court order, agreement or |
558 | +otherwise) that contradict the conditions of this License, they do not |
559 | +excuse you from the conditions of this License. If you cannot convey a |
560 | +covered work so as to satisfy simultaneously your obligations under this |
561 | +License and any other pertinent obligations, then as a consequence you may |
562 | +not convey it at all. For example, if you agree to terms that obligate you |
563 | +to collect a royalty for further conveying from those to whom you convey |
564 | +the Program, the only way you could satisfy both those terms and this |
565 | +License would be to refrain entirely from conveying the Program. |
566 | + |
567 | + 13. Remote Network Interaction; Use with the GNU General Public License. |
568 | + |
569 | + Notwithstanding any other provision of this License, if you modify the |
570 | +Program, your modified version must prominently offer all users |
571 | +interacting with it remotely through a computer network (if your version |
572 | +supports such interaction) an opportunity to receive the Corresponding |
573 | +Source of your version by providing access to the Corresponding Source |
574 | +from a network server at no charge, through some standard or customary |
575 | +means of facilitating copying of software. This Corresponding Source |
576 | +shall include the Corresponding Source for any work covered by version 3 |
577 | +of the GNU General Public License that is incorporated pursuant to the |
578 | +following paragraph. |
579 | + |
580 | + Notwithstanding any other provision of this License, you have |
581 | +permission to link or combine any covered work with a work licensed |
582 | +under version 3 of the GNU General Public License into a single |
583 | +combined work, and to convey the resulting work. The terms of this |
584 | +License will continue to apply to the part which is the covered work, |
585 | +but the work with which it is combined will remain governed by version |
586 | +3 of the GNU General Public License. |
587 | + |
588 | + 14. Revised Versions of this License. |
589 | + |
590 | + The Free Software Foundation may publish revised and/or new versions of |
591 | +the GNU Affero General Public License from time to time. Such new versions |
592 | +will be similar in spirit to the present version, but may differ in detail to |
593 | +address new problems or concerns. |
594 | + |
595 | + Each version is given a distinguishing version number. If the |
596 | +Program specifies that a certain numbered version of the GNU Affero General |
597 | +Public License "or any later version" applies to it, you have the |
598 | +option of following the terms and conditions either of that numbered |
599 | +version or of any later version published by the Free Software |
600 | +Foundation. If the Program does not specify a version number of the |
601 | +GNU Affero General Public License, you may choose any version ever published |
602 | +by the Free Software Foundation. |
603 | + |
604 | + If the Program specifies that a proxy can decide which future |
605 | +versions of the GNU Affero General Public License can be used, that proxy's |
606 | +public statement of acceptance of a version permanently authorizes you |
607 | +to choose that version for the Program. |
608 | + |
609 | + Later license versions may give you additional or different |
610 | +permissions. However, no additional obligations are imposed on any |
611 | +author or copyright holder as a result of your choosing to follow a |
612 | +later version. |
613 | + |
614 | + 15. Disclaimer of Warranty. |
615 | + |
616 | + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY |
617 | +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT |
618 | +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY |
619 | +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, |
620 | +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
621 | +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM |
622 | +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF |
623 | +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. |
624 | + |
625 | + 16. Limitation of Liability. |
626 | + |
627 | + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
628 | +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS |
629 | +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY |
630 | +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE |
631 | +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF |
632 | +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD |
633 | +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), |
634 | +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |
635 | +SUCH DAMAGES. |
636 | + |
637 | + 17. Interpretation of Sections 15 and 16. |
638 | + |
639 | + If the disclaimer of warranty and limitation of liability provided |
640 | +above cannot be given local legal effect according to their terms, |
641 | +reviewing courts shall apply local law that most closely approximates |
642 | +an absolute waiver of all civil liability in connection with the |
643 | +Program, unless a warranty or assumption of liability accompanies a |
644 | +copy of the Program in return for a fee. |
645 | + |
646 | + END OF TERMS AND CONDITIONS |
647 | + |
648 | + How to Apply These Terms to Your New Programs |
649 | + |
650 | + If you develop a new program, and you want it to be of the greatest |
651 | +possible use to the public, the best way to achieve this is to make it |
652 | +free software which everyone can redistribute and change under these terms. |
653 | + |
654 | + To do so, attach the following notices to the program. It is safest |
655 | +to attach them to the start of each source file to most effectively |
656 | +state the exclusion of warranty; and each file should have at least |
657 | +the "copyright" line and a pointer to where the full notice is found. |
658 | + |
659 | + <one line to give the program's name and a brief idea of what it does.> |
660 | + Copyright (C) <year> <name of author> |
661 | + |
662 | + This program is free software: you can redistribute it and/or modify |
663 | + it under the terms of the GNU Affero General Public License as published by |
664 | + the Free Software Foundation, either version 3 of the License, or |
665 | + (at your option) any later version. |
666 | + |
667 | + This program is distributed in the hope that it will be useful, |
668 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
669 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
670 | + GNU Affero General Public License for more details. |
671 | + |
672 | + You should have received a copy of the GNU Affero General Public License |
673 | + along with this program. If not, see <http://www.gnu.org/licenses/>. |
674 | + |
675 | +Also add information on how to contact you by electronic and paper mail. |
676 | + |
677 | + If your software can interact with users remotely through a computer |
678 | +network, you should also make sure that it provides a way for users to |
679 | +get its source. For example, if your program is a web application, its |
680 | +interface could display a "Source" link that leads users to an archive |
681 | +of the code. There are many ways you could offer source, and different |
682 | +solutions will be better for different programs; see section 13 for the |
683 | +specific requirements. |
684 | + |
685 | + You should also get your employer (if you work as a programmer) or school, |
686 | +if any, to sign a "copyright disclaimer" for the program, if necessary. |
687 | +For more information on this, and how to apply and follow the GNU AGPL, see |
688 | +<http://www.gnu.org/licenses/>. |
689 | |
690 | === modified file 'Dependencies.md' |
691 | --- Dependencies.md 2013-06-03 14:27:05 +0000 |
692 | +++ Dependencies.md 2013-07-17 21:35:43 +0000 |
693 | @@ -1,3 +1,12 @@ |
694 | +<!-- |
695 | +Dependencies.md |
696 | +Copyright 2013 Canonical Ltd. |
697 | +This work is licensed under the Creative Commons Attribution-Share Alike 3.0 |
698 | +Unported License. To view a copy of this license, visit |
699 | +http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative |
700 | +Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA. |
701 | +--> |
702 | + |
703 | # Juju GUI Charm external dependencies # |
704 | |
705 | The Juju GUI has a number of external dependencies including packages that are |
706 | @@ -42,5 +51,3 @@ |
707 | single starting place to obtain QA'd software. Dev ops can grab the subset of |
708 | packages they need, audit, test, and then serve them locally. The |
709 | `repository-location` config variable can be used to point to the local repo. |
710 | - |
711 | - |
712 | |
713 | === modified file 'HACKING.md' |
714 | --- HACKING.md 2013-06-27 18:25:15 +0000 |
715 | +++ HACKING.md 2013-07-17 21:35:43 +0000 |
716 | @@ -1,3 +1,12 @@ |
717 | +<!-- |
718 | +HACKING.md |
719 | +Copyright 2013 Canonical Ltd. |
720 | +This work is licensed under the Creative Commons Attribution-Share Alike 3.0 |
721 | +Unported License. To view a copy of this license, visit |
722 | +http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative |
723 | +Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA. |
724 | +--> |
725 | + |
726 | # Juju GUI Charm Development # |
727 | |
728 | ## Contacting the Developers ## |
729 | @@ -16,8 +25,8 @@ |
730 | |
731 | You'll also need some dependencies and developer basics. |
732 | |
733 | - sudo apt-get install bzr autoconf libtool python-charmhelpers python-yaml \ |
734 | - xvfb |
735 | + sudo apt-get install build-essential bzr libapt-pkg-dev python-pip \ |
736 | + python-virtualenv xvfb |
737 | |
738 | Next, you need the bzr branch. We work from |
739 | [lp:~juju-gui/charms/precise/juju-gui/trunk](https://code.launchpad.net/~juju-gui/charms/precise/juju-gui/trunk). |
740 | @@ -25,49 +34,45 @@ |
741 | You could start hacking now, but there's a bit more to do to prepare for |
742 | running and writing tests. |
743 | |
744 | -We use the Jitsu test command to run our functional tests. At the time of |
745 | -this writing it is not yet released. To run it you must first install it |
746 | -locally. The files may be installed globally, or into your home directory (as |
747 | -here): |
748 | - |
749 | - sudo apt-get install autoconf libtool python-charmhelpers python-tempita |
750 | - bzr branch lp:juju-jitsu juju-jitsu |
751 | - cd juju-jitsu |
752 | - autoreconf |
753 | - ./configure --prefix=$HOME |
754 | +We use the juju-test test command to run our functional and unit tests. At the |
755 | +time of this writing it is not yet released. To run it you must first install |
756 | +it locally, e.g.: |
757 | + |
758 | + bzr checkout --lightweight lp:juju-plugins |
759 | + |
760 | +At this point, link "juju-plugins/plugins/juju_test.py" as "juju-test" |
761 | +somewhere in your PATH, so that it is possible to execute "juju-test". |
762 | + |
763 | +Before being able to run the suite, test requirements need to be installed |
764 | +running the command: |
765 | + |
766 | make |
767 | - make install |
768 | - |
769 | -Functional tests make use of Selenium and xvfbwrapper. To install the latest |
770 | -version of these packages, as required by the test suite, you can use pip or |
771 | -easy_install, e.g.: |
772 | - |
773 | - sudo pip install selenium xvfbwrapper |
774 | - |
775 | -The current incarnation of the Jitsu test command requires that the current |
776 | -directory name match the charm name, so you must check out the charm into a |
777 | -directory named "juju-gui": |
778 | - |
779 | - bzr branch lp:~juju-gui/charms/precise/juju-gui/trunk juju-gui |
780 | - |
781 | -The branch directory must be placed (or linked from) within a local charm |
782 | -repository. It consists of a directory, itself containing a number of |
783 | -directories, one for each distribution codename, e.g. `precise`. In turn, the |
784 | -codename directories will contain the charm directories. Therefore, you |
785 | -should put your charm in a path like this: `[REPO]/precise/juju-gui`. |
786 | - |
787 | -Now you are ready to run the functional tests (see the next section). |
788 | + |
789 | +The command above will create a ".venv" directory inside "juju-gui/tests/", |
790 | +ignored by DVCSes, containing the development virtual environment with all the |
791 | +testing dependencies. Run "make help" to see all the available make targets. |
792 | + |
793 | +Now you are ready to run the functional and unit tests (see the next section). |
794 | |
795 | ## Testing ## |
796 | |
797 | There are two types of tests for the charm: unit tests and functional tests. |
798 | +Long story short, to run all the tests: |
799 | + |
800 | + make test JUJU_ENV="myenv" |
801 | + |
802 | +In the command above, "myenv" is the juju environment, as it is specified in |
803 | +your `~/.juju/environments.yaml`, that will be bootstrapped before running the |
804 | +tests and destroyed at the end of the test run. |
805 | + |
806 | +Please read further for additional details. |
807 | |
808 | ### Unit Tests ### |
809 | |
810 | The unit tests do not require a functional Juju environment, and can be run |
811 | with this command:: |
812 | |
813 | - python tests/unit.test |
814 | + make unittest |
815 | |
816 | Unit tests should be created in the "tests" subdirectory and be named in the |
817 | customary way (i.e., "test_*.py"). |
818 | @@ -75,33 +80,15 @@ |
819 | ### Functional Tests ### |
820 | |
821 | Running the functional tests requires a Juju testing environment as provided |
822 | -by the Jitsu test command (see "Getting Started", above). All files in the |
823 | -tests directory which end with ".test" will be run in a Juju Jitsu test |
824 | -environment. |
825 | - |
826 | -Jitsu requires the charm directory to be named the same as the charm and to be |
827 | -the current working directory when the tests are run: |
828 | - |
829 | - JUJU_REPOSITORY=/path/to/charm/repo ~/bin/jitsu test juju-gui \ |
830 | - --logdir /tmp --timeout 40m |
831 | - |
832 | -This command will bootstrap the default Juju environment specified in your |
833 | -`~/.juju/environments.yaml`. |
834 | - |
835 | -Functional tests deploy, by default, the latest stable release of Juju GUI. |
836 | -However, it is possible to provide a different Juju GUI source by setting the |
837 | -environment variable `JUJU_GUI_SOURCE` while running tests, e.g.: |
838 | - |
839 | - JUJU_GUI_SOURCE=lp:mybranch JUJU_REPOSITORY=/path/to/charm/repo \ |
840 | - ~/bin/jitsu test juju-gui ... |
841 | - |
842 | -To test a new trunk release: |
843 | - |
844 | - JUJU_GUI_SOURCE=trunk JUJU_REPOSITORY=/path/to/charm/repo \ |
845 | - ~/bin/jitsu test juju-gui ... |
846 | - |
847 | -This can be useful to verify that a given branch works with the charm, which is |
848 | -one of the tasks that release QA should include. |
849 | +by the juju-test command (see "Getting Started", above). |
850 | + |
851 | +To run only the functional tests: |
852 | + |
853 | + make ftest JUJU_ENV="myenv" |
854 | + |
855 | +As seen before, "myenv" is the juju environment, as it is specified in your |
856 | +`~/.juju/environments.yaml`, that will be bootstrapped before running the |
857 | +tests and destroyed at the end of the test run. |
858 | |
859 | #### LXC #### |
860 | |
861 | @@ -109,33 +96,34 @@ |
862 | for these tests. At this time, we recommend using other environments, such as |
863 | OpenStack; but we will periodically check the tests in LXC environments |
864 | because it would be great to be able to use it. If you do want to use LXC, |
865 | -you will need to install the `apt-cacher-ng` and `lxc` packages. |
866 | - |
867 | -Currently running tests on a local environment is quite slow (with quantal |
868 | -host and precise container at least), so you may want to further increase the |
869 | -`jitsu test` command timeout. |
870 | - |
871 | -If Jitsu generates errors about not being able to bootstrap: |
872 | - |
873 | - CalledProcessError: Command '['juju', 'bootstrap']'... |
874 | - |
875 | -or if it hangs, then you may need to bootstrap the environment yourself and |
876 | -pass the --no-bootstrap switch to Jitsu. |
877 | +you will need to install the `apt-cacher-ng` and `lxc` packages. Also note |
878 | +that at this time juju-core does not support the local provider. |
879 | |
880 | ## Running the Charm From Development ## |
881 | |
882 | -If you have set up your environment to run functional tests, you also have set |
883 | -it up to run your local development charm. Developing and debugging with this |
884 | -is much easier than trying to develop and debug with the tests, unfortunately. |
885 | - |
886 | -To get started, first, simply do a `juju bootstrap`. Using a non-LXC |
887 | -environment probably will reduce frustrations. Then, deploy your charm like |
888 | -this (again, assuming you have set up your repo the way the functional tests |
889 | -need them, as described above). |
890 | - |
891 | +If you have set up your environment to run your local development charm, |
892 | +deploying the charm fails if attempted after the testing virtualenv has been |
893 | +created: juju deploy exits with an error due to ".venv" directory containing |
894 | +an absolute symbolic link. There are two ways to work around this problem. |
895 | + |
896 | +The first one is running "make clean" before deploying the charm: |
897 | + |
898 | + make clean |
899 | + juju bootstrap |
900 | juju deploy --repository=/path/to/charm/repo local:precise/juju-gui |
901 | juju expose juju-gui |
902 | |
903 | +The second one is just running "make deploy": |
904 | + |
905 | + juju bootstrap |
906 | + make deploy |
907 | + |
908 | +The "make deploy" command creates a temporary Juju repository (excluding |
909 | +the ".venv" directory), deploys the Juju GUI charm from that repository and |
910 | +exposes the juju-gui service. Also note that "make deploy" does not require |
911 | +you to manually set up a local Juju environment, and preserves, if already |
912 | +created, the testing virtualenv. |
913 | + |
914 | Now you are working with a test run, as described in |
915 | <https://juju.ubuntu.com/docs/write-charm.html#test-run>. The |
916 | `juju debug-hooks` command, described in the same web page, is your most |
917 | |
918 | === added file 'Makefile' |
919 | --- Makefile 1970-01-01 00:00:00 +0000 |
920 | +++ Makefile 2013-07-17 21:35:43 +0000 |
921 | @@ -0,0 +1,63 @@ |
922 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
923 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
924 | +# Copyright (C) 2012-2013 Canonical Ltd. |
925 | +# |
926 | +# This program is free software: you can redistribute it and/or modify it under |
927 | +# the terms of the GNU Affero General Public License version 3, as published by |
928 | +# the Free Software Foundation. |
929 | +# |
930 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
931 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
932 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
933 | +# Affero General Public License for more details. |
934 | +# |
935 | +# You should have received a copy of the GNU Affero General Public License |
936 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
937 | + |
938 | +JUJUTEST = juju-test --timeout=30m -v -e "$(JUJU_ENV)" --upload-tools |
939 | +VENV = ./tests/.venv |
940 | + |
941 | +all: setup |
942 | + |
943 | +setup: |
944 | + @./tests/00-setup |
945 | + |
946 | +unittest: setup |
947 | + ./tests/10-unit.test |
948 | + |
949 | +ftest: setup |
950 | + $(JUJUTEST) 20-functional.test |
951 | + |
952 | +# This will be eventually removed when we have juju-test --clean-state. |
953 | +test: unittest ftest |
954 | + |
955 | +# This will be eventually renamed as test when we have juju-test --clean-state. |
956 | +jujutest: |
957 | + $(JUJUTEST) |
958 | + |
959 | +lint: setup |
960 | + @$(VENV)/bin/flake8 --show-source --exclude=.venv ./hooks/ ./tests/ |
961 | + |
962 | +clean: |
963 | + find . -name '*.pyc' -delete |
964 | + rm -rf $(VENV) |
965 | + |
966 | +deploy: setup |
967 | + $(VENV)/bin/python ./tests/deploy.py |
968 | + |
969 | +help: |
970 | + @echo -e 'Juju GUI charm - list of make targets:\n' |
971 | + @echo -e 'make - Set up development and testing environment.\n' |
972 | + @echo 'make test JUJU_ENV="my-juju-env" - Run functional and unit tests.' |
973 | + @echo -e ' JUJU_ENV is the Juju environment that will be bootstrapped.\n' |
974 | + @echo -e 'make unittest - Run unit tests.\n' |
975 | + @echo 'make ftest JUJU_ENV="my-juju-env" - Run functional tests.' |
976 | + @echo -e ' JUJU_ENV is the Juju environment that will be bootstrapped.\n' |
977 | + @echo -e 'make lint - Run linter and pep8.\n' |
978 | + @echo -e 'make clean - Remove bytecode files and virtualenvs.\n' |
979 | + @echo 'make deploy [JUJU_ENV="my-juju-env]" - Deploy and expose the Juju' |
980 | + @echo ' GUI charm setting up a temporary Juju repository. Wait for the' |
981 | + @echo ' service to be started. If JUJU_ENV is not passed, the charm will' |
982 | + @echo ' be deployed in the default Juju environment.' |
983 | + |
984 | +.PHONY: all clean deploy ftest help jujutest lint setup test unittest |
985 | |
986 | === modified file 'README.md' |
987 | --- README.md 2013-06-26 20:37:57 +0000 |
988 | +++ README.md 2013-07-17 21:35:43 +0000 |
989 | @@ -1,3 +1,12 @@ |
990 | +<!-- |
991 | +README.md |
992 | +Copyright 2013 Canonical Ltd. |
993 | +This work is licensed under the Creative Commons Attribution-Share Alike 3.0 |
994 | +Unported License. To view a copy of this license, visit |
995 | +http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative |
996 | +Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA. |
997 | +--> |
998 | + |
999 | # Juju GUI Charm # |
1000 | |
1001 | This charm makes it easy to deploy a Juju GUI into an existing environment. |
1002 | @@ -23,7 +32,7 @@ |
1003 | Next, you simply need to deploy the charm and expose it. (See also "Deploying |
1004 | with Jitsu" below, for another option.) |
1005 | |
1006 | - juju deploy cs:precise/juju-gui |
1007 | + juju deploy juju-gui |
1008 | juju expose juju-gui |
1009 | |
1010 | Finally, you need to identify the GUI's URL. It can take a few minutes for the |
1011 | |
1012 | === modified file 'config.yaml' |
1013 | --- config.yaml 2013-05-31 19:54:41 +0000 |
1014 | +++ config.yaml 2013-07-17 21:35:43 +0000 |
1015 | @@ -1,3 +1,19 @@ |
1016 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
1017 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
1018 | +# Copyright (C) 2012-2013 Canonical Ltd. |
1019 | +# |
1020 | +# This program is free software: you can redistribute it and/or modify it under |
1021 | +# the terms of the GNU Affero General Public License version 3, as published by |
1022 | +# the Free Software Foundation. |
1023 | +# |
1024 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1025 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
1026 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
1027 | +# Affero General Public License for more details. |
1028 | +# |
1029 | +# You should have received a copy of the GNU Affero General Public License |
1030 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1031 | + |
1032 | options: |
1033 | juju-gui-source: |
1034 | description: | |
1035 | @@ -111,7 +127,7 @@ |
1036 | The URL of the charm catalog site ("charmworld") from which charm |
1037 | catalog data will be drawn. |
1038 | type: string |
1039 | - default: https://manage.jujucharms.com |
1040 | + default: https://manage.jujucharms.com/ |
1041 | repository-location: |
1042 | description: | |
1043 | The charm depends on several software packages that are not packaged in |
1044 | @@ -124,3 +140,30 @@ |
1045 | 'add-apt-repository'. |
1046 | type: string |
1047 | default: ppa:juju-gui-charmers/stable |
1048 | + use-analytics: |
1049 | + description: | |
1050 | + The team developing the Juju GUI benefits from understanding how |
1051 | + different deployments use the tool. By enabling this setting, |
1052 | + anonymized usage data is reported back using Google Analytics. The type |
1053 | + of data collected includes the charms that are deployed and the number |
1054 | + of units per service. Use of analytics is optional but we hope you will |
1055 | + allow us to improve our tool based on your experience. |
1056 | + type: boolean |
1057 | + default: true |
1058 | + default-viewmode: |
1059 | + description: | |
1060 | + What the charmbrowser's default viewmode should be. Possible options are: |
1061 | + - 'sidebar' (default): the charmwbrowser will appear as a sidebar. This |
1062 | + is also known as build mode. |
1063 | + - 'fullscreen': the charmbrowser will appear in full screen, hiding the |
1064 | + canvas. This is also known as browse mode. |
1065 | + - 'minimized': the charmbrowser will be minimized by default, and hidden. |
1066 | + type: string |
1067 | + default: sidebar |
1068 | + show-get-juju-button: |
1069 | + description: | |
1070 | + There are deployment modes for Juju GUI which are not intended as regular |
1071 | + use mode. In these cases, login/logout are disabled and instead there is a |
1072 | + link to juju.ubuntu.com |
1073 | + type: boolean |
1074 | + default: false |
1075 | |
1076 | === modified file 'config/config.js.template' |
1077 | --- config/config.js.template 2013-04-26 21:05:03 +0000 |
1078 | +++ config/config.js.template 2013-07-17 21:35:43 +0000 |
1079 | @@ -22,5 +22,8 @@ |
1080 | apiBackend: {{api_backend}}, // Value can be 'python' or 'go'. |
1081 | readOnly: {{readonly}}, |
1082 | sandbox: {{sandbox}}, |
1083 | - login_help: {{login_help}} |
1084 | + useAnalytics: {{use_analytics}}, |
1085 | + login_help: {{login_help}}, |
1086 | + defaultViewmode: {{default_viewmode}}, |
1087 | + showGetJujuButton: {{show_get_juju_button}} |
1088 | }; |
1089 | |
1090 | === modified file 'copyright' |
1091 | --- copyright 2013-01-21 16:56:10 +0000 |
1092 | +++ copyright 2013-07-17 21:35:43 +0000 |
1093 | @@ -2,10 +2,10 @@ |
1094 | |
1095 | Files: * |
1096 | Copyright: Copyright 2013, Canonical Ltd., All Rights Reserved. |
1097 | -License: GPL-3 |
1098 | +License: Affero GPL-3 |
1099 | This program is free software: you can redistribute it and/or modify |
1100 | - it under the terms of the GNU General Public License as published by |
1101 | - the Free Software Foundation, either version 3 of the License, or |
1102 | + it under the terms of the GNU Affero General Public License as published |
1103 | + by the Free Software Foundation, either version 3 of the License, or |
1104 | (at your option) any later version. |
1105 | . |
1106 | This program is distributed in the hope that it will be useful, |
1107 | |
1108 | === modified file 'hooks/backend.py' |
1109 | --- hooks/backend.py 2013-07-11 21:02:07 +0000 |
1110 | +++ hooks/backend.py 2013-07-17 21:35:43 +0000 |
1111 | @@ -1,3 +1,19 @@ |
1112 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
1113 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
1114 | +# Copyright (C) 2012-2013 Canonical Ltd. |
1115 | +# |
1116 | +# This program is free software: you can redistribute it and/or modify it under |
1117 | +# the terms of the GNU Affero General Public License version 3, as published by |
1118 | +# the Free Software Foundation. |
1119 | +# |
1120 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1121 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
1122 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
1123 | +# Affero General Public License for more details. |
1124 | +# |
1125 | +# You should have received a copy of the GNU Affero General Public License |
1126 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1127 | + |
1128 | """ |
1129 | A composition system for creating backend objects. |
1130 | |
1131 | @@ -84,7 +100,11 @@ |
1132 | config['juju-gui-console-enabled'], config['login-help'], |
1133 | config['read-only'], config['staging'], config['ssl-cert-path'], |
1134 | config['charmworld-url'], config['serve-tests'], |
1135 | - secure=config['secure'], sandbox=config['sandbox']) |
1136 | + secure=config['secure'], sandbox=config['sandbox'], |
1137 | + use_analytics=config['use-analytics'], |
1138 | + default_viewmode=config['default-viewmode'], |
1139 | + show_get_juju_button=config['show-get-juju-button']) |
1140 | + |
1141 | charmhelpers.open_port(80) |
1142 | charmhelpers.open_port(443) |
1143 | |
1144 | |
1145 | === modified file 'hooks/bootstrap_utils.py' |
1146 | --- hooks/bootstrap_utils.py 2013-04-24 10:46:20 +0000 |
1147 | +++ hooks/bootstrap_utils.py 2013-07-17 21:35:43 +0000 |
1148 | @@ -1,3 +1,19 @@ |
1149 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
1150 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
1151 | +# Copyright (C) 2012-2013 Canonical Ltd. |
1152 | +# |
1153 | +# This program is free software: you can redistribute it and/or modify it under |
1154 | +# the terms of the GNU Affero General Public License version 3, as published by |
1155 | +# the Free Software Foundation. |
1156 | +# |
1157 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1158 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
1159 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
1160 | +# Affero General Public License for more details. |
1161 | +# |
1162 | +# You should have received a copy of the GNU Affero General Public License |
1163 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1164 | + |
1165 | """ |
1166 | These are actually maintained in python-shelltoolbox. Precise does not have |
1167 | that package, so we need to bootstrap the process by copying the functions |
1168 | |
1169 | === modified file 'hooks/config-changed' |
1170 | --- hooks/config-changed 2013-05-03 09:13:45 +0000 |
1171 | +++ hooks/config-changed 2013-07-17 21:35:43 +0000 |
1172 | @@ -1,8 +1,21 @@ |
1173 | #!/usr/bin/env python2 |
1174 | # -*- python -*- |
1175 | |
1176 | -# Copyright 2012 Canonical Ltd. This software is licensed under the |
1177 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
1178 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
1179 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
1180 | +# Copyright (C) 2012-2013 Canonical Ltd. |
1181 | +# |
1182 | +# This program is free software: you can redistribute it and/or modify it under |
1183 | +# the terms of the GNU Affero General Public License version 3, as published by |
1184 | +# the Free Software Foundation. |
1185 | +# |
1186 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1187 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
1188 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
1189 | +# Affero General Public License for more details. |
1190 | +# |
1191 | +# You should have received a copy of the GNU Affero General Public License |
1192 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1193 | |
1194 | import sys |
1195 | |
1196 | |
1197 | === modified file 'hooks/install' |
1198 | --- hooks/install 2013-05-31 19:54:41 +0000 |
1199 | +++ hooks/install 2013-07-17 21:35:43 +0000 |
1200 | @@ -1,6 +1,22 @@ |
1201 | #!/usr/bin/env python2 |
1202 | # -*- python -*- |
1203 | |
1204 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
1205 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
1206 | +# Copyright (C) 2012-2013 Canonical Ltd. |
1207 | +# |
1208 | +# This program is free software: you can redistribute it and/or modify it under |
1209 | +# the terms of the GNU Affero General Public License version 3, as published by |
1210 | +# the Free Software Foundation. |
1211 | +# |
1212 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1213 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
1214 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
1215 | +# Affero General Public License for more details. |
1216 | +# |
1217 | +# You should have received a copy of the GNU Affero General Public License |
1218 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1219 | + |
1220 | import errno |
1221 | import json |
1222 | import os |
1223 | |
1224 | === modified file 'hooks/start' |
1225 | --- hooks/start 2013-05-03 09:13:45 +0000 |
1226 | +++ hooks/start 2013-07-17 21:35:43 +0000 |
1227 | @@ -1,6 +1,22 @@ |
1228 | #!/usr/bin/env python2 |
1229 | #-*- python -*- |
1230 | |
1231 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
1232 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
1233 | +# Copyright (C) 2012-2013 Canonical Ltd. |
1234 | +# |
1235 | +# This program is free software: you can redistribute it and/or modify it under |
1236 | +# the terms of the GNU Affero General Public License version 3, as published by |
1237 | +# the Free Software Foundation. |
1238 | +# |
1239 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1240 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
1241 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
1242 | +# Affero General Public License for more details. |
1243 | +# |
1244 | +# You should have received a copy of the GNU Affero General Public License |
1245 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1246 | + |
1247 | from charmhelpers import get_config |
1248 | |
1249 | from backend import Backend |
1250 | |
1251 | === modified file 'hooks/stop' |
1252 | --- hooks/stop 2013-05-03 10:37:06 +0000 |
1253 | +++ hooks/stop 2013-07-17 21:35:43 +0000 |
1254 | @@ -1,6 +1,22 @@ |
1255 | #!/usr/bin/env python2 |
1256 | #-*- python -*- |
1257 | |
1258 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
1259 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
1260 | +# Copyright (C) 2012-2013 Canonical Ltd. |
1261 | +# |
1262 | +# This program is free software: you can redistribute it and/or modify it under |
1263 | +# the terms of the GNU Affero General Public License version 3, as published by |
1264 | +# the Free Software Foundation. |
1265 | +# |
1266 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1267 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
1268 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
1269 | +# Affero General Public License for more details. |
1270 | +# |
1271 | +# You should have received a copy of the GNU Affero General Public License |
1272 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1273 | + |
1274 | from backend import Backend |
1275 | from utils import log_hook |
1276 | |
1277 | |
1278 | === modified file 'hooks/utils.py' |
1279 | --- hooks/utils.py 2013-07-11 19:10:30 +0000 |
1280 | +++ hooks/utils.py 2013-07-17 21:35:43 +0000 |
1281 | @@ -1,3 +1,19 @@ |
1282 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
1283 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
1284 | +# Copyright (C) 2012-2013 Canonical Ltd. |
1285 | +# |
1286 | +# This program is free software: you can redistribute it and/or modify it under |
1287 | +# the terms of the GNU Affero General Public License version 3, as published by |
1288 | +# the Free Software Foundation. |
1289 | +# |
1290 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1291 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
1292 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
1293 | +# Affero General Public License for more details. |
1294 | +# |
1295 | +# You should have received a copy of the GNU Affero General Public License |
1296 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1297 | + |
1298 | """Juju GUI charm utilities.""" |
1299 | |
1300 | __all__ = [ |
1301 | @@ -112,13 +128,24 @@ |
1302 | cmd_log(apt_get_install(*DEB_BUILD_DEPENDENCIES)) |
1303 | |
1304 | |
1305 | -def get_api_address(unit_dir): |
1306 | - """Return the Juju API address stored in the uniter agent.conf file.""" |
1307 | +def get_api_address(unit_dir=None): |
1308 | + """Return the Juju API address. |
1309 | + |
1310 | + If not present in the hook context as an environment variable, try to |
1311 | + retrieve the address parsing the machiner agent.conf file. |
1312 | + """ |
1313 | + api_addresses = os.getenv("JUJU_API_ADDRESSES") |
1314 | + if api_addresses is not None: |
1315 | + return api_addresses.split()[0] |
1316 | + # The JUJU_API_ADDRESSES environment variable is not included in the hooks |
1317 | + # context in older releases of juju-core. Retrieve it from the machiner |
1318 | + # agent file instead. |
1319 | import yaml # python-yaml is only installed if juju-core is used. |
1320 | - # XXX 2013-03-27 frankban bug=1161443: |
1321 | - # currently the uniter agent.conf file does not include the API |
1322 | - # address. For now retrieve it from the machine agent file. |
1323 | - base_dir = os.path.abspath(os.path.join(unit_dir, '..')) |
1324 | + if unit_dir is None: |
1325 | + base_dir = os.path.join(CURRENT_DIR, '..', '..') |
1326 | + else: |
1327 | + base_dir = os.path.join(unit_dir, '..') |
1328 | + base_dir = os.path.abspath(base_dir) |
1329 | for dirname in os.listdir(base_dir): |
1330 | if dirname.startswith('machine-'): |
1331 | agent_conf = os.path.join(base_dir, dirname, 'agent.conf') |
1332 | @@ -333,11 +360,12 @@ |
1333 | def start_gui( |
1334 | console_enabled, login_help, readonly, in_staging, ssl_cert_path, |
1335 | charmworld_url, serve_tests, haproxy_path='/etc/haproxy/haproxy.cfg', |
1336 | - config_js_path=None, secure=True, sandbox=False): |
1337 | + config_js_path=None, secure=True, sandbox=False, use_analytics=False, |
1338 | + default_viewmode='sidebar', show_get_juju_button=False): |
1339 | """Set up and start the Juju GUI server.""" |
1340 | with su('root'): |
1341 | run('chown', '-R', 'ubuntu:', JUJU_GUI_DIR) |
1342 | - # XXX 2013-02-05 frankban bug=1116320: |
1343 | + # XXX 2013-02-05 frankban bug=1116320: |
1344 | # External insecure resources are still loaded when testing in the |
1345 | # debug environment. For now, switch to the production environment if |
1346 | # the charm is configured to serve tests. |
1347 | @@ -373,6 +401,9 @@ |
1348 | 'protocol': json.dumps(protocol), |
1349 | 'sandbox': json.dumps(sandbox), |
1350 | 'charmworld_url': json.dumps(charmworld_url), |
1351 | + 'use_analytics': json.dumps(use_analytics), |
1352 | + 'default_viewmode': json.dumps(default_viewmode), |
1353 | + 'show_get_juju_button': json.dumps(show_get_juju_button), |
1354 | } |
1355 | if config_js_path is None: |
1356 | config_js_path = os.path.join( |
1357 | @@ -387,7 +418,7 @@ |
1358 | api_address = '127.0.0.1:{0}'.format(API_PORT) |
1359 | else: |
1360 | # Retrieve the juju-core API server address. |
1361 | - api_address = get_api_address(os.path.join(CURRENT_DIR, '..')) |
1362 | + api_address = get_api_address() |
1363 | context = { |
1364 | 'api_address': api_address, |
1365 | 'api_pem': JUJU_PEM, |
1366 | |
1367 | === modified file 'hooks/web-relation-joined' |
1368 | --- hooks/web-relation-joined 2013-05-03 09:13:45 +0000 |
1369 | +++ hooks/web-relation-joined 2013-07-17 21:35:43 +0000 |
1370 | @@ -1,6 +1,22 @@ |
1371 | #!/usr/bin/env python2 |
1372 | #-*- python -*- |
1373 | |
1374 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
1375 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
1376 | +# Copyright (C) 2012-2013 Canonical Ltd. |
1377 | +# |
1378 | +# This program is free software: you can redistribute it and/or modify it under |
1379 | +# the terms of the GNU Affero General Public License version 3, as published by |
1380 | +# the Free Software Foundation. |
1381 | +# |
1382 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1383 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
1384 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
1385 | +# Affero General Public License for more details. |
1386 | +# |
1387 | +# You should have received a copy of the GNU Affero General Public License |
1388 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1389 | + |
1390 | import socket |
1391 | |
1392 | from charmhelpers import ( |
1393 | |
1394 | === modified file 'metadata.yaml' |
1395 | --- metadata.yaml 2013-04-23 07:32:46 +0000 |
1396 | +++ metadata.yaml 2013-07-17 21:35:43 +0000 |
1397 | @@ -1,3 +1,19 @@ |
1398 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
1399 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
1400 | +# Copyright (C) 2012-2013 Canonical Ltd. |
1401 | +# |
1402 | +# This program is free software: you can redistribute it and/or modify it under |
1403 | +# the terms of the GNU Affero General Public License version 3, as published by |
1404 | +# the Free Software Foundation. |
1405 | +# |
1406 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1407 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
1408 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
1409 | +# Affero General Public License for more details. |
1410 | +# |
1411 | +# You should have received a copy of the GNU Affero General Public License |
1412 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1413 | + |
1414 | name: juju-gui |
1415 | summary: User interface for Juju (development) |
1416 | maintainer: Juju GUI Team <juju-gui@lists.launchpad.net> |
1417 | |
1418 | === modified file 'revision' |
1419 | --- revision 2013-07-12 18:37:59 +0000 |
1420 | +++ revision 2013-07-17 21:35:43 +0000 |
1421 | @@ -1,1 +1,1 @@ |
1422 | -52 |
1423 | +53 |
1424 | \ No newline at end of file |
1425 | |
1426 | === added file 'tests/00-setup' |
1427 | --- tests/00-setup 1970-01-01 00:00:00 +0000 |
1428 | +++ tests/00-setup 2013-07-17 21:35:43 +0000 |
1429 | @@ -0,0 +1,36 @@ |
1430 | +#!/bin/sh |
1431 | + |
1432 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
1433 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
1434 | +# Copyright (C) 2012-2013 Canonical Ltd. |
1435 | +# |
1436 | +# This program is free software: you can redistribute it and/or modify it under |
1437 | +# the terms of the GNU Affero General Public License version 3, as published by |
1438 | +# the Free Software Foundation. |
1439 | +# |
1440 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1441 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
1442 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
1443 | +# Affero General Public License for more details. |
1444 | +# |
1445 | +# You should have received a copy of the GNU Affero General Public License |
1446 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1447 | + |
1448 | +TESTDIR=`dirname $0` |
1449 | +VENV="$TESTDIR/.venv" |
1450 | +ACTIVATE="$VENV/bin/activate" |
1451 | +REQUIREMENTS="$TESTDIR/requirements.pip" |
1452 | + |
1453 | + |
1454 | +createvenv() { |
1455 | + # Create a virtualenv if it does not exist, or it is older than requirements. |
1456 | + if [ ! -f "$ACTIVATE" -o "$REQUIREMENTS" -nt "$ACTIVATE" ]; then |
1457 | + virtualenv --distribute $VENV |
1458 | + . $VENV/bin/activate && \ |
1459 | + yes w | pip install --use-mirrors -r $REQUIREMENTS |
1460 | + touch $VENV/bin/activate |
1461 | + fi |
1462 | +} |
1463 | + |
1464 | + |
1465 | +createvenv |
1466 | |
1467 | === renamed file 'tests/unit.test' => 'tests/10-unit.test' |
1468 | --- tests/unit.test 2012-12-07 21:16:55 +0000 |
1469 | +++ tests/10-unit.test 2013-07-17 21:35:43 +0000 |
1470 | @@ -1,5 +1,22 @@ |
1471 | -#!/usr/bin/env python2 |
1472 | -# -*- python -*- |
1473 | +#!tests/.venv/bin/python |
1474 | + |
1475 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
1476 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
1477 | +# Copyright (C) 2012-2013 Canonical Ltd. |
1478 | +# |
1479 | +# This program is free software: you can redistribute it and/or modify it under |
1480 | +# the terms of the GNU Affero General Public License version 3, as published by |
1481 | +# the Free Software Foundation. |
1482 | +# |
1483 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1484 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
1485 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
1486 | +# Affero General Public License for more details. |
1487 | +# |
1488 | +# You should have received a copy of the GNU Affero General Public License |
1489 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1490 | + |
1491 | +"""Unit test suite.""" |
1492 | |
1493 | import os |
1494 | import sys |
1495 | @@ -8,4 +25,5 @@ |
1496 | |
1497 | runner = unittest.TextTestRunner(verbosity=2) |
1498 | suite = unittest.TestLoader().discover(os.path.dirname(__file__)) |
1499 | -sys.exit(runner.run(suite)) |
1500 | +result = runner.run(suite) |
1501 | +sys.exit(not result.wasSuccessful()) |
1502 | |
1503 | === renamed file 'tests/deploy.test' => 'tests/20-functional.test' |
1504 | --- tests/deploy.test 2013-04-27 18:57:12 +0000 |
1505 | +++ tests/20-functional.test 2013-07-17 21:35:43 +0000 |
1506 | @@ -1,30 +1,47 @@ |
1507 | -#!/usr/bin/env python2 |
1508 | -#-*- python -*- |
1509 | - |
1510 | -import os |
1511 | +#!tests/.venv/bin/python |
1512 | + |
1513 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
1514 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
1515 | +# Copyright (C) 2012-2013 Canonical Ltd. |
1516 | +# |
1517 | +# This program is free software: you can redistribute it and/or modify it under |
1518 | +# the terms of the GNU Affero General Public License version 3, as published by |
1519 | +# the Free Software Foundation. |
1520 | +# |
1521 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1522 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
1523 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
1524 | +# Affero General Public License for more details. |
1525 | +# |
1526 | +# You should have received a copy of the GNU Affero General Public License |
1527 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1528 | + |
1529 | import unittest |
1530 | import urlparse |
1531 | |
1532 | -from charmhelpers import make_charm_config_file |
1533 | from selenium.webdriver import Firefox |
1534 | from selenium.webdriver.support import ui |
1535 | -from shelltoolbox import command |
1536 | from xvfbwrapper import Xvfb |
1537 | |
1538 | - |
1539 | -JUJU_GUI_SOURCE = os.getenv('JUJU_GUI_SOURCE') |
1540 | +from deploy import juju_deploy |
1541 | +from helpers import ( |
1542 | + juju_destroy_service, |
1543 | + juju_version, |
1544 | + ssh, |
1545 | +) |
1546 | + |
1547 | + |
1548 | JUJU_GUI_TEST_BRANCH = 'lp:~juju-gui/juju-gui/charm-tests-branch' |
1549 | STAGING_SERVICES = ('haproxy', 'mediawiki', 'memcached', 'mysql', 'wordpress') |
1550 | -jitsu = command('jitsu') |
1551 | -juju = command('juju') |
1552 | -ssh = command('ssh') |
1553 | +is_legacy_juju = juju_version().major == 0 |
1554 | |
1555 | |
1556 | class DeployTestMixin(object): |
1557 | |
1558 | + charm = 'juju-gui' |
1559 | + port = '443' |
1560 | + |
1561 | def setUp(self): |
1562 | - self.charm = 'juju-gui' |
1563 | - self.port = '443' |
1564 | # Perform all graphical operations in memory. |
1565 | vdisplay = Xvfb(width=1280, height=720) |
1566 | vdisplay.start() |
1567 | @@ -34,29 +51,14 @@ |
1568 | self.addCleanup(selenium.quit) |
1569 | |
1570 | def tearDown(self): |
1571 | - juju('destroy-service', self.charm) |
1572 | + juju_destroy_service(self.charm) |
1573 | |
1574 | def assertEnvironmentIsConnected(self): |
1575 | """Assert the GUI environment is connected to the Juju API agent.""" |
1576 | self.wait_for_script( |
1577 | - 'return app.env.get("connected");', |
1578 | + 'return app && app.env && app.env.get("connected");', |
1579 | error='Environment not connected.') |
1580 | |
1581 | - def make_config_file(self, options=None): |
1582 | - """Create a charm config file adding, if required, the Juju GUI source. |
1583 | - |
1584 | - Return the created config file object. |
1585 | - |
1586 | - The Juju GUI source can be provided by setting the environment variable |
1587 | - *JUJU_GUI_SOURCE*. This is just a simple wrapper around |
1588 | - ``charmhelpers.make_charm_config_file``. |
1589 | - """ |
1590 | - if options is None: |
1591 | - options = {} |
1592 | - if JUJU_GUI_SOURCE is not None: |
1593 | - options.setdefault('juju-gui-source', JUJU_GUI_SOURCE) |
1594 | - return make_charm_config_file({self.charm: options}) |
1595 | - |
1596 | def handle_browser_warning(self): |
1597 | """Overstep the browser warning dialog if required.""" |
1598 | self.wait_for_script( |
1599 | @@ -85,7 +87,7 @@ |
1600 | return driver.title == 'Juju Admin' |
1601 | self.wait_for(page_ready, error='Juju GUI not found.') |
1602 | |
1603 | - def wait_for(self, condition, error=None, timeout=20): |
1604 | + def wait_for(self, condition, error=None, timeout=30): |
1605 | """Wait for condition to be True. |
1606 | |
1607 | The argument condition is a callable accepting a driver object. |
1608 | @@ -95,7 +97,7 @@ |
1609 | wait = ui.WebDriverWait(self.selenium, timeout) |
1610 | return wait.until(condition, error) |
1611 | |
1612 | - def wait_for_css_selector(self, selector, error=None, timeout=20): |
1613 | + def wait_for_css_selector(self, selector, error=None, timeout=30): |
1614 | """Wait until the provided CSS selector is found. |
1615 | |
1616 | Fail printing the provided error if timeout is exceeded. |
1617 | @@ -106,7 +108,7 @@ |
1618 | elements = self.wait_for(condition, error=error, timeout=timeout) |
1619 | return elements[0] |
1620 | |
1621 | - def wait_for_script(self, script, error=None, timeout=20): |
1622 | + def wait_for_script(self, script, error=None, timeout=30): |
1623 | """Wait for the given JavaScript snippet to return a True value. |
1624 | |
1625 | Fail printing the provided error if timeout is exceeded. |
1626 | @@ -115,14 +117,6 @@ |
1627 | condition = lambda driver: driver.execute_script(script) |
1628 | return self.wait_for(condition, error=error, timeout=timeout) |
1629 | |
1630 | - def login(self, password): |
1631 | - """Log in to access the Juju GUI using the provided *password*.""" |
1632 | - form = self.wait_for_css_selector('form', 'Login form not found.') |
1633 | - passwd = form.find_element_by_css_selector('input[type=password]') |
1634 | - passwd.send_keys(password) |
1635 | - submit = form.find_element_by_css_selector('input[type=submit]') |
1636 | - submit.click() |
1637 | - |
1638 | def get_service_names(self): |
1639 | """Return the set of services' names displayed in the current page.""" |
1640 | def services_found(driver): |
1641 | @@ -135,8 +129,8 @@ |
1642 | # Just invoking ``juju destroy-service juju-gui`` in tearDown |
1643 | # should execute the ``stop`` hook, stopping all the services |
1644 | # started by the charm in the machine. Right now this does not |
1645 | - # work, so the same behavior is accomplished keeping track of |
1646 | - # started services and manually stopping them here. |
1647 | + # work in pyJuju, so the same behavior is accomplished keeping |
1648 | + # track of started services and manually stopping them here. |
1649 | target = 'ubuntu@{0}'.format(hostname) |
1650 | for service in services: |
1651 | ssh(target, 'sudo', 'service', service, 'stop') |
1652 | @@ -144,36 +138,24 @@ |
1653 | |
1654 | class DeployTest(DeployTestMixin, unittest.TestCase): |
1655 | |
1656 | - def deploy(self, config=None): |
1657 | - """Deploy and expose the Juju GUI charm. Return the service host name. |
1658 | - |
1659 | - Also wait until the service is started. |
1660 | - If *config_path* is provided, it will be used when deploying the charm. |
1661 | - """ |
1662 | - config_file = self.make_config_file(config) |
1663 | - juju('deploy', 'local:{0}'.format(self.charm), |
1664 | - '--config', config_file.name) |
1665 | - juju('expose', self.charm) |
1666 | - jitsu( |
1667 | - 'watch', '--failfast', self.charm, |
1668 | - '--state', 'started', '--open-port', self.port) |
1669 | - address = jitsu('get-service-info', self.charm, 'public-address') |
1670 | - return address.strip().split(':')[1] |
1671 | - |
1672 | def test_api_agent(self): |
1673 | # Ensure the Juju GUI and API agent services are correctly set up. |
1674 | - hostname = self.deploy() |
1675 | - # XXX 2012-11-29 frankban bug=872264: see *stop_services* above. |
1676 | - self.addCleanup( |
1677 | - self.stop_services, |
1678 | - hostname, ['haproxy', 'apache2', 'juju-api-agent']) |
1679 | + unit_info = juju_deploy(self.charm) |
1680 | + hostname = unit_info['public-address'] |
1681 | + if is_legacy_juju: |
1682 | + # XXX 2012-11-29 frankban bug=872264: see *stop_services* above. |
1683 | + self.addCleanup( |
1684 | + self.stop_services, |
1685 | + hostname, ['haproxy', 'apache2', 'juju-api-agent']) |
1686 | self.navigate_to(hostname) |
1687 | self.handle_browser_warning() |
1688 | self.assertEnvironmentIsConnected() |
1689 | |
1690 | + @unittest.skipUnless(is_legacy_juju, 'staging only works in pyJuju') |
1691 | def test_staging(self): |
1692 | # Ensure the Juju GUI and improv services are correctly set up. |
1693 | - hostname = self.deploy({'staging': 'true'}) |
1694 | + unit_info = juju_deploy(self.charm, options={'staging': 'true'}) |
1695 | + hostname = unit_info['public-address'] |
1696 | # XXX 2012-11-29 frankban bug=872264: see *stop_services* above. |
1697 | self.addCleanup( |
1698 | self.stop_services, |
1699 | @@ -186,44 +168,24 @@ |
1700 | |
1701 | def test_branch_source(self): |
1702 | # Ensure the Juju GUI is correctly deployed from a Bazaar branch. |
1703 | - hostname = self.deploy({'juju-gui-source': JUJU_GUI_TEST_BRANCH}) |
1704 | - # XXX 2012-11-29 frankban bug=872264: see *stop_services* above. |
1705 | - self.addCleanup( |
1706 | - self.stop_services, |
1707 | - hostname, ['haproxy', 'apache2', 'juju-api-agent']) |
1708 | + unit_info = juju_deploy( |
1709 | + self.charm, options={'juju-gui-source': JUJU_GUI_TEST_BRANCH}) |
1710 | + hostname = unit_info['public-address'] |
1711 | + if is_legacy_juju: |
1712 | + # XXX 2012-11-29 frankban bug=872264: see *stop_services* above. |
1713 | + self.addCleanup( |
1714 | + self.stop_services, |
1715 | + hostname, ['haproxy', 'apache2', 'juju-api-agent']) |
1716 | self.navigate_to(hostname) |
1717 | self.handle_browser_warning() |
1718 | self.assertEnvironmentIsConnected() |
1719 | |
1720 | - |
1721 | -class DeployToTest(DeployTestMixin, unittest.TestCase): |
1722 | - |
1723 | - def deploy_to(self): |
1724 | - """Deploy the Juju GUI charm in the Juju bootstrap node. |
1725 | - |
1726 | - Expose it and return the service host name. |
1727 | - Also wait until the service is started. |
1728 | - """ |
1729 | - config_file = self.make_config_file() |
1730 | - # The id of the bootstrap node is 0 (the first node started by Juju). |
1731 | - jitsu('deploy-to', '0', 'local:{0}'.format(self.charm), |
1732 | - '--config', config_file.name) |
1733 | - juju('expose', self.charm) |
1734 | - jitsu( |
1735 | - 'watch', '--failfast', self.charm, |
1736 | - '--state', 'started', '--open-port', self.port) |
1737 | - address = jitsu('get-service-info', self.charm, 'public-address') |
1738 | - return address.strip().split(':')[1] |
1739 | - |
1740 | - def test_api_agent(self): |
1741 | - # Ensure the Juju GUI and API agent services are correctly set up in |
1742 | - # the Juju bootstrap node. |
1743 | - hostname = self.deploy_to() |
1744 | - # XXX 2012-11-29 frankban bug=872264: see *stop_services* above. |
1745 | - self.addCleanup( |
1746 | - self.stop_services, |
1747 | - hostname, ['haproxy', 'apache2', 'juju-api-agent']) |
1748 | - self.navigate_to(hostname) |
1749 | + @unittest.skipIf(is_legacy_juju, 'force-machine only works in juju-core') |
1750 | + def test_force_machine(self): |
1751 | + # Ensure the Juju GUI is correctly set up in the Juju bootstrap node. |
1752 | + unit_info = juju_deploy(self.charm, force_machine=0) |
1753 | + self.assertEqual('0', unit_info['machine']) |
1754 | + self.navigate_to(unit_info['public-address']) |
1755 | self.handle_browser_warning() |
1756 | self.assertEnvironmentIsConnected() |
1757 | |
1758 | |
1759 | === added file 'tests/deploy.py' |
1760 | --- tests/deploy.py 1970-01-01 00:00:00 +0000 |
1761 | +++ tests/deploy.py 2013-07-17 21:35:43 +0000 |
1762 | @@ -0,0 +1,80 @@ |
1763 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
1764 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
1765 | +# Copyright (C) 2012-2013 Canonical Ltd. |
1766 | +# |
1767 | +# This program is free software: you can redistribute it and/or modify it under |
1768 | +# the terms of the GNU Affero General Public License version 3, as published by |
1769 | +# the Free Software Foundation. |
1770 | +# |
1771 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1772 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
1773 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
1774 | +# Affero General Public License for more details. |
1775 | +# |
1776 | +# You should have received a copy of the GNU Affero General Public License |
1777 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1778 | + |
1779 | +"""Juju GUI deploy helper.""" |
1780 | + |
1781 | +from __future__ import print_function |
1782 | +import json |
1783 | +import os |
1784 | +import tempfile |
1785 | + |
1786 | +from charmhelpers import make_charm_config_file |
1787 | + |
1788 | +from helpers import ( |
1789 | + command, |
1790 | + juju, |
1791 | + wait_for_unit, |
1792 | +) |
1793 | + |
1794 | + |
1795 | +rsync = command('rsync', '-a', '--exclude', '.bzr', '--exclude', '.venv') |
1796 | + |
1797 | + |
1798 | +def setup_repository(name, source, series='precise'): |
1799 | + """Create a temporary Juju repository to use for charm deployment. |
1800 | + |
1801 | + Copy the charm files in source in the precise repository section, using the |
1802 | + provided charm name and excluding the virtualenv and Bazaar directories. |
1803 | + |
1804 | + Return the repository path. |
1805 | + """ |
1806 | + source = os.path.abspath(source) + os.path.sep |
1807 | + repo = tempfile.mkdtemp() |
1808 | + destination = os.path.join(repo, series, name) |
1809 | + os.makedirs(destination) |
1810 | + rsync(source, destination) |
1811 | + return repo |
1812 | + |
1813 | + |
1814 | +def juju_deploy( |
1815 | + charm, options=None, force_machine=None, charm_source=None, |
1816 | + series='precise'): |
1817 | + """Deploy and expose the charm. Return the first unit's public address. |
1818 | + |
1819 | + Also wait until the service is exposed and the first unit started. |
1820 | + If options are provided, they will be used when deploying the charm. |
1821 | + If force_machine is not None, create the unit in the specified machine. |
1822 | + If charm_source is None, dynamically retrieve the charm source directory. |
1823 | + """ |
1824 | + if charm_source is None: |
1825 | + # Dynamically retrieve the charm source based on the path of this file. |
1826 | + charm_source = os.path.join(os.path.dirname(__file__), '..') |
1827 | + repo = setup_repository(charm, charm_source, series=series) |
1828 | + args = ['deploy', '--repository', repo] |
1829 | + if options is not None: |
1830 | + config_file = make_charm_config_file({charm: options}) |
1831 | + args.extend(['--config', config_file.name]) |
1832 | + if force_machine is not None: |
1833 | + args.extend(['--force-machine', str(force_machine)]) |
1834 | + args.append('local:{}/{}'.format(series, charm)) |
1835 | + juju(*args) |
1836 | + juju('expose', charm) |
1837 | + return wait_for_unit(charm) |
1838 | + |
1839 | + |
1840 | +if __name__ == '__main__': |
1841 | + unit = juju_deploy('juju-gui') |
1842 | + print(json.dumps(unit, indent=2)) |
1843 | |
1844 | === added file 'tests/helpers.py' |
1845 | --- tests/helpers.py 1970-01-01 00:00:00 +0000 |
1846 | +++ tests/helpers.py 2013-07-17 21:35:43 +0000 |
1847 | @@ -0,0 +1,185 @@ |
1848 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
1849 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
1850 | +# Copyright (C) 2012-2013 Canonical Ltd. |
1851 | +# |
1852 | +# This program is free software: you can redistribute it and/or modify it under |
1853 | +# the terms of the GNU Affero General Public License version 3, as published by |
1854 | +# the Free Software Foundation. |
1855 | +# |
1856 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
1857 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
1858 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
1859 | +# Affero General Public License for more details. |
1860 | +# |
1861 | +# You should have received a copy of the GNU Affero General Public License |
1862 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1863 | + |
1864 | +"""Juju GUI test helpers.""" |
1865 | + |
1866 | +from collections import namedtuple |
1867 | +from functools import wraps |
1868 | +import json |
1869 | +import os |
1870 | +import re |
1871 | +import subprocess |
1872 | +import time |
1873 | + |
1874 | + |
1875 | +class ProcessError(subprocess.CalledProcessError): |
1876 | + """Error running a shell command.""" |
1877 | + |
1878 | + def __init__(self, retcode, cmd, output, error): |
1879 | + super(ProcessError, self).__init__(retcode, cmd, output) |
1880 | + self.error = error |
1881 | + |
1882 | + def __str__(self): |
1883 | + msg = super(ProcessError, self).__str__() |
1884 | + return '{}. Output: {!r}. Error: {!r}.'.format( |
1885 | + msg, self.output, self.error) |
1886 | + |
1887 | + |
1888 | +def command(*base_args): |
1889 | + """Return a callable that will run the given command with any arguments. |
1890 | + |
1891 | + The first argument is the path to the command to run, subsequent arguments |
1892 | + are command-line arguments to "bake into" the returned callable. |
1893 | + |
1894 | + The callable runs the given executable and also takes arguments that will |
1895 | + be appended to the "baked in" arguments. |
1896 | + |
1897 | + For example, this code will list a file named "foo" (if it exists): |
1898 | + |
1899 | + ls_foo = command('/bin/ls', 'foo') |
1900 | + ls_foo() |
1901 | + |
1902 | + While this invocation will list "foo" and "bar" (assuming they exist): |
1903 | + |
1904 | + ls_foo('bar') |
1905 | + """ |
1906 | + PIPE = subprocess.PIPE |
1907 | + |
1908 | + def runner(*args, **kwargs): |
1909 | + cmd = base_args + args |
1910 | + process = subprocess.Popen(cmd, stdout=PIPE, stderr=PIPE, **kwargs) |
1911 | + output, error = process.communicate() |
1912 | + retcode = process.poll() |
1913 | + if retcode: |
1914 | + raise ProcessError(retcode, cmd, output, error) |
1915 | + return output |
1916 | + |
1917 | + return runner |
1918 | + |
1919 | + |
1920 | +juju_command = command('juju') |
1921 | +juju_env = lambda: os.getenv('JUJU_ENV') # This is propagated by juju-test. |
1922 | +ssh = command('ssh') |
1923 | +Version = namedtuple('Version', 'major minor patch') |
1924 | + |
1925 | + |
1926 | +def retry(exception, tries=10, delay=1): |
1927 | + """If the decorated function raises the exception, wait and try it again. |
1928 | + |
1929 | + Raise the exception raised by the last call if the function does not |
1930 | + exit normally after the specified number of tries. |
1931 | + |
1932 | + Original from http://wiki.python.org/moin/PythonDecoratorLibrary#Retry. |
1933 | + """ |
1934 | + def decorator(func): |
1935 | + @wraps(func) |
1936 | + def decorated(*args, **kwargs): |
1937 | + mtries = tries |
1938 | + while mtries: |
1939 | + try: |
1940 | + return func(*args, **kwargs) |
1941 | + except exception as err: |
1942 | + time.sleep(delay) |
1943 | + mtries -= 1 |
1944 | + raise err |
1945 | + return decorated |
1946 | + return decorator |
1947 | + |
1948 | + |
1949 | +@retry(ProcessError) |
1950 | +def juju(command, *args): |
1951 | + """Call the juju command, passing the environment parameters if required. |
1952 | + |
1953 | + The environment value can be provided in args, or can be found in the |
1954 | + context as JUJU_ENV. |
1955 | + """ |
1956 | + arguments = [command] |
1957 | + if ('-e' not in args) and ('--environment' not in args): |
1958 | + env = juju_env() |
1959 | + if env is not None: |
1960 | + arguments.extend(['-e', env]) |
1961 | + arguments.extend(args) |
1962 | + return juju_command(*arguments) |
1963 | + |
1964 | + |
1965 | +def juju_destroy_service(service): |
1966 | + """Destroy the given service and wait for the service to be removed.""" |
1967 | + juju('destroy-service', service) |
1968 | + while True: |
1969 | + services = juju_status().get('services', {}) |
1970 | + if service not in services: |
1971 | + return |
1972 | + |
1973 | + |
1974 | +def juju_status(): |
1975 | + """Return the Juju status as a dictionary.""" |
1976 | + status = juju('status', '--format', 'json') |
1977 | + return json.loads(status) |
1978 | + |
1979 | + |
1980 | +_juju_version_expression = re.compile(r""" |
1981 | + ^ # Beginning of line. |
1982 | + (?:juju\s+)? # Optional juju prefix. |
1983 | + (\d+)\.(\d+) # Major and minor versions. |
1984 | + (?:\.(\d+))? # Optional patch version. |
1985 | + .* # Optional suffix. |
1986 | + $ # End of line. |
1987 | +""", re.VERBOSE) |
1988 | + |
1989 | + |
1990 | +def juju_version(): |
1991 | + """Return the currently used Juju version. |
1992 | + |
1993 | + The version is returned as a named tuple (major, minor, patch). |
1994 | + If the patch number is missing, it is set to zero. |
1995 | + """ |
1996 | + try: |
1997 | + # In pyJuju, version info is printed to stderr. |
1998 | + output = subprocess.check_output( |
1999 | + ['juju', '--version'], stderr=subprocess.STDOUT) |
2000 | + except subprocess.CalledProcessError: |
2001 | + # Current juju-core exposes a version subcommand. |
2002 | + output = subprocess.check_output(['juju', 'version']) |
2003 | + match = _juju_version_expression.match(output) |
2004 | + if match is None: |
2005 | + raise ValueError('invalid juju version: {!r}'.format(output)) |
2006 | + to_int = lambda num: 0 if num is None else int(num) |
2007 | + return Version._make(map(to_int, match.groups())) |
2008 | + |
2009 | + |
2010 | +def wait_for_unit(sevice): |
2011 | + """Wait for the first unit of the given service to be started. |
2012 | + |
2013 | + Also wait for the service to be exposed. |
2014 | + Raise a RuntimeError if the unit is found in an error state. |
2015 | + Return info about the first unit as a dict containing at least the |
2016 | + following keys: agent-state, machine, and public-address. |
2017 | + """ |
2018 | + while True: |
2019 | + status = juju_status() |
2020 | + service = status.get('services', {}).get(sevice) |
2021 | + if service is None or not service.get('exposed'): |
2022 | + continue |
2023 | + units = service.get('units', {}) |
2024 | + if not len(units): |
2025 | + continue |
2026 | + unit = units.values()[0] |
2027 | + state = unit['agent-state'] |
2028 | + if 'error' in state: |
2029 | + raise RuntimeError( |
2030 | + 'the service unit is in an error state: {}'.format(state)) |
2031 | + if state == 'started': |
2032 | + return unit |
2033 | |
2034 | === added file 'tests/requirements.pip' |
2035 | --- tests/requirements.pip 1970-01-01 00:00:00 +0000 |
2036 | +++ tests/requirements.pip 2013-07-17 21:35:43 +0000 |
2037 | @@ -0,0 +1,28 @@ |
2038 | +# Juju GUI test requirements. |
2039 | + |
2040 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
2041 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
2042 | +# Copyright (C) 2012-2013 Canonical Ltd. |
2043 | +# |
2044 | +# This program is free software: you can redistribute it and/or modify it under |
2045 | +# the terms of the GNU Affero General Public License version 3, as published by |
2046 | +# the Free Software Foundation. |
2047 | +# |
2048 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
2049 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
2050 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
2051 | +# Affero General Public License for more details. |
2052 | +# |
2053 | +# You should have received a copy of the GNU Affero General Public License |
2054 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2055 | + |
2056 | +-e bzr+http://launchpad.net/charm-tools#egg=charm-tools |
2057 | +flake8==2.0 |
2058 | +launchpadlib==1.10.2 |
2059 | +mock==1.0.1 |
2060 | +python-apt==0.8.5 |
2061 | +-e bzr+http://launchpad.net/python-shelltoolbox#egg=python-shelltoolbox |
2062 | +PyYAML==3.10 |
2063 | +selenium==2.33.0 |
2064 | +Tempita==0.5.1 |
2065 | +xvfbwrapper==0.2.2 |
2066 | |
2067 | === modified file 'tests/test_backends.py' |
2068 | --- tests/test_backends.py 2013-05-02 16:51:14 +0000 |
2069 | +++ tests/test_backends.py 2013-07-17 21:35:43 +0000 |
2070 | @@ -1,3 +1,19 @@ |
2071 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
2072 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
2073 | +# Copyright (C) 2012-2013 Canonical Ltd. |
2074 | +# |
2075 | +# This program is free software: you can redistribute it and/or modify it under |
2076 | +# the terms of the GNU Affero General Public License version 3, as published by |
2077 | +# the Free Software Foundation. |
2078 | +# |
2079 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
2080 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
2081 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
2082 | +# Affero General Public License for more details. |
2083 | +# |
2084 | +# You should have received a copy of the GNU Affero General Public License |
2085 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2086 | + |
2087 | from collections import defaultdict |
2088 | from contextlib import contextmanager |
2089 | import os |
2090 | @@ -34,7 +50,7 @@ |
2091 | frozenset(('apache2', 'curl', 'haproxy', 'openssl', 'zookeeper')), |
2092 | test_backend.debs) |
2093 | self.assertEqual( |
2094 | - frozenset(('ppa:juju-gui/ppa',)), |
2095 | + frozenset(()), |
2096 | test_backend.repositories) |
2097 | self.assertEqual( |
2098 | frozenset(('haproxy.conf',)), |
2099 | @@ -51,7 +67,7 @@ |
2100 | frozenset(('apache2', 'curl', 'haproxy', 'openssl')), |
2101 | test_backend.debs) |
2102 | self.assertEqual( |
2103 | - frozenset(('ppa:juju-gui/ppa',)), |
2104 | + frozenset(()), |
2105 | test_backend.repositories) |
2106 | self.assertEqual( |
2107 | frozenset(('haproxy.conf',)), |
2108 | @@ -68,7 +84,7 @@ |
2109 | frozenset(('apache2', 'curl', 'haproxy', 'openssl')), |
2110 | test_backend.debs) |
2111 | self.assertEqual( |
2112 | - frozenset(('ppa:juju-gui/ppa',)), |
2113 | + frozenset(()), |
2114 | test_backend.repositories) |
2115 | self.assertEqual( |
2116 | frozenset(('haproxy.conf',)), |
2117 | @@ -97,7 +113,7 @@ |
2118 | ('apache2', 'curl', 'haproxy', 'openssl', 'python-yaml')), |
2119 | test_backend.debs) |
2120 | self.assertEqual( |
2121 | - frozenset(('ppa:juju-gui/ppa',)), |
2122 | + frozenset(()), |
2123 | test_backend.repositories) |
2124 | self.assertEqual( |
2125 | frozenset(('haproxy.conf',)), |
2126 | @@ -192,14 +208,16 @@ |
2127 | def test_start_agent(self): |
2128 | test_backend = backend.Backend(config=self.alwaysFalse) |
2129 | test_backend.start() |
2130 | - for mocked in ('service_control', 'start_agent', 'start_gui', |
2131 | + for mocked in ( |
2132 | + 'service_control', 'start_agent', 'start_gui', |
2133 | 'open_port', 'su'): |
2134 | self.assertTrue(mocked, '{} was not called'.format(mocked)) |
2135 | |
2136 | def test_start_improv(self): |
2137 | test_backend = backend.Backend(config=self.alwaysTrue) |
2138 | test_backend.start() |
2139 | - for mocked in ('service_control', 'start_improv', 'start_gui', |
2140 | + for mocked in ( |
2141 | + 'service_control', 'start_improv', 'start_gui', |
2142 | 'open_port', 'su'): |
2143 | self.assertTrue(mocked, '{} was not called'.format(mocked)) |
2144 | |
2145 | |
2146 | === added file 'tests/test_deploy.py' |
2147 | --- tests/test_deploy.py 1970-01-01 00:00:00 +0000 |
2148 | +++ tests/test_deploy.py 2013-07-17 21:35:43 +0000 |
2149 | @@ -0,0 +1,196 @@ |
2150 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
2151 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
2152 | +# Copyright (C) 2012-2013 Canonical Ltd. |
2153 | +# |
2154 | +# This program is free software: you can redistribute it and/or modify it under |
2155 | +# the terms of the GNU Affero General Public License version 3, as published by |
2156 | +# the Free Software Foundation. |
2157 | +# |
2158 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
2159 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
2160 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
2161 | +# Affero General Public License for more details. |
2162 | +# |
2163 | +# You should have received a copy of the GNU Affero General Public License |
2164 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2165 | + |
2166 | +"""Juju GUI deploy module tests.""" |
2167 | + |
2168 | +import os |
2169 | +import shutil |
2170 | +import tempfile |
2171 | +import unittest |
2172 | + |
2173 | +import mock |
2174 | + |
2175 | +from deploy import ( |
2176 | + juju_deploy, |
2177 | + setup_repository, |
2178 | +) |
2179 | + |
2180 | + |
2181 | +class TestSetupRepository(unittest.TestCase): |
2182 | + |
2183 | + name = 'test-charm' |
2184 | + |
2185 | + def setUp(self): |
2186 | + # Create a directory structure for the charm source. |
2187 | + self.source = tempfile.mkdtemp() |
2188 | + self.addCleanup(shutil.rmtree, self.source) |
2189 | + # Create a file in the source dir. |
2190 | + _, self.root_file = tempfile.mkstemp(dir=self.source) |
2191 | + # Create a Bazaar repository directory with a file in it. |
2192 | + bzr_dir = os.path.join(self.source, '.bzr') |
2193 | + os.mkdir(bzr_dir) |
2194 | + tempfile.mkstemp(dir=bzr_dir) |
2195 | + # Create a tests directory including a .venv directory and a file. |
2196 | + self.tests_dir = os.path.join(self.source, 'tests') |
2197 | + venv_dir = os.path.join(self.tests_dir, '.venv') |
2198 | + os.makedirs(venv_dir) |
2199 | + tempfile.mkstemp(dir=venv_dir) |
2200 | + # Create a test file. |
2201 | + _, self.tests_file = tempfile.mkstemp(dir=self.tests_dir) |
2202 | + |
2203 | + def assert_dir_exists(self, path): |
2204 | + self.assertTrue( |
2205 | + os.path.isdir(path), |
2206 | + 'the directory {!r} does not exist'.format(path)) |
2207 | + |
2208 | + def assert_files_equal(self, expected, path): |
2209 | + fileset = set() |
2210 | + for dirpath, _, filenames in os.walk(path): |
2211 | + relpath = os.path.relpath(dirpath, path) |
2212 | + if relpath == '.': |
2213 | + relpath = '' |
2214 | + else: |
2215 | + fileset.add(relpath + os.path.sep) |
2216 | + fileset.update(os.path.join(relpath, name) for name in filenames) |
2217 | + self.assertEqual(expected, fileset) |
2218 | + |
2219 | + def check_repository(self, repo, series): |
2220 | + # The repository has been created in the temp directory. |
2221 | + self.assertEqual(tempfile.tempdir, os.path.split(repo)[0]) |
2222 | + self.assert_dir_exists(repo) |
2223 | + # The repository only contains the series directory. |
2224 | + self.assertEqual([series], os.listdir(repo)) |
2225 | + series_dir = os.path.join(repo, series) |
2226 | + self.assert_dir_exists(series_dir) |
2227 | + # The series directory only contains our charm. |
2228 | + self.assertEqual([self.name], os.listdir(series_dir)) |
2229 | + self.assert_dir_exists(os.path.join(series_dir, self.name)) |
2230 | + |
2231 | + def test_repository(self): |
2232 | + # The charm repository is correctly created with the default series. |
2233 | + repo = setup_repository(self.name, self.source) |
2234 | + self.check_repository(repo, 'precise') |
2235 | + |
2236 | + def test_series(self): |
2237 | + # The charm repository is created with the given series. |
2238 | + repo = setup_repository(self.name, self.source, series='raring') |
2239 | + self.check_repository(repo, 'raring') |
2240 | + |
2241 | + def test_charm_files(self): |
2242 | + # The charm files are correctly copied inside the repository, excluding |
2243 | + # unwanted directories. |
2244 | + repo = setup_repository(self.name, self.source) |
2245 | + charm_dir = os.path.join(repo, 'precise', self.name) |
2246 | + test_dir_name = os.path.basename(self.tests_dir) |
2247 | + expected = set([ |
2248 | + os.path.basename(self.root_file), |
2249 | + test_dir_name + os.path.sep, |
2250 | + os.path.join(test_dir_name, os.path.basename(self.tests_file)) |
2251 | + ]) |
2252 | + self.assert_files_equal(expected, charm_dir) |
2253 | + |
2254 | + |
2255 | +class TestJujuDeploy(unittest.TestCase): |
2256 | + |
2257 | + unit_info = {'public-address': 'unit.example.com'} |
2258 | + charm = 'test-charm' |
2259 | + expose_call = mock.call('expose', charm) |
2260 | + local_charm = 'local:precise/{}'.format(charm) |
2261 | + repo = '/tmp/repo/' |
2262 | + |
2263 | + @mock.patch('deploy.juju') |
2264 | + @mock.patch('deploy.wait_for_unit') |
2265 | + @mock.patch('deploy.setup_repository') |
2266 | + def call_deploy( |
2267 | + self, mock_setup_repository, mock_wait_for_unit, mock_juju, |
2268 | + options=None, force_machine=None, charm_source=None, |
2269 | + series='precise'): |
2270 | + mock_setup_repository.return_value = self.repo |
2271 | + mock_wait_for_unit.return_value = self.unit_info |
2272 | + if charm_source is None: |
2273 | + expected_source = os.path.join(os.path.dirname(__file__), '..') |
2274 | + else: |
2275 | + expected_source = charm_source |
2276 | + unit_info = juju_deploy( |
2277 | + self.charm, options=options, force_machine=force_machine, |
2278 | + charm_source=charm_source, series=series) |
2279 | + mock_setup_repository.assert_called_once_with( |
2280 | + self.charm, expected_source, series=series) |
2281 | + # The unit address is correctly returned. |
2282 | + self.assertEqual(self.unit_info, unit_info) |
2283 | + self.assertEqual(1, mock_wait_for_unit.call_count) |
2284 | + # Juju is called two times: deploy and expose. |
2285 | + juju_calls = mock_juju.call_args_list |
2286 | + self.assertEqual(2, len(juju_calls)) |
2287 | + deploy_call, expose_call = juju_calls |
2288 | + self.assertEqual(self.expose_call, expose_call) |
2289 | + return deploy_call |
2290 | + |
2291 | + def test_deployment(self): |
2292 | + # The function deploys and exposes the given charm. |
2293 | + expected_deploy_call = mock.call( |
2294 | + 'deploy', |
2295 | + '--repository', self.repo, |
2296 | + self.local_charm, |
2297 | + ) |
2298 | + deploy_call = self.call_deploy() |
2299 | + self.assertEqual(expected_deploy_call, deploy_call) |
2300 | + |
2301 | + def test_options(self): |
2302 | + # The function handles charm options. |
2303 | + mock_config_file = mock.Mock() |
2304 | + mock_config_file.name = '/tmp/config.yaml' |
2305 | + expected_deploy_call = mock.call( |
2306 | + 'deploy', |
2307 | + '--repository', self.repo, |
2308 | + '--config', mock_config_file.name, |
2309 | + self.local_charm, |
2310 | + ) |
2311 | + with mock.patch('deploy.make_charm_config_file') as mock_callable: |
2312 | + mock_callable.return_value = mock_config_file |
2313 | + deploy_call = self.call_deploy(options={'foo': 'bar'}) |
2314 | + self.assertEqual(expected_deploy_call, deploy_call) |
2315 | + |
2316 | + def test_force_machine(self): |
2317 | + # The function can deploy charms in a specified machine. |
2318 | + expected_deploy_call = mock.call( |
2319 | + 'deploy', |
2320 | + '--repository', self.repo, |
2321 | + '--force-machine', '42', |
2322 | + self.local_charm, |
2323 | + ) |
2324 | + deploy_call = self.call_deploy(force_machine=42) |
2325 | + self.assertEqual(expected_deploy_call, deploy_call) |
2326 | + |
2327 | + def test_charm_source(self): |
2328 | + # The function can deploy a charm from a specific source. |
2329 | + expected_deploy_call = mock.call( |
2330 | + 'deploy', |
2331 | + '--repository', self.repo, |
2332 | + self.local_charm, |
2333 | + ) |
2334 | + deploy_call = self.call_deploy(charm_source='/tmp/source/') |
2335 | + self.assertEqual(expected_deploy_call, deploy_call) |
2336 | + |
2337 | + def test_series(self): |
2338 | + # The function can deploy a charm from a specific series. |
2339 | + expected_deploy_call = mock.call( |
2340 | + 'deploy', |
2341 | + '--repository', self.repo, |
2342 | + 'local:raring/{}'.format(self.charm) |
2343 | + ) |
2344 | + deploy_call = self.call_deploy(series='raring') |
2345 | + self.assertEqual(expected_deploy_call, deploy_call) |
2346 | |
2347 | === added file 'tests/test_helpers.py' |
2348 | --- tests/test_helpers.py 1970-01-01 00:00:00 +0000 |
2349 | +++ tests/test_helpers.py 2013-07-17 21:35:43 +0000 |
2350 | @@ -0,0 +1,397 @@ |
2351 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
2352 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
2353 | +# Copyright (C) 2012-2013 Canonical Ltd. |
2354 | +# |
2355 | +# This program is free software: you can redistribute it and/or modify it under |
2356 | +# the terms of the GNU Affero General Public License version 3, as published by |
2357 | +# the Free Software Foundation. |
2358 | +# |
2359 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
2360 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
2361 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
2362 | +# Affero General Public License for more details. |
2363 | +# |
2364 | +# You should have received a copy of the GNU Affero General Public License |
2365 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2366 | + |
2367 | +"""Juju GUI helpers tests.""" |
2368 | + |
2369 | +import json |
2370 | +import subprocess |
2371 | +import unittest |
2372 | + |
2373 | +import mock |
2374 | + |
2375 | +from helpers import ( |
2376 | + command, |
2377 | + juju, |
2378 | + juju_destroy_service, |
2379 | + juju_env, |
2380 | + juju_status, |
2381 | + juju_version, |
2382 | + ProcessError, |
2383 | + retry, |
2384 | + Version, |
2385 | + wait_for_unit, |
2386 | +) |
2387 | + |
2388 | + |
2389 | +class TestCommand(unittest.TestCase): |
2390 | + |
2391 | + def test_simple_command(self): |
2392 | + # Creating a simple command (ls) works and running the command |
2393 | + # produces a string. |
2394 | + ls = command('/bin/ls') |
2395 | + self.assertIsInstance(ls(), str) |
2396 | + |
2397 | + def test_arguments(self): |
2398 | + # Arguments can be passed to commands. |
2399 | + ls = command('/bin/ls') |
2400 | + self.assertIn('Usage:', ls('--help')) |
2401 | + |
2402 | + def test_missing(self): |
2403 | + # If the command does not exist, an OSError (No such file or |
2404 | + # directory) is raised. |
2405 | + bad = command('this command does not exist') |
2406 | + with self.assertRaises(OSError) as info: |
2407 | + bad() |
2408 | + self.assertEqual(2, info.exception.errno) |
2409 | + |
2410 | + def test_error(self): |
2411 | + # If the command returns a non-zero exit code, an exception is raised. |
2412 | + bad = command('/bin/ls', '--not a valid switch') |
2413 | + self.assertRaises(ProcessError, bad) |
2414 | + |
2415 | + def test_baked_in_arguments(self): |
2416 | + # Arguments can be passed when creating the command as well as when |
2417 | + # executing it. |
2418 | + ll = command('/bin/ls', '-al') |
2419 | + self.assertIn('rw', ll()) # Assumes a file is r/w in the pwd. |
2420 | + self.assertIn('Usage:', ll('--help')) |
2421 | + |
2422 | + def test_quoting(self): |
2423 | + # There is no need to quote special shell characters in commands. |
2424 | + ls = command('/bin/ls') |
2425 | + ls('--help', '>') |
2426 | + |
2427 | + |
2428 | +@mock.patch('helpers.juju_command') |
2429 | +class TestJuju(unittest.TestCase): |
2430 | + |
2431 | + env = 'test-env' |
2432 | + patch_environ = mock.patch('os.environ', {'JUJU_ENV': env}) |
2433 | + process_error = ProcessError(1, 'an error occurred', 'output', 'error') |
2434 | + |
2435 | + def test_e_in_args(self, mock_juju_command): |
2436 | + # The command includes the environment if provided with -e. |
2437 | + with self.patch_environ: |
2438 | + juju('deploy', '-e', 'another-env', 'test-charm') |
2439 | + mock_juju_command.assert_called_once_with( |
2440 | + 'deploy', '-e', 'another-env', 'test-charm') |
2441 | + |
2442 | + def test_environment_in_args(self, mock_juju_command): |
2443 | + # The command includes the environment if provided with --environment. |
2444 | + with self.patch_environ: |
2445 | + juju('deploy', '--environment', 'another-env', 'test-charm') |
2446 | + mock_juju_command.assert_called_once_with( |
2447 | + 'deploy', '--environment', 'another-env', 'test-charm') |
2448 | + |
2449 | + def test_environment_in_context(self, mock_juju_command): |
2450 | + # The command includes the environment if found in the context as |
2451 | + # the environment variable JUJU_ENV. |
2452 | + with self.patch_environ: |
2453 | + juju('deploy', 'test-charm') |
2454 | + mock_juju_command.assert_called_once_with( |
2455 | + 'deploy', '-e', self.env, 'test-charm') |
2456 | + |
2457 | + def test_environment_not_in_context(self, mock_juju_command): |
2458 | + # The command does not include the environment if not found in the |
2459 | + # context as the environment variable JUJU_ENV. |
2460 | + with mock.patch('os.environ', {}): |
2461 | + juju('deploy', 'test-charm') |
2462 | + mock_juju_command.assert_called_once_with('deploy', 'test-charm') |
2463 | + |
2464 | + def test_handle_process_errors(self, mock_juju_command): |
2465 | + # The command retries several times before failing if a ProcessError is |
2466 | + # raised. |
2467 | + mock_juju_command.side_effect = ([self.process_error] * 9) + ['value'] |
2468 | + with mock.patch('time.sleep') as mock_sleep: |
2469 | + with mock.patch('os.environ', {}): |
2470 | + result = juju('deploy', 'test-charm') |
2471 | + self.assertEqual('value', result) |
2472 | + self.assertEqual(10, mock_juju_command.call_count) |
2473 | + mock_juju_command.assert_called_with('deploy', 'test-charm') |
2474 | + self.assertEqual(9, mock_sleep.call_count) |
2475 | + mock_sleep.assert_called_with(1) |
2476 | + |
2477 | + def test_raise_process_errors(self, mock_juju_command): |
2478 | + # The command raises the last ProcessError after a number of retries. |
2479 | + mock_juju_command.side_effect = [self.process_error] * 10 |
2480 | + with mock.patch('time.sleep') as mock_sleep: |
2481 | + with mock.patch('os.environ', {}): |
2482 | + with self.assertRaises(ProcessError) as info: |
2483 | + juju('deploy', 'test-charm') |
2484 | + self.assertIs(self.process_error, info.exception) |
2485 | + self.assertEqual(10, mock_juju_command.call_count) |
2486 | + mock_juju_command.assert_called_with('deploy', 'test-charm') |
2487 | + self.assertEqual(10, mock_sleep.call_count) |
2488 | + mock_sleep.assert_called_with(1) |
2489 | + |
2490 | + |
2491 | +@mock.patch('helpers.juju') |
2492 | +@mock.patch('helpers.juju_status') |
2493 | +class TestJujuDestroyService(unittest.TestCase): |
2494 | + |
2495 | + service = 'test-service' |
2496 | + |
2497 | + def test_service_destroyed(self, mock_juju_status, mock_juju): |
2498 | + # The juju destroy-service command is correctly called. |
2499 | + mock_juju_status.return_value = {} |
2500 | + juju_destroy_service(self.service) |
2501 | + self.assertEqual(1, mock_juju_status.call_count) |
2502 | + mock_juju.assert_called_once_with('destroy-service', self.service) |
2503 | + |
2504 | + def test_wait_until_removed(self, mock_juju_status, mock_juju): |
2505 | + # The function waits for the service to be removed. |
2506 | + mock_juju_status.side_effect = ( |
2507 | + {'services': {self.service: {}, 'another-service': {}}}, |
2508 | + {'services': {'another-service': {}}}, |
2509 | + ) |
2510 | + juju_destroy_service(self.service) |
2511 | + self.assertEqual(2, mock_juju_status.call_count) |
2512 | + mock_juju.assert_called_once_with('destroy-service', self.service) |
2513 | + |
2514 | + |
2515 | +class TestJujuEnv(unittest.TestCase): |
2516 | + |
2517 | + def test_env_in_context(self): |
2518 | + # The function returns the juju env if found in the execution context. |
2519 | + with mock.patch('os.environ', {'JUJU_ENV': 'test-env'}): |
2520 | + self.assertEqual('test-env', juju_env()) |
2521 | + |
2522 | + def test_env_not_in_context(self): |
2523 | + # The function returns None if JUJU_ENV is not included in the context. |
2524 | + with mock.patch('os.environ', {}): |
2525 | + self.assertIsNone(juju_env()) |
2526 | + |
2527 | + |
2528 | +class TestJujuStatus(unittest.TestCase): |
2529 | + |
2530 | + status = { |
2531 | + 'machines': { |
2532 | + '0': {'agent-state': 'running', 'dns-name': 'ec2.example.com'}, |
2533 | + }, |
2534 | + 'services': { |
2535 | + 'juju-gui': {'charm': 'cs:precise/juju-gui-48', 'exposed': True}, |
2536 | + }, |
2537 | + } |
2538 | + |
2539 | + @mock.patch('helpers.juju') |
2540 | + def test_status(self, mock_juju): |
2541 | + # The function returns the unserialized juju status. |
2542 | + mock_juju.return_value = json.dumps(self.status) |
2543 | + status = juju_status() |
2544 | + self.assertEqual(self.status, status) |
2545 | + mock_juju.assert_called_once_with('status', '--format', 'json') |
2546 | + |
2547 | + |
2548 | +@mock.patch('subprocess.check_output') |
2549 | +class TestJujuVersion(unittest.TestCase): |
2550 | + |
2551 | + error = subprocess.CalledProcessError(2, 'invalid flag', 'output') |
2552 | + |
2553 | + def test_pyjuju(self, mock_check_output): |
2554 | + # The pyJuju version is correctly retrieved. |
2555 | + mock_check_output.return_value = '0.7.2' |
2556 | + version = juju_version() |
2557 | + self.assertEqual(Version(0, 7, 2), version) |
2558 | + mock_check_output.assert_called_once_with( |
2559 | + ['juju', '--version'], stderr=subprocess.STDOUT, |
2560 | + ) |
2561 | + |
2562 | + def test_juju_core(self, mock_check_output): |
2563 | + # The juju-core version is correctly retrieved. |
2564 | + mock_check_output.side_effect = (self.error, '1.12.3') |
2565 | + version = juju_version() |
2566 | + self.assertEqual(Version(1, 12, 3), version) |
2567 | + self.assertEqual(2, mock_check_output.call_count) |
2568 | + first_call, second_call = mock_check_output.call_args_list |
2569 | + self.assertEqual( |
2570 | + mock.call(['juju', '--version'], stderr=subprocess.STDOUT), |
2571 | + first_call, |
2572 | + ) |
2573 | + self.assertEqual(mock.call(['juju', 'version']), second_call) |
2574 | + |
2575 | + def test_not_semantic_versioning(self, mock_check_output): |
2576 | + # If the patch number is missing, it is set to zero. |
2577 | + mock_check_output.return_value = '0.7' |
2578 | + version = juju_version() |
2579 | + self.assertEqual(Version(0, 7, 0), version) |
2580 | + |
2581 | + def test_prefix(self, mock_check_output): |
2582 | + # The function handles versions returned as "juju x.y.z". |
2583 | + mock_check_output.return_value = 'juju 0.8.3' |
2584 | + version = juju_version() |
2585 | + self.assertEqual(Version(0, 8, 3), version) |
2586 | + |
2587 | + def test_suffix(self, mock_check_output): |
2588 | + # The function handles versions returned as "x.y.z-series-arch". |
2589 | + mock_check_output.return_value = '1.10.3-raring-amd64' |
2590 | + version = juju_version() |
2591 | + self.assertEqual(Version(1, 10, 3), version) |
2592 | + |
2593 | + def test_all(self, mock_check_output): |
2594 | + # Additional information is correctly handled. |
2595 | + mock_check_output.side_effect = (self.error, 'juju 1.234-precise-i386') |
2596 | + version = juju_version() |
2597 | + self.assertEqual(Version(1, 234, 0), version) |
2598 | + self.assertEqual(2, mock_check_output.call_count) |
2599 | + |
2600 | + def test_invalid_version(self, mock_check_output): |
2601 | + # A ValueError is raised if the returned version is not valid. |
2602 | + mock_check_output.return_value = '42' |
2603 | + with self.assertRaises(ValueError) as info: |
2604 | + juju_version() |
2605 | + self.assertEqual("invalid juju version: '42'", str(info.exception)) |
2606 | + |
2607 | + def test_failure(self, mock_check_output): |
2608 | + # A CalledProcessError is raised if the Juju version cannot be found. |
2609 | + mock_check_output.side_effect = (self.error, self.error) |
2610 | + with self.assertRaises(subprocess.CalledProcessError) as info: |
2611 | + juju_version() |
2612 | + self.assertIs(self.error, info.exception) |
2613 | + self.assertEqual(2, mock_check_output.call_count) |
2614 | + |
2615 | + |
2616 | +class TestProcessError(unittest.TestCase): |
2617 | + |
2618 | + def test_str(self): |
2619 | + # The string representation of the error includes required info. |
2620 | + err = ProcessError(1, 'mycommand', 'myoutput', 'myerror') |
2621 | + expected = ( |
2622 | + "Command 'mycommand' returned non-zero exit status 1. " |
2623 | + "Output: 'myoutput'. Error: 'myerror'." |
2624 | + ) |
2625 | + self.assertEqual(expected, str(err)) |
2626 | + |
2627 | + |
2628 | +@mock.patch('time.sleep') |
2629 | +class TestRetry(unittest.TestCase): |
2630 | + |
2631 | + retry_type_error = retry(TypeError, tries=10, delay=1) |
2632 | + result = 'my value' |
2633 | + |
2634 | + def make_callable(self, side_effect): |
2635 | + mock_callable = mock.Mock() |
2636 | + mock_callable.side_effect = side_effect |
2637 | + mock_callable.__name__ = 'mock_callable' # Required by wraps. |
2638 | + decorated = retry(TypeError, tries=5, delay=1)(mock_callable) |
2639 | + return mock_callable, decorated |
2640 | + |
2641 | + def test_immediate_success(self, mock_sleep): |
2642 | + # The decorated function returns without retrying if no errors occur. |
2643 | + mock_callable, decorated = self.make_callable([self.result]) |
2644 | + result = decorated() |
2645 | + self.assertEqual(self.result, result) |
2646 | + self.assertEqual(1, mock_callable.call_count) |
2647 | + self.assertFalse(mock_sleep.called) |
2648 | + |
2649 | + def test_success(self, mock_sleep): |
2650 | + # The decorated function returns without errors after several tries. |
2651 | + side_effect = ([TypeError] * 4) + [self.result] |
2652 | + mock_callable, decorated = self.make_callable(side_effect) |
2653 | + result = decorated() |
2654 | + self.assertEqual(self.result, result) |
2655 | + self.assertEqual(5, mock_callable.call_count) |
2656 | + self.assertEqual(4, mock_sleep.call_count) |
2657 | + mock_sleep.assert_called_with(1) |
2658 | + |
2659 | + def test_failure(self, mock_sleep): |
2660 | + # The decorated function raises the last error. |
2661 | + mock_callable, decorated = self.make_callable([TypeError] * 5) |
2662 | + self.assertRaises(TypeError, decorated) |
2663 | + self.assertEqual(5, mock_callable.call_count) |
2664 | + self.assertEqual(5, mock_sleep.call_count) |
2665 | + mock_sleep.assert_called_with(1) |
2666 | + |
2667 | + |
2668 | +@mock.patch('helpers.juju_status') |
2669 | +class TestWaitForService(unittest.TestCase): |
2670 | + |
2671 | + address = 'unit.example.com' |
2672 | + service = 'test-service' |
2673 | + |
2674 | + def get_status(self, state='started', exposed=True, unit=0): |
2675 | + """Return a dict-like Juju status.""" |
2676 | + unit_name = '{}/{}'.format(self.service, unit) |
2677 | + return { |
2678 | + 'services': { |
2679 | + self.service: { |
2680 | + 'exposed': exposed, |
2681 | + 'units': { |
2682 | + unit_name: { |
2683 | + 'agent-state': state, |
2684 | + 'public-address': self.address, |
2685 | + } |
2686 | + }, |
2687 | + }, |
2688 | + }, |
2689 | + } |
2690 | + |
2691 | + def test_service_not_deployed(self, mock_juju_status): |
2692 | + # The function waits until the service is deployed. |
2693 | + mock_juju_status.side_effect = ( |
2694 | + {}, {'services': {}}, self.get_status(), |
2695 | + ) |
2696 | + unit_info = wait_for_unit(self.service) |
2697 | + self.assertEqual(self.address, unit_info['public-address']) |
2698 | + self.assertEqual(3, mock_juju_status.call_count) |
2699 | + |
2700 | + def test_service_not_exposed(self, mock_juju_status): |
2701 | + # The function waits until the service is exposed. |
2702 | + mock_juju_status.side_effect = ( |
2703 | + self.get_status(exposed=False), self.get_status(), |
2704 | + ) |
2705 | + unit_info = wait_for_unit(self.service) |
2706 | + self.assertEqual(self.address, unit_info['public-address']) |
2707 | + self.assertEqual(2, mock_juju_status.call_count) |
2708 | + |
2709 | + def test_unit_not_ready(self, mock_juju_status): |
2710 | + # The function waits until the unit is created. |
2711 | + mock_juju_status.side_effect = ( |
2712 | + {'services': {'juju-gui': {}}}, |
2713 | + {'services': {'juju-gui': {'units': {}}}}, |
2714 | + self.get_status(), |
2715 | + ) |
2716 | + unit_info = wait_for_unit(self.service) |
2717 | + self.assertEqual(self.address, unit_info['public-address']) |
2718 | + self.assertEqual(3, mock_juju_status.call_count) |
2719 | + |
2720 | + def test_state_error(self, mock_juju_status): |
2721 | + # An error is raised if the unit is in an error state. |
2722 | + mock_juju_status.return_value = self.get_status(state='install-error') |
2723 | + self.assertRaises(RuntimeError, wait_for_unit, self.service) |
2724 | + self.assertEqual(1, mock_juju_status.call_count) |
2725 | + |
2726 | + def test_not_started(self, mock_juju_status): |
2727 | + # The function waits until the unit is in a started state. |
2728 | + mock_juju_status.side_effect = ( |
2729 | + self.get_status(state='pending'), self.get_status(), |
2730 | + ) |
2731 | + unit_info = wait_for_unit(self.service) |
2732 | + self.assertEqual(self.address, unit_info['public-address']) |
2733 | + self.assertEqual(2, mock_juju_status.call_count) |
2734 | + |
2735 | + def test_unit_number(self, mock_juju_status): |
2736 | + # Different unit names are correctly handled. |
2737 | + mock_juju_status.return_value = self.get_status(unit=42) |
2738 | + unit_info = wait_for_unit(self.service) |
2739 | + self.assertEqual(self.address, unit_info['public-address']) |
2740 | + self.assertEqual(1, mock_juju_status.call_count) |
2741 | + |
2742 | + def test_public_address(self, mock_juju_status): |
2743 | + # The public address is returned when the service is ready. |
2744 | + mock_juju_status.return_value = self.get_status() |
2745 | + unit_info = wait_for_unit(self.service) |
2746 | + self.assertEqual(self.address, unit_info['public-address']) |
2747 | + self.assertEqual(1, mock_juju_status.call_count) |
2748 | |
2749 | === modified file 'tests/test_utils.py' |
2750 | --- tests/test_utils.py 2013-07-11 19:10:30 +0000 |
2751 | +++ tests/test_utils.py 2013-07-17 21:35:43 +0000 |
2752 | @@ -1,14 +1,31 @@ |
2753 | -#!/usr/bin/env python2 |
2754 | +# This file is part of the Juju GUI, which lets users view and manage Juju |
2755 | +# environments within a graphical interface (https://launchpad.net/juju-gui). |
2756 | +# Copyright (C) 2012-2013 Canonical Ltd. |
2757 | +# |
2758 | +# This program is free software: you can redistribute it and/or modify it under |
2759 | +# the terms of the GNU Affero General Public License version 3, as published by |
2760 | +# the Free Software Foundation. |
2761 | +# |
2762 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
2763 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
2764 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
2765 | +# Affero General Public License for more details. |
2766 | +# |
2767 | +# You should have received a copy of the GNU Affero General Public License |
2768 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2769 | + |
2770 | +"""Juju GUI utils tests.""" |
2771 | |
2772 | from contextlib import contextmanager |
2773 | +import json |
2774 | import os |
2775 | import shutil |
2776 | -from simplejson import dumps |
2777 | from subprocess import CalledProcessError |
2778 | import tempfile |
2779 | import unittest |
2780 | |
2781 | import charmhelpers |
2782 | +from shelltoolbox import environ |
2783 | import tempita |
2784 | import yaml |
2785 | |
2786 | @@ -86,28 +103,70 @@ |
2787 | |
2788 | class TestGetApiAddress(unittest.TestCase): |
2789 | |
2790 | - def setUp(self): |
2791 | - self.base_dir = tempfile.mkdtemp() |
2792 | - self.addCleanup(shutil.rmtree, self.base_dir) |
2793 | - self.unit_dir = tempfile.mkdtemp(dir=self.base_dir) |
2794 | - self.machine_dir = os.path.join(self.base_dir, 'machine-1') |
2795 | - |
2796 | - def test_retrieving_address(self): |
2797 | - # The API address is correctly returned. |
2798 | - address = 'example.com:17070' |
2799 | - os.mkdir(self.machine_dir) |
2800 | - with open(os.path.join(self.machine_dir, 'agent.conf'), 'w') as conf: |
2801 | - yaml.dump({'apiinfo': {'addrs': [address]}}, conf) |
2802 | - self.assertEqual(address, get_api_address(self.unit_dir)) |
2803 | - |
2804 | - def test_missing_file(self): |
2805 | + env_address = 'env.example.com:17070' |
2806 | + agent_address = 'agent.example.com:17070' |
2807 | + |
2808 | + @contextmanager |
2809 | + def agent_file(self, addresses=None): |
2810 | + """Set up a directory structure similar to the one created by juju. |
2811 | + |
2812 | + If addresses are provided, also create a machiner directory and an |
2813 | + agent file containing the addresses. |
2814 | + Remove the directory structure when exiting from the context manager. |
2815 | + """ |
2816 | + base_dir = tempfile.mkdtemp() |
2817 | + unit_dir = tempfile.mkdtemp(dir=base_dir) |
2818 | + machine_dir = os.path.join(base_dir, 'machine-1') |
2819 | + if addresses is not None: |
2820 | + os.mkdir(machine_dir) |
2821 | + with open(os.path.join(machine_dir, 'agent.conf'), 'w') as conf: |
2822 | + yaml.dump({'apiinfo': {'addrs': addresses}}, conf) |
2823 | + try: |
2824 | + yield unit_dir, machine_dir |
2825 | + finally: |
2826 | + shutil.rmtree(base_dir) |
2827 | + |
2828 | + def test_retrieving_address_from_env(self): |
2829 | + # The API address is correctly retrieved from the environment. |
2830 | + with environ(JUJU_API_ADDRESSES=self.env_address): |
2831 | + self.assertEqual(self.env_address, get_api_address()) |
2832 | + |
2833 | + def test_multiple_addresses_in_env(self): |
2834 | + # If multiple API addresses are listed in the environment variable, |
2835 | + # the first one is returned. |
2836 | + addresses = '{} foo.example.com:42'.format(self.env_address) |
2837 | + with environ(JUJU_API_ADDRESSES=addresses): |
2838 | + self.assertEqual(self.env_address, get_api_address()) |
2839 | + |
2840 | + def test_both_env_and_agent_file(self): |
2841 | + # If the API address is included in both the environment and the |
2842 | + # agent.conf file, the environment variable takes precedence. |
2843 | + with environ(JUJU_API_ADDRESSES=self.env_address): |
2844 | + with self.agent_file([self.agent_address]) as (unit_dir, _): |
2845 | + self.assertEqual(self.env_address, get_api_address(unit_dir)) |
2846 | + |
2847 | + def test_retrieving_address_from_agent_file(self): |
2848 | + # The API address is correctly retrieved from the machiner agent file. |
2849 | + with self.agent_file([self.agent_address]) as (unit_dir, _): |
2850 | + self.assertEqual(self.agent_address, get_api_address(unit_dir)) |
2851 | + |
2852 | + def test_multiple_addresses_in_agent_file(self): |
2853 | + # If multiple API addresses are listed in the agent file, the first |
2854 | + # one is returned. |
2855 | + addresses = [self.agent_address, 'foo.example.com:42'] |
2856 | + with self.agent_file(addresses) as (unit_dir, _): |
2857 | + self.assertEqual(self.agent_address, get_api_address(unit_dir)) |
2858 | + |
2859 | + def test_missing_env_and_agent_file(self): |
2860 | # An IOError is raised if the agent configuration file is not found. |
2861 | - os.mkdir(self.machine_dir) |
2862 | - self.assertRaises(IOError, get_api_address, self.unit_dir) |
2863 | + with self.agent_file() as (unit_dir, machine_dir): |
2864 | + os.mkdir(machine_dir) |
2865 | + self.assertRaises(IOError, get_api_address, unit_dir) |
2866 | |
2867 | - def test_missing_directory(self): |
2868 | + def test_missing_env_and_agent_directory(self): |
2869 | # An IOError is raised if the machine directory is not found. |
2870 | - self.assertRaises(IOError, get_api_address, self.unit_dir) |
2871 | + with self.agent_file() as (unit_dir, _): |
2872 | + self.assertRaises(IOError, get_api_address, unit_dir) |
2873 | |
2874 | |
2875 | class TestLegacyJuju(unittest.TestCase): |
2876 | @@ -484,7 +543,7 @@ |
2877 | fd, self.log_file_name = tempfile.mkstemp() |
2878 | os.close(fd) |
2879 | mock_config = {'command-log-file': self.log_file_name} |
2880 | - charmhelpers.command = lambda *args: lambda: dumps(mock_config) |
2881 | + charmhelpers.command = lambda *args: lambda: json.dumps(mock_config) |
2882 | |
2883 | def tearDown(self): |
2884 | charmhelpers.command = self.command |
2885 | @@ -578,7 +637,7 @@ |
2886 | start_gui( |
2887 | False, 'This is login help.', True, True, ssl_cert_path, |
2888 | charmworld_url, True, haproxy_path='haproxy', |
2889 | - config_js_path='config') |
2890 | + config_js_path='config', use_analytics=True) |
2891 | haproxy_conf = self.files['haproxy'] |
2892 | self.assertIn('ca-base {0}'.format(ssl_cert_path), haproxy_conf) |
2893 | self.assertIn('crt-base {0}'.format(ssl_cert_path), haproxy_conf) |
2894 | @@ -596,6 +655,7 @@ |
2895 | self.assertIn("socket_url: 'wss://", js_conf) |
2896 | self.assertIn('socket_protocol: "wss"', js_conf) |
2897 | self.assertIn('charmworldURL: "http://charmworld.example"', js_conf) |
2898 | + self.assertIn('useAnalytics: true', js_conf) |
2899 | apache_conf = self.files['juju-gui'] |
2900 | self.assertIn('juju-gui/build-', apache_conf) |
2901 | self.assertIn('VirtualHost *:{0}'.format(WEB_PORT), apache_conf) |
2902 | @@ -628,6 +688,38 @@ |
2903 | self.assertIn('user: "admin"', js_conf) |
2904 | self.assertIn('password: "admin"', js_conf) |
2905 | |
2906 | + def test_start_gui_no_analytics(self): |
2907 | + ssl_cert_path = '/tmp/certificates/' |
2908 | + charmworld_url = 'http://charmworld.example' |
2909 | + start_gui( |
2910 | + False, 'This is login help.', False, False, ssl_cert_path, |
2911 | + charmworld_url, True, haproxy_path='haproxy', |
2912 | + config_js_path='config', use_analytics=False) |
2913 | + js_conf = self.files['config'] |
2914 | + self.assertIn('useAnalytics: false', js_conf) |
2915 | + |
2916 | + def test_start_gui_fullscreen(self): |
2917 | + ssl_cert_path = '/tmp/certificates/' |
2918 | + charmworld_url = 'http://charmworld.example' |
2919 | + start_gui( |
2920 | + False, 'This is login help.', False, False, ssl_cert_path, |
2921 | + charmworld_url, True, haproxy_path='haproxy', |
2922 | + config_js_path='config', sandbox=True, |
2923 | + default_viewmode='fullscreen') |
2924 | + js_conf = self.files['config'] |
2925 | + self.assertIn('defaultViewmode: "fullscreen"', js_conf) |
2926 | + |
2927 | + def test_start_gui_with_button(self): |
2928 | + ssl_cert_path = '/tmp/certificates/' |
2929 | + charmworld_url = 'http://charmworld.example' |
2930 | + start_gui( |
2931 | + False, 'This is login help.', False, False, ssl_cert_path, |
2932 | + charmworld_url, True, haproxy_path='haproxy', |
2933 | + config_js_path='config', sandbox=True, |
2934 | + show_get_juju_button=True) |
2935 | + js_conf = self.files['config'] |
2936 | + self.assertIn('showGetJujuButton: true', js_conf) |
2937 | + |
2938 | |
2939 | class TestNpmCache(unittest.TestCase): |
2940 | """To speed building from a branch we prepopulate the NPM cache.""" |