Merge lp:~tvansteenburgh/charm-tools/ansible-template into lp:charm-tools/1.4

Proposed by Tim Van Steenburgh
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
Reviewer Review Type Date Requested Status
Charles Butler (community) Approve
Review via email: mp+224480@code.launchpad.net

Description of the change

Added ansible template for 'charm create'.

To post a comment you must log in.
Revision history for this message
Whit Morriss (whitmo) wrote :

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)

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

Revision history for this message
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.

Revision history for this message
Charles Butler (lazypower) wrote :

+1 LGTM

Lets get this in charm-tools and start iterating on the output templates.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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."""

Subscribers

People subscribed via source and target branches