Merge lp:~tvansteenburgh/charm-tools/ansible-template into lp:charm-tools/1.4
- ansible-template
- Merge into 1.4
Status: | Merged |
---|---|
Merge reported by: | Tim Van Steenburgh |
Merged at revision: | not available |
Proposed branch: | lp:~tvansteenburgh/charm-tools/ansible-template |
Merge into: | lp:charm-tools/1.4 |
Diff against target: |
1271 lines (+984/-106) 23 files modified
charmtools/charms.py (+1/-1) charmtools/generators/template.py (+8/-0) charmtools/templates/ansible/__init__.py (+18/-0) charmtools/templates/ansible/files/Makefile (+24/-0) charmtools/templates/ansible/files/README.ex (+44/-0) charmtools/templates/ansible/files/charm-helpers.yaml (+7/-0) charmtools/templates/ansible/files/config.yaml (+14/-0) charmtools/templates/ansible/files/hooks/hooks.py (+35/-0) charmtools/templates/ansible/files/icon.svg (+279/-0) charmtools/templates/ansible/files/metadata.yaml (+17/-0) charmtools/templates/ansible/files/playbooks/site.yaml (+47/-0) charmtools/templates/ansible/files/revision (+1/-0) charmtools/templates/ansible/files/scripts/charm_helpers_sync.py (+225/-0) charmtools/templates/ansible/files/unit_tests/test_hooks.py (+62/-0) charmtools/templates/ansible/template.py (+88/-0) charmtools/templates/python/config.yaml (+0/-5) charmtools/templates/python/files/hooks_symlinked/hooks.py (+0/-54) charmtools/templates/python/template.py (+0/-16) setup.py (+1/-0) tests/test_charm_proof.py (+4/-1) tests_functional/create/test.sh (+3/-0) tests_functional/create/test_ansible_create.py (+101/-0) tests_functional/create/test_python_create.py (+5/-29) |
To merge this branch: | bzr merge lp:~tvansteenburgh/charm-tools/ansible-template |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Charles Butler (community) | Approve | ||
Review via email: mp+224480@code.launchpad.net |
Commit message
Description of the change
Added ansible template for 'charm create'.
Whit Morriss (whitmo) wrote : | # |
- 336. By Marco Ceppi
-
[tvansteenburgh] Fixed symlinks in python charm by using relative paths so that symlinks are copied properly when the charm is moved to it's final filesystem location.
- 337. By Marco Ceppi
-
[tvansteenburgh] Ensure *.pyc files are skipped during template processing.
- 338. By Tim Van Steenburgh
-
Fix charm-proof email validation
formataddr() will put quotes around a name component that
contains special chars. We need to remove those to accurately
compare against the original maintainer string.Also fixes tests that were broken due to new files in
charmhelpers.core - 339. By Tim Van Steenburgh
-
Remove option to symlink hooks when creating charm
- 340. By Tim Van Steenburgh
-
More helpful comments in playbook; update tests
Tim Van Steenburgh (tvansteenburgh) wrote : | # |
Per Whit's suggestion I updated the playbook comments to explicitly enumerate the available variables. Also updated tests to pass against current charmhelpers, and rebased against charmtools head.
Charles Butler (lazypower) wrote : | # |
+1 LGTM
Lets get this in charm-tools and start iterating on the output templates.
Preview Diff
1 | === modified file 'charmtools/charms.py' |
2 | --- charmtools/charms.py 2014-06-17 15:44:04 +0000 |
3 | +++ charmtools/charms.py 2014-09-09 18:56:48 +0000 |
4 | @@ -453,7 +453,7 @@ |
5 | for maintainer in maintainers: |
6 | (name, address) = email.utils.parseaddr(maintainer) |
7 | formatted = email.utils.formataddr((name, address)) |
8 | - if formatted != maintainer: |
9 | + if formatted.replace('"', '') != maintainer: |
10 | linter.warn( |
11 | 'Maintainer format should be "Name <Email>", ' |
12 | 'not "%s"' % formatted) |
13 | |
14 | === modified file 'charmtools/generators/template.py' |
15 | --- charmtools/generators/template.py 2014-07-29 18:15:41 +0000 |
16 | +++ charmtools/generators/template.py 2014-09-09 18:56:48 +0000 |
17 | @@ -46,6 +46,14 @@ |
18 | """Return default configuration for this template, loaded from a |
19 | config.yaml file. |
20 | |
21 | + This is a sample config.yaml that configures one user prompt:: |
22 | + |
23 | + prompts: |
24 | + symlink: |
25 | + prompt: Symlink all hooks to one python source file? [yN] |
26 | + default: n |
27 | + type: boolean |
28 | + |
29 | """ |
30 | path = self.config_path() |
31 | if os.path.exists(path): |
32 | |
33 | === added directory 'charmtools/templates/ansible' |
34 | === added file 'charmtools/templates/ansible/__init__.py' |
35 | --- charmtools/templates/ansible/__init__.py 1970-01-01 00:00:00 +0000 |
36 | +++ charmtools/templates/ansible/__init__.py 2014-09-09 18:56:48 +0000 |
37 | @@ -0,0 +1,18 @@ |
38 | +#!/usr/bin/python |
39 | +# |
40 | +# Copyright (C) 2014 Canonical Ltd. |
41 | +# |
42 | +# This program is free software: you can redistribute it and/or modify |
43 | +# it under the terms of the GNU General Public License as published by |
44 | +# the Free Software Foundation, either version 3 of the License, or |
45 | +# (at your option) any later version. |
46 | +# |
47 | +# This program is distributed in the hope that it will be useful, |
48 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
49 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
50 | +# GNU General Public License for more details. |
51 | +# |
52 | +# You should have received a copy of the GNU General Public License |
53 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
54 | + |
55 | +from .template import AnsibleCharmTemplate # noqa |
56 | |
57 | === added directory 'charmtools/templates/ansible/files' |
58 | === added file 'charmtools/templates/ansible/files/Makefile' |
59 | --- charmtools/templates/ansible/files/Makefile 1970-01-01 00:00:00 +0000 |
60 | +++ charmtools/templates/ansible/files/Makefile 2014-09-09 18:56:48 +0000 |
61 | @@ -0,0 +1,24 @@ |
62 | +#!/usr/bin/make |
63 | + |
64 | +build: virtualenv lint test |
65 | + |
66 | +virtualenv: .venv/bin/python |
67 | +.venv/bin/python: |
68 | + sudo apt-get install python-virtualenv |
69 | + virtualenv .venv |
70 | + .venv/bin/pip install nose flake8 mock pyyaml |
71 | + |
72 | +lint: |
73 | + @.venv/bin/flake8 hooks unit_tests |
74 | + @charm proof |
75 | + |
76 | +test: |
77 | + @echo Starting tests... |
78 | + @CHARM_DIR=. PYTHONPATH=./hooks .venv/bin/nosetests --nologcapture unit_tests |
79 | + |
80 | +sync-charm-helpers: |
81 | + @.venv/bin/python scripts/charm_helpers_sync.py -c charm-helpers.yaml |
82 | + |
83 | +clean: |
84 | + rm -rf .venv |
85 | + find -name *.pyc -delete |
86 | |
87 | === added file 'charmtools/templates/ansible/files/README.ex' |
88 | --- charmtools/templates/ansible/files/README.ex 1970-01-01 00:00:00 +0000 |
89 | +++ charmtools/templates/ansible/files/README.ex 2014-09-09 18:56:48 +0000 |
90 | @@ -0,0 +1,44 @@ |
91 | +# Overview |
92 | + |
93 | +Describe the intended usage of this charm and anything unique about how this charm relates to others here. |
94 | + |
95 | +This README will be displayed in the Charm Store, it should be either Markdown or RST. Ideal READMEs include instructions on how to use the charm, expected usage, and charm features that your audience might be interested in. For an example of a well written README check out Hadoop: http://jujucharms.com/charms/precise/hadoop |
96 | + |
97 | +Use this as a Markdown reference if you need help with the formatting of this README: http://askubuntu.com/editing-help |
98 | + |
99 | +This charm provides [service](http://example.com). Add a description here of what the service itself actually does. |
100 | + |
101 | +Also remember to check the [icon guidelines](https://juju.ubuntu.com/docs/authors-charm-icon.html) so that your charm looks good in the Juju GUI. |
102 | + |
103 | +# Usage |
104 | + |
105 | +Step by step instructions on using the charm: |
106 | + |
107 | + juju deploy servicename |
108 | + |
109 | +and so on. If you're providing a web service or something that the end user needs to go to, tell them here, especially if you're deploying a service that might listen to a non-default port. |
110 | + |
111 | +You can then browse to http://ip-address to configure the service. |
112 | + |
113 | +## Scale out Usage |
114 | + |
115 | +If the charm has any recommendations for running at scale, outline them in examples here. For example if you have a memcached relation that improves performance, mention it here. |
116 | + |
117 | +## Known Limitations and Issues |
118 | + |
119 | +This not only helps users but gives people a place to start if they want to help you add features to your charm. |
120 | + |
121 | +# Configuration |
122 | + |
123 | +The configuration options will be listed on the charm store, however If you're making assumptions or opinionated decisions in the charm (like setting a default administrator password), you should detail that here so the user knows how to change it immediately, etc. |
124 | + |
125 | +# Contact Information |
126 | + |
127 | +Though this will be listed in the charm store itself don't assume a user will know that, so include that information here: |
128 | + |
129 | +## Upstream Project Name |
130 | + |
131 | +- Upstream website |
132 | +- Upstream bug tracker |
133 | +- Upstream mailing list or contact information |
134 | +- Feel free to add things if it's useful for users |
135 | |
136 | === added file 'charmtools/templates/ansible/files/charm-helpers.yaml' |
137 | --- charmtools/templates/ansible/files/charm-helpers.yaml 1970-01-01 00:00:00 +0000 |
138 | +++ charmtools/templates/ansible/files/charm-helpers.yaml 2014-09-09 18:56:48 +0000 |
139 | @@ -0,0 +1,7 @@ |
140 | +destination: lib/charmhelpers |
141 | +branch: lp:charm-helpers |
142 | +include: |
143 | + - core |
144 | + - fetch |
145 | + - contrib.ansible|inc=* |
146 | + - contrib.templating.contexts |
147 | |
148 | === added file 'charmtools/templates/ansible/files/config.yaml' |
149 | --- charmtools/templates/ansible/files/config.yaml 1970-01-01 00:00:00 +0000 |
150 | +++ charmtools/templates/ansible/files/config.yaml 2014-09-09 18:56:48 +0000 |
151 | @@ -0,0 +1,14 @@ |
152 | +options: |
153 | + string-option: |
154 | + type: string |
155 | + default: "Default Value" |
156 | + description: "A short description of the configuration option" |
157 | + boolean-option: |
158 | + type: boolean |
159 | + default: False |
160 | + description: "A short description of the configuration option" |
161 | + int-option: |
162 | + type: int |
163 | + default: 9001 |
164 | + description: "A short description of the configuration option" |
165 | + |
166 | |
167 | === added directory 'charmtools/templates/ansible/files/hooks' |
168 | === added file 'charmtools/templates/ansible/files/hooks/hooks.py' |
169 | --- charmtools/templates/ansible/files/hooks/hooks.py 1970-01-01 00:00:00 +0000 |
170 | +++ charmtools/templates/ansible/files/hooks/hooks.py 2014-09-09 18:56:48 +0000 |
171 | @@ -0,0 +1,35 @@ |
172 | +#!/usr/bin/env python |
173 | + |
174 | +import os |
175 | +import sys |
176 | + |
177 | +sys.path.insert(0, os.path.join(os.environ['CHARM_DIR'], 'lib')) |
178 | + |
179 | +import charmhelpers.contrib.ansible |
180 | + |
181 | +# Create the hooks helper, passing a list of hooks which will be |
182 | +# handled by default by running all sections of the playbook |
183 | +# tagged with the hook name. |
184 | +hooks = charmhelpers.contrib.ansible.AnsibleHooks( |
185 | + playbook_path='playbooks/site.yaml', |
186 | + default_hooks=[ |
187 | + 'start', |
188 | + 'stop', |
189 | + 'config-changed', |
190 | + 'upgrade-charm', |
191 | + ]) |
192 | + |
193 | + |
194 | +@hooks.hook('install', 'upgrade-charm') |
195 | +def install(): |
196 | + """Install ansible. |
197 | + |
198 | + The hook() helper decorating this install function ensures that after this |
199 | + function finishes, any tasks in the playbook tagged with install or |
200 | + upgrade-charm are executed. |
201 | + """ |
202 | + charmhelpers.contrib.ansible.install_ansible_support(from_ppa=True) |
203 | + |
204 | + |
205 | +if __name__ == "__main__": |
206 | + hooks.execute(sys.argv) |
207 | |
208 | === added file 'charmtools/templates/ansible/files/icon.svg' |
209 | --- charmtools/templates/ansible/files/icon.svg 1970-01-01 00:00:00 +0000 |
210 | +++ charmtools/templates/ansible/files/icon.svg 2014-09-09 18:56:48 +0000 |
211 | @@ -0,0 +1,279 @@ |
212 | +<?xml version="1.0" encoding="UTF-8" standalone="no"?> |
213 | +<!-- Created with Inkscape (http://www.inkscape.org/) --> |
214 | + |
215 | +<svg |
216 | + xmlns:dc="http://purl.org/dc/elements/1.1/" |
217 | + xmlns:cc="http://creativecommons.org/ns#" |
218 | + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" |
219 | + xmlns:svg="http://www.w3.org/2000/svg" |
220 | + xmlns="http://www.w3.org/2000/svg" |
221 | + xmlns:xlink="http://www.w3.org/1999/xlink" |
222 | + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" |
223 | + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" |
224 | + width="96" |
225 | + height="96" |
226 | + id="svg6517" |
227 | + version="1.1" |
228 | + inkscape:version="0.48+devel r12274" |
229 | + sodipodi:docname="Juju_charm_icon_template.svg"> |
230 | + <defs |
231 | + id="defs6519"> |
232 | + <linearGradient |
233 | + inkscape:collect="always" |
234 | + xlink:href="#Background" |
235 | + id="linearGradient6461" |
236 | + gradientUnits="userSpaceOnUse" |
237 | + x1="0" |
238 | + y1="970.29498" |
239 | + x2="144" |
240 | + y2="970.29498" |
241 | + gradientTransform="matrix(0,-0.66666669,0.6660448,0,-866.25992,731.29077)" /> |
242 | + <linearGradient |
243 | + id="Background"> |
244 | + <stop |
245 | + id="stop4178" |
246 | + offset="0" |
247 | + style="stop-color:#b8b8b8;stop-opacity:1" /> |
248 | + <stop |
249 | + id="stop4180" |
250 | + offset="1" |
251 | + style="stop-color:#c9c9c9;stop-opacity:1" /> |
252 | + </linearGradient> |
253 | + <filter |
254 | + style="color-interpolation-filters:sRGB;" |
255 | + inkscape:label="Inner Shadow" |
256 | + id="filter1121"> |
257 | + <feFlood |
258 | + flood-opacity="0.59999999999999998" |
259 | + flood-color="rgb(0,0,0)" |
260 | + result="flood" |
261 | + id="feFlood1123" /> |
262 | + <feComposite |
263 | + in="flood" |
264 | + in2="SourceGraphic" |
265 | + operator="out" |
266 | + result="composite1" |
267 | + id="feComposite1125" /> |
268 | + <feGaussianBlur |
269 | + in="composite1" |
270 | + stdDeviation="1" |
271 | + result="blur" |
272 | + id="feGaussianBlur1127" /> |
273 | + <feOffset |
274 | + dx="0" |
275 | + dy="2" |
276 | + result="offset" |
277 | + id="feOffset1129" /> |
278 | + <feComposite |
279 | + in="offset" |
280 | + in2="SourceGraphic" |
281 | + operator="atop" |
282 | + result="composite2" |
283 | + id="feComposite1131" /> |
284 | + </filter> |
285 | + <filter |
286 | + style="color-interpolation-filters:sRGB;" |
287 | + inkscape:label="Drop Shadow" |
288 | + id="filter950"> |
289 | + <feFlood |
290 | + flood-opacity="0.25" |
291 | + flood-color="rgb(0,0,0)" |
292 | + result="flood" |
293 | + id="feFlood952" /> |
294 | + <feComposite |
295 | + in="flood" |
296 | + in2="SourceGraphic" |
297 | + operator="in" |
298 | + result="composite1" |
299 | + id="feComposite954" /> |
300 | + <feGaussianBlur |
301 | + in="composite1" |
302 | + stdDeviation="1" |
303 | + result="blur" |
304 | + id="feGaussianBlur956" /> |
305 | + <feOffset |
306 | + dx="0" |
307 | + dy="1" |
308 | + result="offset" |
309 | + id="feOffset958" /> |
310 | + <feComposite |
311 | + in="SourceGraphic" |
312 | + in2="offset" |
313 | + operator="over" |
314 | + result="composite2" |
315 | + id="feComposite960" /> |
316 | + </filter> |
317 | + <clipPath |
318 | + clipPathUnits="userSpaceOnUse" |
319 | + id="clipPath873"> |
320 | + <g |
321 | + transform="matrix(0,-0.66666667,0.66604479,0,-258.25992,677.00001)" |
322 | + id="g875" |
323 | + inkscape:label="Layer 1" |
324 | + style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline"> |
325 | + <path |
326 | + style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline" |
327 | + d="m 46.702703,898.22775 50.594594,0 C 138.16216,898.22775 144,904.06497 144,944.92583 l 0,50.73846 c 0,40.86071 -5.83784,46.69791 -46.702703,46.69791 l -50.594594,0 C 5.8378378,1042.3622 0,1036.525 0,995.66429 L 0,944.92583 C 0,904.06497 5.8378378,898.22775 46.702703,898.22775 Z" |
328 | + id="path877" |
329 | + inkscape:connector-curvature="0" |
330 | + sodipodi:nodetypes="sssssssss" /> |
331 | + </g> |
332 | + </clipPath> |
333 | + <filter |
334 | + inkscape:collect="always" |
335 | + id="filter891" |
336 | + inkscape:label="Badge Shadow"> |
337 | + <feGaussianBlur |
338 | + inkscape:collect="always" |
339 | + stdDeviation="0.71999962" |
340 | + id="feGaussianBlur893" /> |
341 | + </filter> |
342 | + </defs> |
343 | + <sodipodi:namedview |
344 | + id="base" |
345 | + pagecolor="#ffffff" |
346 | + bordercolor="#666666" |
347 | + borderopacity="1.0" |
348 | + inkscape:pageopacity="0.0" |
349 | + inkscape:pageshadow="2" |
350 | + inkscape:zoom="4.0745362" |
351 | + inkscape:cx="18.514671" |
352 | + inkscape:cy="49.018169" |
353 | + inkscape:document-units="px" |
354 | + inkscape:current-layer="layer1" |
355 | + showgrid="true" |
356 | + fit-margin-top="0" |
357 | + fit-margin-left="0" |
358 | + fit-margin-right="0" |
359 | + fit-margin-bottom="0" |
360 | + inkscape:window-width="1920" |
361 | + inkscape:window-height="1029" |
362 | + inkscape:window-x="0" |
363 | + inkscape:window-y="24" |
364 | + inkscape:window-maximized="1" |
365 | + showborder="true" |
366 | + showguides="true" |
367 | + inkscape:guide-bbox="true" |
368 | + inkscape:showpageshadow="false"> |
369 | + <inkscape:grid |
370 | + type="xygrid" |
371 | + id="grid821" /> |
372 | + <sodipodi:guide |
373 | + orientation="1,0" |
374 | + position="16,48" |
375 | + id="guide823" /> |
376 | + <sodipodi:guide |
377 | + orientation="0,1" |
378 | + position="64,80" |
379 | + id="guide825" /> |
380 | + <sodipodi:guide |
381 | + orientation="1,0" |
382 | + position="80,40" |
383 | + id="guide827" /> |
384 | + <sodipodi:guide |
385 | + orientation="0,1" |
386 | + position="64,16" |
387 | + id="guide829" /> |
388 | + </sodipodi:namedview> |
389 | + <metadata |
390 | + id="metadata6522"> |
391 | + <rdf:RDF> |
392 | + <cc:Work |
393 | + rdf:about=""> |
394 | + <dc:format>image/svg+xml</dc:format> |
395 | + <dc:type |
396 | + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> |
397 | + <dc:title></dc:title> |
398 | + </cc:Work> |
399 | + </rdf:RDF> |
400 | + </metadata> |
401 | + <g |
402 | + inkscape:label="BACKGROUND" |
403 | + inkscape:groupmode="layer" |
404 | + id="layer1" |
405 | + transform="translate(268,-635.29076)" |
406 | + style="display:inline"> |
407 | + <path |
408 | + style="fill:url(#linearGradient6461);fill-opacity:1;stroke:none;display:inline;filter:url(#filter1121)" |
409 | + d="m -268,700.15563 0,-33.72973 c 0,-27.24324 3.88785,-31.13513 31.10302,-31.13513 l 33.79408,0 c 27.21507,0 31.1029,3.89189 31.1029,31.13513 l 0,33.72973 c 0,27.24325 -3.88783,31.13514 -31.1029,31.13514 l -33.79408,0 C -264.11215,731.29077 -268,727.39888 -268,700.15563 Z" |
410 | + id="path6455" |
411 | + inkscape:connector-curvature="0" |
412 | + sodipodi:nodetypes="sssssssss" /> |
413 | + </g> |
414 | + <g |
415 | + inkscape:groupmode="layer" |
416 | + id="layer3" |
417 | + inkscape:label="PLACE YOUR PICTOGRAM HERE" |
418 | + style="display:inline" /> |
419 | + <g |
420 | + inkscape:groupmode="layer" |
421 | + id="layer2" |
422 | + inkscape:label="BADGE" |
423 | + style="display:none" |
424 | + sodipodi:insensitive="true"> |
425 | + <g |
426 | + style="display:inline" |
427 | + transform="translate(-340.00001,-581)" |
428 | + id="g4394" |
429 | + clip-path="none"> |
430 | + <g |
431 | + id="g855"> |
432 | + <g |
433 | + inkscape:groupmode="maskhelper" |
434 | + id="g870" |
435 | + clip-path="url(#clipPath873)" |
436 | + style="opacity:0.6;filter:url(#filter891)"> |
437 | + <path |
438 | + transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-237.54282)" |
439 | + d="m 264,552.36218 a 12,12 0 1 1 -24,0 A 12,12 0 1 1 264,552.36218 Z" |
440 | + sodipodi:ry="12" |
441 | + sodipodi:rx="12" |
442 | + sodipodi:cy="552.36218" |
443 | + sodipodi:cx="252" |
444 | + id="path844" |
445 | + style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" |
446 | + sodipodi:type="arc" /> |
447 | + </g> |
448 | + <g |
449 | + id="g862"> |
450 | + <path |
451 | + sodipodi:type="arc" |
452 | + style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" |
453 | + id="path4398" |
454 | + sodipodi:cx="252" |
455 | + sodipodi:cy="552.36218" |
456 | + sodipodi:rx="12" |
457 | + sodipodi:ry="12" |
458 | + d="m 264,552.36218 a 12,12 0 1 1 -24,0 A 12,12 0 1 1 264,552.36218 Z" |
459 | + transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-238.54282)" /> |
460 | + <path |
461 | + transform="matrix(1.25,0,0,1.25,33,-100.45273)" |
462 | + d="m 264,552.36218 a 12,12 0 1 1 -24,0 A 12,12 0 1 1 264,552.36218 Z" |
463 | + sodipodi:ry="12" |
464 | + sodipodi:rx="12" |
465 | + sodipodi:cy="552.36218" |
466 | + sodipodi:cx="252" |
467 | + id="path4400" |
468 | + style="color:#000000;fill:#dd4814;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" |
469 | + sodipodi:type="arc" /> |
470 | + <path |
471 | + sodipodi:type="star" |
472 | + style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" |
473 | + id="path4459" |
474 | + sodipodi:sides="5" |
475 | + sodipodi:cx="666.19574" |
476 | + sodipodi:cy="589.50385" |
477 | + sodipodi:r1="7.2431178" |
478 | + sodipodi:r2="4.3458705" |
479 | + sodipodi:arg1="1.0471976" |
480 | + sodipodi:arg2="1.6755161" |
481 | + inkscape:flatsided="false" |
482 | + inkscape:rounded="0.1" |
483 | + inkscape:randomized="0" |
484 | + d="m 669.8173,595.77657 c -0.39132,0.22593 -3.62645,-1.90343 -4.07583,-1.95066 -0.44938,-0.0472 -4.05653,1.36297 -4.39232,1.06062 -0.3358,-0.30235 0.68963,-4.03715 0.59569,-4.47913 -0.0939,-0.44198 -2.5498,-3.43681 -2.36602,-3.8496 0.18379,-0.41279 4.05267,-0.59166 4.44398,-0.81759 0.39132,-0.22593 2.48067,-3.48704 2.93005,-3.4398 0.44938,0.0472 1.81505,3.67147 2.15084,3.97382 0.3358,0.30236 4.08294,1.2817 4.17689,1.72369 0.0939,0.44198 -2.9309,2.86076 -3.11469,3.27355 C 669.9821,591.68426 670.20862,595.55064 669.8173,595.77657 Z" |
485 | + transform="matrix(1.511423,-0.16366377,0.16366377,1.511423,-755.37346,-191.93651)" /> |
486 | + </g> |
487 | + </g> |
488 | + </g> |
489 | + </g> |
490 | +</svg> |
491 | |
492 | === added file 'charmtools/templates/ansible/files/metadata.yaml' |
493 | --- charmtools/templates/ansible/files/metadata.yaml 1970-01-01 00:00:00 +0000 |
494 | +++ charmtools/templates/ansible/files/metadata.yaml 2014-09-09 18:56:48 +0000 |
495 | @@ -0,0 +1,17 @@ |
496 | +name: $metadata.package |
497 | +summary: $metadata.summary |
498 | +maintainer: $metadata.maintainer |
499 | +description: | |
500 | + $metadata.description |
501 | +categories: |
502 | + - misc |
503 | +subordinate: false |
504 | +provides: |
505 | + provides-relation: |
506 | + interface: interface-name |
507 | +requires: |
508 | + requires-relation: |
509 | + interface: interface-name |
510 | +peers: |
511 | + peer-relation: |
512 | + interface: interface-name |
513 | |
514 | === added directory 'charmtools/templates/ansible/files/playbooks' |
515 | === added file 'charmtools/templates/ansible/files/playbooks/site.yaml' |
516 | --- charmtools/templates/ansible/files/playbooks/site.yaml 1970-01-01 00:00:00 +0000 |
517 | +++ charmtools/templates/ansible/files/playbooks/site.yaml 2014-09-09 18:56:48 +0000 |
518 | @@ -0,0 +1,47 @@ |
519 | +# The tasks here are left as examples and should be removed/replaced by the |
520 | +# charm author. Some things to note: |
521 | +# |
522 | +# 1. All charm config values are available as template variables |
523 | +# e.g. repo -> {{repo}}, app-name -> {{app_name}} (note underscore) |
524 | +# |
525 | +# 2. Along with charm config values, the following variables are also |
526 | +# made available as template vars: |
527 | +# |
528 | +# charm_dir |
529 | +# local_unit |
530 | +# unit_private_address |
531 | +# unit_public_address |
532 | +# |
533 | +# 3. Use tags to control when each task is executed. The tags list should |
534 | +# contain one or more hook names. |
535 | + |
536 | +- hosts: all |
537 | + tasks: |
538 | + |
539 | + - name: Install required packages. |
540 | + apt: pkg={{ item }} state=latest update_cache=yes |
541 | + with_items: |
542 | + - python-django |
543 | + - python-django-celery |
544 | + tags: |
545 | + - install |
546 | + - upgrade-charm |
547 | + |
548 | + - name: Put app code in place. |
549 | + git: repo=git://github.com/absoludity/charm-bootstrap-ansible.git |
550 | + dest=/srv/myapp |
551 | + version=HEAD |
552 | + tags: |
553 | + - install |
554 | + - config-changed |
555 | + |
556 | + - name: Start service |
557 | + debug: msg="You'd start some service here. The config 'string-option' has the value '{{ string_option }}'" |
558 | + tags: |
559 | + - start |
560 | + - config-changed |
561 | + |
562 | + - name: Stop service |
563 | + debug: msg="You'd stop some service here. The config 'string-option' is '{{ string_option }}'" |
564 | + tags: |
565 | + - stop |
566 | |
567 | === added file 'charmtools/templates/ansible/files/revision' |
568 | --- charmtools/templates/ansible/files/revision 1970-01-01 00:00:00 +0000 |
569 | +++ charmtools/templates/ansible/files/revision 2014-09-09 18:56:48 +0000 |
570 | @@ -0,0 +1,1 @@ |
571 | +1 |
572 | |
573 | === added directory 'charmtools/templates/ansible/files/scripts' |
574 | === added file 'charmtools/templates/ansible/files/scripts/charm_helpers_sync.py' |
575 | --- charmtools/templates/ansible/files/scripts/charm_helpers_sync.py 1970-01-01 00:00:00 +0000 |
576 | +++ charmtools/templates/ansible/files/scripts/charm_helpers_sync.py 2014-09-09 18:56:48 +0000 |
577 | @@ -0,0 +1,225 @@ |
578 | +#!/usr/bin/python |
579 | +# |
580 | +# Copyright 2013 Canonical Ltd. |
581 | + |
582 | +# Authors: |
583 | +# Adam Gandelman <adamg@ubuntu.com> |
584 | +# |
585 | + |
586 | +import logging |
587 | +import optparse |
588 | +import os |
589 | +import subprocess |
590 | +import shutil |
591 | +import sys |
592 | +import tempfile |
593 | +import yaml |
594 | + |
595 | +from fnmatch import fnmatch |
596 | + |
597 | +CHARM_HELPERS_BRANCH = 'lp:charm-helpers' |
598 | + |
599 | + |
600 | +def parse_config(conf_file): |
601 | + if not os.path.isfile(conf_file): |
602 | + logging.error('Invalid config file: %s.' % conf_file) |
603 | + return False |
604 | + return yaml.load(open(conf_file).read()) |
605 | + |
606 | + |
607 | +def clone_helpers(work_dir, branch): |
608 | + dest = os.path.join(work_dir, 'charm-helpers') |
609 | + logging.info('Checking out %s to %s.' % (branch, dest)) |
610 | + cmd = ['bzr', 'branch', branch, dest] |
611 | + subprocess.check_call(cmd) |
612 | + return dest |
613 | + |
614 | + |
615 | +def _module_path(module): |
616 | + return os.path.join(*module.split('.')) |
617 | + |
618 | + |
619 | +def _src_path(src, module): |
620 | + return os.path.join(src, 'charmhelpers', _module_path(module)) |
621 | + |
622 | + |
623 | +def _dest_path(dest, module): |
624 | + return os.path.join(dest, _module_path(module)) |
625 | + |
626 | + |
627 | +def _is_pyfile(path): |
628 | + return os.path.isfile(path + '.py') |
629 | + |
630 | + |
631 | +def ensure_init(path): |
632 | + ''' |
633 | + ensure directories leading up to path are importable, omitting |
634 | + parent directory, eg path='/hooks/helpers/foo'/: |
635 | + hooks/ |
636 | + hooks/helpers/__init__.py |
637 | + hooks/helpers/foo/__init__.py |
638 | + ''' |
639 | + for d, dirs, files in os.walk(os.path.join(*path.split('/')[:2])): |
640 | + _i = os.path.join(d, '__init__.py') |
641 | + if not os.path.exists(_i): |
642 | + logging.info('Adding missing __init__.py: %s' % _i) |
643 | + open(_i, 'wb').close() |
644 | + |
645 | + |
646 | +def sync_pyfile(src, dest): |
647 | + src = src + '.py' |
648 | + src_dir = os.path.dirname(src) |
649 | + logging.info('Syncing pyfile: %s -> %s.' % (src, dest)) |
650 | + if not os.path.exists(dest): |
651 | + os.makedirs(dest) |
652 | + shutil.copy(src, dest) |
653 | + if os.path.isfile(os.path.join(src_dir, '__init__.py')): |
654 | + shutil.copy(os.path.join(src_dir, '__init__.py'), |
655 | + dest) |
656 | + ensure_init(dest) |
657 | + |
658 | + |
659 | +def get_filter(opts=None): |
660 | + opts = opts or [] |
661 | + if 'inc=*' in opts: |
662 | + # do not filter any files, include everything |
663 | + return None |
664 | + |
665 | + def _filter(dir, ls): |
666 | + incs = [opt.split('=').pop() for opt in opts if 'inc=' in opt] |
667 | + _filter = [] |
668 | + for f in ls: |
669 | + _f = os.path.join(dir, f) |
670 | + |
671 | + if not os.path.isdir(_f) and not _f.endswith('.py') and incs: |
672 | + if True not in [fnmatch(_f, inc) for inc in incs]: |
673 | + logging.debug('Not syncing %s, does not match include ' |
674 | + 'filters (%s)' % (_f, incs)) |
675 | + _filter.append(f) |
676 | + else: |
677 | + logging.debug('Including file, which matches include ' |
678 | + 'filters (%s): %s' % (incs, _f)) |
679 | + elif (os.path.isfile(_f) and not _f.endswith('.py')): |
680 | + logging.debug('Not syncing file: %s' % f) |
681 | + _filter.append(f) |
682 | + elif (os.path.isdir(_f) and not |
683 | + os.path.isfile(os.path.join(_f, '__init__.py'))): |
684 | + logging.debug('Not syncing directory: %s' % f) |
685 | + _filter.append(f) |
686 | + return _filter |
687 | + return _filter |
688 | + |
689 | + |
690 | +def sync_directory(src, dest, opts=None): |
691 | + if os.path.exists(dest): |
692 | + logging.debug('Removing existing directory: %s' % dest) |
693 | + shutil.rmtree(dest) |
694 | + logging.info('Syncing directory: %s -> %s.' % (src, dest)) |
695 | + |
696 | + shutil.copytree(src, dest, ignore=get_filter(opts)) |
697 | + ensure_init(dest) |
698 | + |
699 | + |
700 | +def sync(src, dest, module, opts=None): |
701 | + if os.path.isdir(_src_path(src, module)): |
702 | + sync_directory(_src_path(src, module), _dest_path(dest, module), opts) |
703 | + elif _is_pyfile(_src_path(src, module)): |
704 | + sync_pyfile(_src_path(src, module), |
705 | + os.path.dirname(_dest_path(dest, module))) |
706 | + else: |
707 | + logging.warn('Could not sync: %s. Neither a pyfile or directory, ' |
708 | + 'does it even exist?' % module) |
709 | + |
710 | + |
711 | +def parse_sync_options(options): |
712 | + if not options: |
713 | + return [] |
714 | + return options.split(',') |
715 | + |
716 | + |
717 | +def extract_options(inc, global_options=None): |
718 | + global_options = global_options or [] |
719 | + if global_options and isinstance(global_options, basestring): |
720 | + global_options = [global_options] |
721 | + if '|' not in inc: |
722 | + return (inc, global_options) |
723 | + inc, opts = inc.split('|') |
724 | + return (inc, parse_sync_options(opts) + global_options) |
725 | + |
726 | + |
727 | +def sync_helpers(include, src, dest, options=None): |
728 | + if not os.path.isdir(dest): |
729 | + os.mkdir(dest) |
730 | + |
731 | + global_options = parse_sync_options(options) |
732 | + |
733 | + for inc in include: |
734 | + if isinstance(inc, str): |
735 | + inc, opts = extract_options(inc, global_options) |
736 | + sync(src, dest, inc, opts) |
737 | + elif isinstance(inc, dict): |
738 | + # could also do nested dicts here. |
739 | + for k, v in inc.iteritems(): |
740 | + if isinstance(v, list): |
741 | + for m in v: |
742 | + inc, opts = extract_options(m, global_options) |
743 | + sync(src, dest, '%s.%s' % (k, inc), opts) |
744 | + |
745 | +if __name__ == '__main__': |
746 | + parser = optparse.OptionParser() |
747 | + parser.add_option('-c', '--config', action='store', dest='config', |
748 | + default=None, help='helper config file') |
749 | + parser.add_option('-D', '--debug', action='store_true', dest='debug', |
750 | + default=False, help='debug') |
751 | + parser.add_option('-b', '--branch', action='store', dest='branch', |
752 | + help='charm-helpers bzr branch (overrides config)') |
753 | + parser.add_option('-d', '--destination', action='store', dest='dest_dir', |
754 | + help='sync destination dir (overrides config)') |
755 | + (opts, args) = parser.parse_args() |
756 | + |
757 | + if opts.debug: |
758 | + logging.basicConfig(level=logging.DEBUG) |
759 | + else: |
760 | + logging.basicConfig(level=logging.INFO) |
761 | + |
762 | + if opts.config: |
763 | + logging.info('Loading charm helper config from %s.' % opts.config) |
764 | + config = parse_config(opts.config) |
765 | + if not config: |
766 | + logging.error('Could not parse config from %s.' % opts.config) |
767 | + sys.exit(1) |
768 | + else: |
769 | + config = {} |
770 | + |
771 | + if 'branch' not in config: |
772 | + config['branch'] = CHARM_HELPERS_BRANCH |
773 | + if opts.branch: |
774 | + config['branch'] = opts.branch |
775 | + if opts.dest_dir: |
776 | + config['destination'] = opts.dest_dir |
777 | + |
778 | + if 'destination' not in config: |
779 | + logging.error('No destination dir. specified as option or config.') |
780 | + sys.exit(1) |
781 | + |
782 | + if 'include' not in config: |
783 | + if not args: |
784 | + logging.error('No modules to sync specified as option or config.') |
785 | + sys.exit(1) |
786 | + config['include'] = [] |
787 | + [config['include'].append(a) for a in args] |
788 | + |
789 | + sync_options = None |
790 | + if 'options' in config: |
791 | + sync_options = config['options'] |
792 | + tmpd = tempfile.mkdtemp() |
793 | + try: |
794 | + checkout = clone_helpers(tmpd, config['branch']) |
795 | + sync_helpers(config['include'], checkout, config['destination'], |
796 | + options=sync_options) |
797 | + except Exception, e: |
798 | + logging.error("Could not sync: %s" % e) |
799 | + raise e |
800 | + finally: |
801 | + logging.debug('Cleaning up %s' % tmpd) |
802 | + shutil.rmtree(tmpd) |
803 | |
804 | === added directory 'charmtools/templates/ansible/files/unit_tests' |
805 | === added file 'charmtools/templates/ansible/files/unit_tests/test_hooks.py' |
806 | --- charmtools/templates/ansible/files/unit_tests/test_hooks.py 1970-01-01 00:00:00 +0000 |
807 | +++ charmtools/templates/ansible/files/unit_tests/test_hooks.py 2014-09-09 18:56:48 +0000 |
808 | @@ -0,0 +1,62 @@ |
809 | +import unittest |
810 | + |
811 | +try: |
812 | + import mock |
813 | +except ImportError: |
814 | + raise ImportError( |
815 | + "Please ensure both python-mock and python-nose are installed.") |
816 | + |
817 | + |
818 | +from hooks import hooks |
819 | + |
820 | + |
821 | +class InstallHookTestCase(unittest.TestCase): |
822 | + |
823 | + def setUp(self): |
824 | + super(InstallHookTestCase, self).setUp() |
825 | + |
826 | + patcher = mock.patch('hooks.charmhelpers') |
827 | + self.mock_charmhelpers = patcher.start() |
828 | + self.addCleanup(patcher.stop) |
829 | + self.mock_charmhelpers.core.hookenv.config.return_value = { |
830 | + 'install_deps_from_ppa': False, |
831 | + } |
832 | + |
833 | + patcher = mock.patch('charmhelpers.contrib.ansible.apply_playbook') |
834 | + self.mock_apply_playbook = patcher.start() |
835 | + self.addCleanup(patcher.stop) |
836 | + |
837 | + def test_installs_ansible_support(self): |
838 | + hooks.execute(['install']) |
839 | + |
840 | + ansible = self.mock_charmhelpers.contrib.ansible |
841 | + ansible.install_ansible_support.assert_called_once_with( |
842 | + from_ppa=True) |
843 | + |
844 | + def test_applies_install_playbook(self): |
845 | + hooks.execute(['install']) |
846 | + |
847 | + self.assertEqual([ |
848 | + mock.call('playbooks/site.yaml', tags=['install']), |
849 | + ], self.mock_apply_playbook.call_args_list) |
850 | + |
851 | + |
852 | +class DefaultHooksTestCase(unittest.TestCase): |
853 | + |
854 | + def setUp(self): |
855 | + super(DefaultHooksTestCase, self).setUp() |
856 | + patcher = mock.patch('charmhelpers.contrib.ansible.apply_playbook') |
857 | + self.mock_apply_playbook = patcher.start() |
858 | + self.addCleanup(patcher.stop) |
859 | + |
860 | + def test_default_hooks(self): |
861 | + """Most of the hooks let ansible do all the work.""" |
862 | + for hook in ('start', 'stop', 'config-changed'): |
863 | + self.mock_apply_playbook.reset_mock() |
864 | + |
865 | + hooks.execute([hook]) |
866 | + |
867 | + self.assertEqual([ |
868 | + mock.call('playbooks/site.yaml', |
869 | + tags=[hook]), |
870 | + ], self.mock_apply_playbook.call_args_list) |
871 | |
872 | === added file 'charmtools/templates/ansible/template.py' |
873 | --- charmtools/templates/ansible/template.py 1970-01-01 00:00:00 +0000 |
874 | +++ charmtools/templates/ansible/template.py 2014-09-09 18:56:48 +0000 |
875 | @@ -0,0 +1,88 @@ |
876 | +#!/usr/bin/python |
877 | +# |
878 | +# Copyright (C) 2014 Canonical Ltd. |
879 | +# |
880 | +# This program is free software: you can redistribute it and/or modify |
881 | +# it under the terms of the GNU General Public License as published by |
882 | +# the Free Software Foundation, either version 3 of the License, or |
883 | +# (at your option) any later version. |
884 | +# |
885 | +# This program is distributed in the hope that it will be useful, |
886 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
887 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
888 | +# GNU General Public License for more details. |
889 | +# |
890 | +# You should have received a copy of the GNU General Public License |
891 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
892 | + |
893 | +import logging |
894 | +import os |
895 | +import os.path as path |
896 | +import time |
897 | +import shutil |
898 | +import subprocess |
899 | +import tempfile |
900 | + |
901 | +from Cheetah.Template import Template |
902 | +from stat import ST_MODE |
903 | + |
904 | +from charmtools.generators import ( |
905 | + CharmTemplate, |
906 | +) |
907 | + |
908 | +log = logging.getLogger(__name__) |
909 | + |
910 | + |
911 | +class AnsibleCharmTemplate(CharmTemplate): |
912 | + skip_parsing = ['README.ex', 'Makefile'] |
913 | + |
914 | + def create_charm(self, config, output_dir): |
915 | + self._copy_files(output_dir) |
916 | + |
917 | + for root, dirs, files in os.walk(output_dir): |
918 | + for outfile in files: |
919 | + if outfile in self.skip_parsing: |
920 | + continue |
921 | + |
922 | + self._template_file(config, path.join(root, outfile)) |
923 | + |
924 | + self._cleanup_hooks(config, output_dir) |
925 | + self._install_charmhelpers(output_dir) |
926 | + |
927 | + def _copy_files(self, output_dir): |
928 | + here = path.abspath(path.dirname(__file__)) |
929 | + template_dir = path.join(here, 'files') |
930 | + if os.path.exists(output_dir): |
931 | + shutil.rmtree(output_dir) |
932 | + shutil.copytree(template_dir, output_dir) |
933 | + |
934 | + def _template_file(self, config, outfile): |
935 | + if path.islink(outfile): |
936 | + return |
937 | + |
938 | + mode = os.stat(outfile)[ST_MODE] |
939 | + t = Template(file=outfile, searchList=(config)) |
940 | + o = tempfile.NamedTemporaryFile( |
941 | + dir=path.dirname(outfile), delete=False) |
942 | + os.chmod(o.name, mode) |
943 | + o.write(str(t)) |
944 | + o.close() |
945 | + backupname = outfile + str(time.time()) |
946 | + os.rename(outfile, backupname) |
947 | + os.rename(o.name, outfile) |
948 | + os.unlink(backupname) |
949 | + |
950 | + def _cleanup_hooks(self, config, output_dir): |
951 | + # Symlinks must be relative so that they are copied properly |
952 | + # when output_dir is moved to it's final location. |
953 | + for link in ['config-changed', 'install', 'start', 'stop', |
954 | + 'upgrade-charm']: |
955 | + os.symlink('hooks.py', os.path.join(output_dir, 'hooks', link)) |
956 | + |
957 | + def _install_charmhelpers(self, output_dir): |
958 | + helpers_dest = os.path.join(output_dir, 'lib', 'charmhelpers') |
959 | + if not os.path.exists(helpers_dest): |
960 | + os.makedirs(helpers_dest) |
961 | + |
962 | + cmd = './scripts/charm_helpers_sync.py -c charm-helpers.yaml' |
963 | + subprocess.check_call(cmd.split(), cwd=output_dir) |
964 | |
965 | === removed file 'charmtools/templates/python/config.yaml' |
966 | --- charmtools/templates/python/config.yaml 2014-05-27 21:23:41 +0000 |
967 | +++ charmtools/templates/python/config.yaml 1970-01-01 00:00:00 +0000 |
968 | @@ -1,5 +0,0 @@ |
969 | -prompts: |
970 | - symlink: |
971 | - prompt: Symlink all hooks to one python source file? [yN] |
972 | - default: n |
973 | - type: boolean |
974 | |
975 | === removed directory 'charmtools/templates/python/files/hooks_symlinked' |
976 | === removed file 'charmtools/templates/python/files/hooks_symlinked/hooks.py' |
977 | --- charmtools/templates/python/files/hooks_symlinked/hooks.py 2014-05-27 21:23:41 +0000 |
978 | +++ charmtools/templates/python/files/hooks_symlinked/hooks.py 1970-01-01 00:00:00 +0000 |
979 | @@ -1,54 +0,0 @@ |
980 | -#!/usr/bin/python |
981 | - |
982 | -import os |
983 | -import sys |
984 | - |
985 | -sys.path.insert(0, os.path.join(os.environ['CHARM_DIR'], 'lib')) |
986 | - |
987 | -from charmhelpers.core import ( |
988 | - hookenv, |
989 | - host, |
990 | -) |
991 | - |
992 | -hooks = hookenv.Hooks() |
993 | -log = hookenv.log |
994 | - |
995 | -SERVICE = '$metadata.package' |
996 | - |
997 | - |
998 | -@hooks.hook('install') |
999 | -def install(): |
1000 | - log('Installing $metadata.package') |
1001 | - |
1002 | - |
1003 | -@hooks.hook('config-changed') |
1004 | -def config_changed(): |
1005 | - config = hookenv.config() |
1006 | - |
1007 | - for key in config: |
1008 | - if config.changed(key): |
1009 | - log("config['{}'] changed from {} to {}".format( |
1010 | - key, config.previous(key), config[key])) |
1011 | - |
1012 | - config.save() |
1013 | - start() |
1014 | - |
1015 | - |
1016 | -@hooks.hook('upgrade-charm') |
1017 | -def upgrade_charm(): |
1018 | - log('Upgrading $metadata.package') |
1019 | - |
1020 | - |
1021 | -@hooks.hook('start') |
1022 | -def start(): |
1023 | - host.service_restart(SERVICE) or host.service_start(SERVICE) |
1024 | - |
1025 | - |
1026 | -@hooks.hook('stop') |
1027 | -def stop(): |
1028 | - host.service_stop(SERVICE) |
1029 | - |
1030 | - |
1031 | -if __name__ == "__main__": |
1032 | - # execute a hook based on the name the program is called by |
1033 | - hooks.execute(sys.argv) |
1034 | |
1035 | === modified file 'charmtools/templates/python/template.py' |
1036 | --- charmtools/templates/python/template.py 2014-07-29 18:15:41 +0000 |
1037 | +++ charmtools/templates/python/template.py 2014-09-09 18:56:48 +0000 |
1038 | @@ -46,7 +46,6 @@ |
1039 | |
1040 | self._template_file(config, path.join(root, outfile)) |
1041 | |
1042 | - self._cleanup_hooks(config, output_dir) |
1043 | self._install_charmhelpers(output_dir) |
1044 | |
1045 | def _copy_files(self, output_dir): |
1046 | @@ -72,21 +71,6 @@ |
1047 | os.rename(o.name, outfile) |
1048 | os.unlink(backupname) |
1049 | |
1050 | - def _cleanup_hooks(self, config, output_dir): |
1051 | - rmdir = 'hooks' if config['symlink'] else 'hooks_symlinked' |
1052 | - shutil.rmtree(os.path.join(output_dir, rmdir)) |
1053 | - |
1054 | - if config['symlink']: |
1055 | - os.rename( |
1056 | - os.path.join(output_dir, 'hooks_symlinked'), |
1057 | - os.path.join(output_dir, 'hooks') |
1058 | - ) |
1059 | - # Symlinks must be relative so that they are copied properly |
1060 | - # when output_dir is moved to it's final location. |
1061 | - for link in ['config-changed', 'install', 'start', 'stop', |
1062 | - 'upgrade-charm']: |
1063 | - os.symlink('hooks.py', os.path.join(output_dir, 'hooks', link)) |
1064 | - |
1065 | def _install_charmhelpers(self, output_dir): |
1066 | helpers_dest = os.path.join(output_dir, 'lib', 'charmhelpers') |
1067 | if not os.path.exists(helpers_dest): |
1068 | |
1069 | === modified file 'setup.py' |
1070 | --- setup.py 2014-06-23 14:44:59 +0000 |
1071 | +++ setup.py 2014-09-09 18:56:48 +0000 |
1072 | @@ -58,6 +58,7 @@ |
1073 | 'charmtools.templates': [ |
1074 | 'bash = charmtools.templates.bash:BashCharmTemplate', |
1075 | 'python = charmtools.templates.python:PythonCharmTemplate', |
1076 | + 'ansible = charmtools.templates.ansible:AnsibleCharmTemplate', |
1077 | ] |
1078 | }, |
1079 | ) |
1080 | |
1081 | === modified file 'tests/test_charm_proof.py' |
1082 | --- tests/test_charm_proof.py 2014-06-17 15:44:04 +0000 |
1083 | +++ tests/test_charm_proof.py 2014-09-09 18:56:48 +0000 |
1084 | @@ -420,7 +420,10 @@ |
1085 | """Maintainers field happy path.""" |
1086 | linter = Mock() |
1087 | charm = { |
1088 | - 'maintainers': ['Tester <tester@example.com>'], |
1089 | + 'maintainers': [ |
1090 | + 'Tester <tester@example.com>', |
1091 | + 'Tester Joe H. <tester@example.com>', |
1092 | + ] |
1093 | } |
1094 | validate_maintainer(charm, linter) |
1095 | self.assertFalse(linter.err.called) |
1096 | |
1097 | === modified file 'tests_functional/create/test.sh' |
1098 | --- tests_functional/create/test.sh 2014-06-12 00:21:24 +0000 |
1099 | +++ tests_functional/create/test.sh 2014-09-09 18:56:48 +0000 |
1100 | @@ -52,6 +52,9 @@ |
1101 | echo ===== Testing python charm template ===== |
1102 | $PYTHON $TESTDIR/test_python_create.py |
1103 | |
1104 | +echo ===== Testing ansible charm template ===== |
1105 | +$PYTHON $TESTDIR/test_ansible_create.py |
1106 | + |
1107 | echo ===== All tests passed! ===== |
1108 | echo PASS |
1109 | set +e |
1110 | |
1111 | === added file 'tests_functional/create/test_ansible_create.py' |
1112 | --- tests_functional/create/test_ansible_create.py 1970-01-01 00:00:00 +0000 |
1113 | +++ tests_functional/create/test_ansible_create.py 2014-09-09 18:56:48 +0000 |
1114 | @@ -0,0 +1,101 @@ |
1115 | +#!/usr/bin/python |
1116 | + |
1117 | +# Copyright (C) 2014 Canonical Ltd. |
1118 | +# |
1119 | +# This program is free software: you can redistribute it and/or modify |
1120 | +# it under the terms of the GNU General Public License as published by |
1121 | +# the Free Software Foundation, either version 3 of the License, or |
1122 | +# (at your option) any later version. |
1123 | +# |
1124 | +# This program is distributed in the hope that it will be useful, |
1125 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1126 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1127 | +# GNU General Public License for more details. |
1128 | +# |
1129 | +# You should have received a copy of the GNU General Public License |
1130 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1131 | + |
1132 | +import os |
1133 | +import shutil |
1134 | +import tempfile |
1135 | +import unittest |
1136 | + |
1137 | +from mock import patch |
1138 | +from os.path import join |
1139 | +from unittest import TestCase |
1140 | + |
1141 | +import pkg_resources |
1142 | +import yaml |
1143 | + |
1144 | +from charmtools.create import ( |
1145 | + main, |
1146 | +) |
1147 | + |
1148 | + |
1149 | +def flatten(path): |
1150 | + for root, dirs, files in os.walk(path): |
1151 | + for f in files: |
1152 | + yield join(root[len(path):], f).lstrip('/') |
1153 | + |
1154 | + |
1155 | +class AnsibleCreateTest(TestCase): |
1156 | + maxDiff = None |
1157 | + def setUp(self): |
1158 | + self.tempdir = tempfile.mkdtemp() |
1159 | + |
1160 | + def tearDown(self): |
1161 | + shutil.rmtree(self.tempdir) |
1162 | + |
1163 | + def _expected_files(self): |
1164 | + static_files = list(flatten(pkg_resources.resource_filename( |
1165 | + 'charmtools', 'templates/ansible/files'))) |
1166 | + dynamic_files = [ |
1167 | + 'hooks/config-changed', |
1168 | + 'hooks/install', |
1169 | + 'hooks/start', |
1170 | + 'hooks/stop', |
1171 | + 'hooks/upgrade-charm', |
1172 | + 'lib/charmhelpers/__init__.py', |
1173 | + 'lib/charmhelpers/contrib/__init__.py', |
1174 | + 'lib/charmhelpers/contrib/ansible/__init__.py', |
1175 | + 'lib/charmhelpers/contrib/templating/__init__.py', |
1176 | + 'lib/charmhelpers/contrib/templating/contexts.py', |
1177 | + 'lib/charmhelpers/core/__init__.py', |
1178 | + 'lib/charmhelpers/core/fstab.py', |
1179 | + 'lib/charmhelpers/core/hookenv.py', |
1180 | + 'lib/charmhelpers/core/host.py', |
1181 | + 'lib/charmhelpers/core/services/__init__.py', |
1182 | + 'lib/charmhelpers/core/services/base.py', |
1183 | + 'lib/charmhelpers/core/services/helpers.py', |
1184 | + 'lib/charmhelpers/core/templating.py', |
1185 | + 'lib/charmhelpers/fetch/__init__.py', |
1186 | + 'lib/charmhelpers/fetch/archiveurl.py', |
1187 | + 'lib/charmhelpers/fetch/bzrurl.py', |
1188 | + ] |
1189 | + return sorted(static_files + dynamic_files) |
1190 | + |
1191 | + @patch('charmtools.create.setup_parser') |
1192 | + def test_default(self, setup_parser): |
1193 | + """Test of `charm create -t ansible testcharm`""" |
1194 | + class args(object): |
1195 | + charmname = 'testcharm' |
1196 | + charmhome = self.tempdir |
1197 | + template = 'ansible' |
1198 | + accept_defaults = True |
1199 | + verbose = False |
1200 | + |
1201 | + setup_parser.return_value.parse_args.return_value = args |
1202 | + |
1203 | + main() |
1204 | + |
1205 | + outputdir = join(self.tempdir, args.charmname) |
1206 | + actual_files = sorted(flatten(outputdir)) |
1207 | + expected_files = self._expected_files() |
1208 | + metadata = yaml.load(open(join(outputdir, 'metadata.yaml'), 'r')) |
1209 | + |
1210 | + self.assertEqual(expected_files, actual_files) |
1211 | + self.assertEqual(metadata['name'], args.charmname) |
1212 | + |
1213 | + |
1214 | +if __name__ == '__main__': |
1215 | + unittest.main() |
1216 | |
1217 | === modified file 'tests_functional/create/test_python_create.py' |
1218 | --- tests_functional/create/test_python_create.py 2014-06-20 19:46:59 +0000 |
1219 | +++ tests_functional/create/test_python_create.py 2014-09-09 18:56:48 +0000 |
1220 | @@ -45,46 +45,22 @@ |
1221 | def tearDown(self): |
1222 | shutil.rmtree(self.tempdir) |
1223 | |
1224 | - def _expected_files(self, symlinked=False): |
1225 | + def _expected_files(self): |
1226 | static_files = list(flatten(pkg_resources.resource_filename( |
1227 | 'charmtools', 'templates/python/files'))) |
1228 | - static_files = [f for f in static_files |
1229 | - if not f.startswith('hooks_symlinked/')] |
1230 | - if symlinked: |
1231 | - static_files.append('hooks/hooks.py') |
1232 | dynamic_files = [ |
1233 | 'lib/charmhelpers/__init__.py', |
1234 | 'lib/charmhelpers/core/__init__.py', |
1235 | 'lib/charmhelpers/core/fstab.py', |
1236 | 'lib/charmhelpers/core/hookenv.py', |
1237 | 'lib/charmhelpers/core/host.py', |
1238 | + 'lib/charmhelpers/core/services/__init__.py', |
1239 | + 'lib/charmhelpers/core/services/base.py', |
1240 | + 'lib/charmhelpers/core/services/helpers.py', |
1241 | + 'lib/charmhelpers/core/templating.py', |
1242 | ] |
1243 | return sorted(static_files + dynamic_files) |
1244 | |
1245 | - @patch('__builtin__.raw_input') |
1246 | - @patch('charmtools.create.setup_parser') |
1247 | - def test_interactive(self, setup_parser, raw_input_): |
1248 | - """Functional test of a full 'charm create' run.""" |
1249 | - class args(object): |
1250 | - charmname = 'testcharm' |
1251 | - charmhome = self.tempdir |
1252 | - template = 'python' |
1253 | - accept_defaults = False |
1254 | - verbose = False |
1255 | - |
1256 | - setup_parser.return_value.parse_args.return_value = args |
1257 | - raw_input_.side_effect = ['Y'] |
1258 | - |
1259 | - main() |
1260 | - |
1261 | - outputdir = join(self.tempdir, args.charmname) |
1262 | - actual_files = sorted(flatten(outputdir)) |
1263 | - expected_files = self._expected_files(symlinked=True) |
1264 | - metadata = yaml.load(open(join(outputdir, 'metadata.yaml'), 'r')) |
1265 | - |
1266 | - self.assertEqual(expected_files, actual_files) |
1267 | - self.assertEqual(metadata['name'], args.charmname) |
1268 | - |
1269 | @patch('charmtools.create.setup_parser') |
1270 | def test_defaults(self, setup_parser): |
1271 | """Functional test of a full 'charm create' run.""" |
Example playbook should show all the available vars that get written by `apply_playbook` to `/etc/ansible/ host_vars/ localhost` (and maybe charmhelpers. contrib. ansible should document these too)