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
=== modified file 'charmtools/charms.py'
--- charmtools/charms.py 2014-06-17 15:44:04 +0000
+++ charmtools/charms.py 2014-09-09 18:56:48 +0000
@@ -453,7 +453,7 @@
453 for maintainer in maintainers:453 for maintainer in maintainers:
454 (name, address) = email.utils.parseaddr(maintainer)454 (name, address) = email.utils.parseaddr(maintainer)
455 formatted = email.utils.formataddr((name, address))455 formatted = email.utils.formataddr((name, address))
456 if formatted != maintainer:456 if formatted.replace('"', '') != maintainer:
457 linter.warn(457 linter.warn(
458 'Maintainer format should be "Name <Email>", '458 'Maintainer format should be "Name <Email>", '
459 'not "%s"' % formatted)459 'not "%s"' % formatted)
460460
=== modified file 'charmtools/generators/template.py'
--- charmtools/generators/template.py 2014-07-29 18:15:41 +0000
+++ charmtools/generators/template.py 2014-09-09 18:56:48 +0000
@@ -46,6 +46,14 @@
46 """Return default configuration for this template, loaded from a46 """Return default configuration for this template, loaded from a
47 config.yaml file.47 config.yaml file.
4848
49 This is a sample config.yaml that configures one user prompt::
50
51 prompts:
52 symlink:
53 prompt: Symlink all hooks to one python source file? [yN]
54 default: n
55 type: boolean
56
49 """57 """
50 path = self.config_path()58 path = self.config_path()
51 if os.path.exists(path):59 if os.path.exists(path):
5260
=== added directory 'charmtools/templates/ansible'
=== added file 'charmtools/templates/ansible/__init__.py'
--- charmtools/templates/ansible/__init__.py 1970-01-01 00:00:00 +0000
+++ charmtools/templates/ansible/__init__.py 2014-09-09 18:56:48 +0000
@@ -0,0 +1,18 @@
1#!/usr/bin/python
2#
3# Copyright (C) 2014 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18from .template import AnsibleCharmTemplate # noqa
019
=== added directory 'charmtools/templates/ansible/files'
=== added file 'charmtools/templates/ansible/files/Makefile'
--- charmtools/templates/ansible/files/Makefile 1970-01-01 00:00:00 +0000
+++ charmtools/templates/ansible/files/Makefile 2014-09-09 18:56:48 +0000
@@ -0,0 +1,24 @@
1#!/usr/bin/make
2
3build: virtualenv lint test
4
5virtualenv: .venv/bin/python
6.venv/bin/python:
7 sudo apt-get install python-virtualenv
8 virtualenv .venv
9 .venv/bin/pip install nose flake8 mock pyyaml
10
11lint:
12 @.venv/bin/flake8 hooks unit_tests
13 @charm proof
14
15test:
16 @echo Starting tests...
17 @CHARM_DIR=. PYTHONPATH=./hooks .venv/bin/nosetests --nologcapture unit_tests
18
19sync-charm-helpers:
20 @.venv/bin/python scripts/charm_helpers_sync.py -c charm-helpers.yaml
21
22clean:
23 rm -rf .venv
24 find -name *.pyc -delete
025
=== added file 'charmtools/templates/ansible/files/README.ex'
--- charmtools/templates/ansible/files/README.ex 1970-01-01 00:00:00 +0000
+++ charmtools/templates/ansible/files/README.ex 2014-09-09 18:56:48 +0000
@@ -0,0 +1,44 @@
1# Overview
2
3Describe the intended usage of this charm and anything unique about how this charm relates to others here.
4
5This 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
6
7Use this as a Markdown reference if you need help with the formatting of this README: http://askubuntu.com/editing-help
8
9This charm provides [service](http://example.com). Add a description here of what the service itself actually does.
10
11Also 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.
12
13# Usage
14
15Step by step instructions on using the charm:
16
17 juju deploy servicename
18
19and 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.
20
21You can then browse to http://ip-address to configure the service.
22
23## Scale out Usage
24
25If 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.
26
27## Known Limitations and Issues
28
29This not only helps users but gives people a place to start if they want to help you add features to your charm.
30
31# Configuration
32
33The 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.
34
35# Contact Information
36
37Though this will be listed in the charm store itself don't assume a user will know that, so include that information here:
38
39## Upstream Project Name
40
41- Upstream website
42- Upstream bug tracker
43- Upstream mailing list or contact information
44- Feel free to add things if it's useful for users
045
=== added file 'charmtools/templates/ansible/files/charm-helpers.yaml'
--- charmtools/templates/ansible/files/charm-helpers.yaml 1970-01-01 00:00:00 +0000
+++ charmtools/templates/ansible/files/charm-helpers.yaml 2014-09-09 18:56:48 +0000
@@ -0,0 +1,7 @@
1destination: lib/charmhelpers
2branch: lp:charm-helpers
3include:
4 - core
5 - fetch
6 - contrib.ansible|inc=*
7 - contrib.templating.contexts
08
=== added file 'charmtools/templates/ansible/files/config.yaml'
--- charmtools/templates/ansible/files/config.yaml 1970-01-01 00:00:00 +0000
+++ charmtools/templates/ansible/files/config.yaml 2014-09-09 18:56:48 +0000
@@ -0,0 +1,14 @@
1options:
2 string-option:
3 type: string
4 default: "Default Value"
5 description: "A short description of the configuration option"
6 boolean-option:
7 type: boolean
8 default: False
9 description: "A short description of the configuration option"
10 int-option:
11 type: int
12 default: 9001
13 description: "A short description of the configuration option"
14
015
=== added directory 'charmtools/templates/ansible/files/hooks'
=== added file 'charmtools/templates/ansible/files/hooks/hooks.py'
--- charmtools/templates/ansible/files/hooks/hooks.py 1970-01-01 00:00:00 +0000
+++ charmtools/templates/ansible/files/hooks/hooks.py 2014-09-09 18:56:48 +0000
@@ -0,0 +1,35 @@
1#!/usr/bin/env python
2
3import os
4import sys
5
6sys.path.insert(0, os.path.join(os.environ['CHARM_DIR'], 'lib'))
7
8import charmhelpers.contrib.ansible
9
10# Create the hooks helper, passing a list of hooks which will be
11# handled by default by running all sections of the playbook
12# tagged with the hook name.
13hooks = charmhelpers.contrib.ansible.AnsibleHooks(
14 playbook_path='playbooks/site.yaml',
15 default_hooks=[
16 'start',
17 'stop',
18 'config-changed',
19 'upgrade-charm',
20 ])
21
22
23@hooks.hook('install', 'upgrade-charm')
24def install():
25 """Install ansible.
26
27 The hook() helper decorating this install function ensures that after this
28 function finishes, any tasks in the playbook tagged with install or
29 upgrade-charm are executed.
30 """
31 charmhelpers.contrib.ansible.install_ansible_support(from_ppa=True)
32
33
34if __name__ == "__main__":
35 hooks.execute(sys.argv)
036
=== added file 'charmtools/templates/ansible/files/icon.svg'
--- charmtools/templates/ansible/files/icon.svg 1970-01-01 00:00:00 +0000
+++ charmtools/templates/ansible/files/icon.svg 2014-09-09 18:56:48 +0000
@@ -0,0 +1,279 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<!-- Created with Inkscape (http://www.inkscape.org/) -->
3
4<svg
5 xmlns:dc="http://purl.org/dc/elements/1.1/"
6 xmlns:cc="http://creativecommons.org/ns#"
7 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8 xmlns:svg="http://www.w3.org/2000/svg"
9 xmlns="http://www.w3.org/2000/svg"
10 xmlns:xlink="http://www.w3.org/1999/xlink"
11 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
12 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
13 width="96"
14 height="96"
15 id="svg6517"
16 version="1.1"
17 inkscape:version="0.48+devel r12274"
18 sodipodi:docname="Juju_charm_icon_template.svg">
19 <defs
20 id="defs6519">
21 <linearGradient
22 inkscape:collect="always"
23 xlink:href="#Background"
24 id="linearGradient6461"
25 gradientUnits="userSpaceOnUse"
26 x1="0"
27 y1="970.29498"
28 x2="144"
29 y2="970.29498"
30 gradientTransform="matrix(0,-0.66666669,0.6660448,0,-866.25992,731.29077)" />
31 <linearGradient
32 id="Background">
33 <stop
34 id="stop4178"
35 offset="0"
36 style="stop-color:#b8b8b8;stop-opacity:1" />
37 <stop
38 id="stop4180"
39 offset="1"
40 style="stop-color:#c9c9c9;stop-opacity:1" />
41 </linearGradient>
42 <filter
43 style="color-interpolation-filters:sRGB;"
44 inkscape:label="Inner Shadow"
45 id="filter1121">
46 <feFlood
47 flood-opacity="0.59999999999999998"
48 flood-color="rgb(0,0,0)"
49 result="flood"
50 id="feFlood1123" />
51 <feComposite
52 in="flood"
53 in2="SourceGraphic"
54 operator="out"
55 result="composite1"
56 id="feComposite1125" />
57 <feGaussianBlur
58 in="composite1"
59 stdDeviation="1"
60 result="blur"
61 id="feGaussianBlur1127" />
62 <feOffset
63 dx="0"
64 dy="2"
65 result="offset"
66 id="feOffset1129" />
67 <feComposite
68 in="offset"
69 in2="SourceGraphic"
70 operator="atop"
71 result="composite2"
72 id="feComposite1131" />
73 </filter>
74 <filter
75 style="color-interpolation-filters:sRGB;"
76 inkscape:label="Drop Shadow"
77 id="filter950">
78 <feFlood
79 flood-opacity="0.25"
80 flood-color="rgb(0,0,0)"
81 result="flood"
82 id="feFlood952" />
83 <feComposite
84 in="flood"
85 in2="SourceGraphic"
86 operator="in"
87 result="composite1"
88 id="feComposite954" />
89 <feGaussianBlur
90 in="composite1"
91 stdDeviation="1"
92 result="blur"
93 id="feGaussianBlur956" />
94 <feOffset
95 dx="0"
96 dy="1"
97 result="offset"
98 id="feOffset958" />
99 <feComposite
100 in="SourceGraphic"
101 in2="offset"
102 operator="over"
103 result="composite2"
104 id="feComposite960" />
105 </filter>
106 <clipPath
107 clipPathUnits="userSpaceOnUse"
108 id="clipPath873">
109 <g
110 transform="matrix(0,-0.66666667,0.66604479,0,-258.25992,677.00001)"
111 id="g875"
112 inkscape:label="Layer 1"
113 style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline">
114 <path
115 style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline"
116 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"
117 id="path877"
118 inkscape:connector-curvature="0"
119 sodipodi:nodetypes="sssssssss" />
120 </g>
121 </clipPath>
122 <filter
123 inkscape:collect="always"
124 id="filter891"
125 inkscape:label="Badge Shadow">
126 <feGaussianBlur
127 inkscape:collect="always"
128 stdDeviation="0.71999962"
129 id="feGaussianBlur893" />
130 </filter>
131 </defs>
132 <sodipodi:namedview
133 id="base"
134 pagecolor="#ffffff"
135 bordercolor="#666666"
136 borderopacity="1.0"
137 inkscape:pageopacity="0.0"
138 inkscape:pageshadow="2"
139 inkscape:zoom="4.0745362"
140 inkscape:cx="18.514671"
141 inkscape:cy="49.018169"
142 inkscape:document-units="px"
143 inkscape:current-layer="layer1"
144 showgrid="true"
145 fit-margin-top="0"
146 fit-margin-left="0"
147 fit-margin-right="0"
148 fit-margin-bottom="0"
149 inkscape:window-width="1920"
150 inkscape:window-height="1029"
151 inkscape:window-x="0"
152 inkscape:window-y="24"
153 inkscape:window-maximized="1"
154 showborder="true"
155 showguides="true"
156 inkscape:guide-bbox="true"
157 inkscape:showpageshadow="false">
158 <inkscape:grid
159 type="xygrid"
160 id="grid821" />
161 <sodipodi:guide
162 orientation="1,0"
163 position="16,48"
164 id="guide823" />
165 <sodipodi:guide
166 orientation="0,1"
167 position="64,80"
168 id="guide825" />
169 <sodipodi:guide
170 orientation="1,0"
171 position="80,40"
172 id="guide827" />
173 <sodipodi:guide
174 orientation="0,1"
175 position="64,16"
176 id="guide829" />
177 </sodipodi:namedview>
178 <metadata
179 id="metadata6522">
180 <rdf:RDF>
181 <cc:Work
182 rdf:about="">
183 <dc:format>image/svg+xml</dc:format>
184 <dc:type
185 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
186 <dc:title></dc:title>
187 </cc:Work>
188 </rdf:RDF>
189 </metadata>
190 <g
191 inkscape:label="BACKGROUND"
192 inkscape:groupmode="layer"
193 id="layer1"
194 transform="translate(268,-635.29076)"
195 style="display:inline">
196 <path
197 style="fill:url(#linearGradient6461);fill-opacity:1;stroke:none;display:inline;filter:url(#filter1121)"
198 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"
199 id="path6455"
200 inkscape:connector-curvature="0"
201 sodipodi:nodetypes="sssssssss" />
202 </g>
203 <g
204 inkscape:groupmode="layer"
205 id="layer3"
206 inkscape:label="PLACE YOUR PICTOGRAM HERE"
207 style="display:inline" />
208 <g
209 inkscape:groupmode="layer"
210 id="layer2"
211 inkscape:label="BADGE"
212 style="display:none"
213 sodipodi:insensitive="true">
214 <g
215 style="display:inline"
216 transform="translate(-340.00001,-581)"
217 id="g4394"
218 clip-path="none">
219 <g
220 id="g855">
221 <g
222 inkscape:groupmode="maskhelper"
223 id="g870"
224 clip-path="url(#clipPath873)"
225 style="opacity:0.6;filter:url(#filter891)">
226 <path
227 transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-237.54282)"
228 d="m 264,552.36218 a 12,12 0 1 1 -24,0 A 12,12 0 1 1 264,552.36218 Z"
229 sodipodi:ry="12"
230 sodipodi:rx="12"
231 sodipodi:cy="552.36218"
232 sodipodi:cx="252"
233 id="path844"
234 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"
235 sodipodi:type="arc" />
236 </g>
237 <g
238 id="g862">
239 <path
240 sodipodi:type="arc"
241 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"
242 id="path4398"
243 sodipodi:cx="252"
244 sodipodi:cy="552.36218"
245 sodipodi:rx="12"
246 sodipodi:ry="12"
247 d="m 264,552.36218 a 12,12 0 1 1 -24,0 A 12,12 0 1 1 264,552.36218 Z"
248 transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-238.54282)" />
249 <path
250 transform="matrix(1.25,0,0,1.25,33,-100.45273)"
251 d="m 264,552.36218 a 12,12 0 1 1 -24,0 A 12,12 0 1 1 264,552.36218 Z"
252 sodipodi:ry="12"
253 sodipodi:rx="12"
254 sodipodi:cy="552.36218"
255 sodipodi:cx="252"
256 id="path4400"
257 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"
258 sodipodi:type="arc" />
259 <path
260 sodipodi:type="star"
261 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"
262 id="path4459"
263 sodipodi:sides="5"
264 sodipodi:cx="666.19574"
265 sodipodi:cy="589.50385"
266 sodipodi:r1="7.2431178"
267 sodipodi:r2="4.3458705"
268 sodipodi:arg1="1.0471976"
269 sodipodi:arg2="1.6755161"
270 inkscape:flatsided="false"
271 inkscape:rounded="0.1"
272 inkscape:randomized="0"
273 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"
274 transform="matrix(1.511423,-0.16366377,0.16366377,1.511423,-755.37346,-191.93651)" />
275 </g>
276 </g>
277 </g>
278 </g>
279</svg>
0280
=== added file 'charmtools/templates/ansible/files/metadata.yaml'
--- charmtools/templates/ansible/files/metadata.yaml 1970-01-01 00:00:00 +0000
+++ charmtools/templates/ansible/files/metadata.yaml 2014-09-09 18:56:48 +0000
@@ -0,0 +1,17 @@
1name: $metadata.package
2summary: $metadata.summary
3maintainer: $metadata.maintainer
4description: |
5 $metadata.description
6categories:
7 - misc
8subordinate: false
9provides:
10 provides-relation:
11 interface: interface-name
12requires:
13 requires-relation:
14 interface: interface-name
15peers:
16 peer-relation:
17 interface: interface-name
018
=== added directory 'charmtools/templates/ansible/files/playbooks'
=== added file 'charmtools/templates/ansible/files/playbooks/site.yaml'
--- charmtools/templates/ansible/files/playbooks/site.yaml 1970-01-01 00:00:00 +0000
+++ charmtools/templates/ansible/files/playbooks/site.yaml 2014-09-09 18:56:48 +0000
@@ -0,0 +1,47 @@
1# The tasks here are left as examples and should be removed/replaced by the
2# charm author. Some things to note:
3#
4# 1. All charm config values are available as template variables
5# e.g. repo -> {{repo}}, app-name -> {{app_name}} (note underscore)
6#
7# 2. Along with charm config values, the following variables are also
8# made available as template vars:
9#
10# charm_dir
11# local_unit
12# unit_private_address
13# unit_public_address
14#
15# 3. Use tags to control when each task is executed. The tags list should
16# contain one or more hook names.
17
18- hosts: all
19 tasks:
20
21 - name: Install required packages.
22 apt: pkg={{ item }} state=latest update_cache=yes
23 with_items:
24 - python-django
25 - python-django-celery
26 tags:
27 - install
28 - upgrade-charm
29
30 - name: Put app code in place.
31 git: repo=git://github.com/absoludity/charm-bootstrap-ansible.git
32 dest=/srv/myapp
33 version=HEAD
34 tags:
35 - install
36 - config-changed
37
38 - name: Start service
39 debug: msg="You'd start some service here. The config 'string-option' has the value '{{ string_option }}'"
40 tags:
41 - start
42 - config-changed
43
44 - name: Stop service
45 debug: msg="You'd stop some service here. The config 'string-option' is '{{ string_option }}'"
46 tags:
47 - stop
048
=== added file 'charmtools/templates/ansible/files/revision'
--- charmtools/templates/ansible/files/revision 1970-01-01 00:00:00 +0000
+++ charmtools/templates/ansible/files/revision 2014-09-09 18:56:48 +0000
@@ -0,0 +1,1 @@
11
02
=== added directory 'charmtools/templates/ansible/files/scripts'
=== added file 'charmtools/templates/ansible/files/scripts/charm_helpers_sync.py'
--- charmtools/templates/ansible/files/scripts/charm_helpers_sync.py 1970-01-01 00:00:00 +0000
+++ charmtools/templates/ansible/files/scripts/charm_helpers_sync.py 2014-09-09 18:56:48 +0000
@@ -0,0 +1,225 @@
1#!/usr/bin/python
2#
3# Copyright 2013 Canonical Ltd.
4
5# Authors:
6# Adam Gandelman <adamg@ubuntu.com>
7#
8
9import logging
10import optparse
11import os
12import subprocess
13import shutil
14import sys
15import tempfile
16import yaml
17
18from fnmatch import fnmatch
19
20CHARM_HELPERS_BRANCH = 'lp:charm-helpers'
21
22
23def parse_config(conf_file):
24 if not os.path.isfile(conf_file):
25 logging.error('Invalid config file: %s.' % conf_file)
26 return False
27 return yaml.load(open(conf_file).read())
28
29
30def clone_helpers(work_dir, branch):
31 dest = os.path.join(work_dir, 'charm-helpers')
32 logging.info('Checking out %s to %s.' % (branch, dest))
33 cmd = ['bzr', 'branch', branch, dest]
34 subprocess.check_call(cmd)
35 return dest
36
37
38def _module_path(module):
39 return os.path.join(*module.split('.'))
40
41
42def _src_path(src, module):
43 return os.path.join(src, 'charmhelpers', _module_path(module))
44
45
46def _dest_path(dest, module):
47 return os.path.join(dest, _module_path(module))
48
49
50def _is_pyfile(path):
51 return os.path.isfile(path + '.py')
52
53
54def ensure_init(path):
55 '''
56 ensure directories leading up to path are importable, omitting
57 parent directory, eg path='/hooks/helpers/foo'/:
58 hooks/
59 hooks/helpers/__init__.py
60 hooks/helpers/foo/__init__.py
61 '''
62 for d, dirs, files in os.walk(os.path.join(*path.split('/')[:2])):
63 _i = os.path.join(d, '__init__.py')
64 if not os.path.exists(_i):
65 logging.info('Adding missing __init__.py: %s' % _i)
66 open(_i, 'wb').close()
67
68
69def sync_pyfile(src, dest):
70 src = src + '.py'
71 src_dir = os.path.dirname(src)
72 logging.info('Syncing pyfile: %s -> %s.' % (src, dest))
73 if not os.path.exists(dest):
74 os.makedirs(dest)
75 shutil.copy(src, dest)
76 if os.path.isfile(os.path.join(src_dir, '__init__.py')):
77 shutil.copy(os.path.join(src_dir, '__init__.py'),
78 dest)
79 ensure_init(dest)
80
81
82def get_filter(opts=None):
83 opts = opts or []
84 if 'inc=*' in opts:
85 # do not filter any files, include everything
86 return None
87
88 def _filter(dir, ls):
89 incs = [opt.split('=').pop() for opt in opts if 'inc=' in opt]
90 _filter = []
91 for f in ls:
92 _f = os.path.join(dir, f)
93
94 if not os.path.isdir(_f) and not _f.endswith('.py') and incs:
95 if True not in [fnmatch(_f, inc) for inc in incs]:
96 logging.debug('Not syncing %s, does not match include '
97 'filters (%s)' % (_f, incs))
98 _filter.append(f)
99 else:
100 logging.debug('Including file, which matches include '
101 'filters (%s): %s' % (incs, _f))
102 elif (os.path.isfile(_f) and not _f.endswith('.py')):
103 logging.debug('Not syncing file: %s' % f)
104 _filter.append(f)
105 elif (os.path.isdir(_f) and not
106 os.path.isfile(os.path.join(_f, '__init__.py'))):
107 logging.debug('Not syncing directory: %s' % f)
108 _filter.append(f)
109 return _filter
110 return _filter
111
112
113def sync_directory(src, dest, opts=None):
114 if os.path.exists(dest):
115 logging.debug('Removing existing directory: %s' % dest)
116 shutil.rmtree(dest)
117 logging.info('Syncing directory: %s -> %s.' % (src, dest))
118
119 shutil.copytree(src, dest, ignore=get_filter(opts))
120 ensure_init(dest)
121
122
123def sync(src, dest, module, opts=None):
124 if os.path.isdir(_src_path(src, module)):
125 sync_directory(_src_path(src, module), _dest_path(dest, module), opts)
126 elif _is_pyfile(_src_path(src, module)):
127 sync_pyfile(_src_path(src, module),
128 os.path.dirname(_dest_path(dest, module)))
129 else:
130 logging.warn('Could not sync: %s. Neither a pyfile or directory, '
131 'does it even exist?' % module)
132
133
134def parse_sync_options(options):
135 if not options:
136 return []
137 return options.split(',')
138
139
140def extract_options(inc, global_options=None):
141 global_options = global_options or []
142 if global_options and isinstance(global_options, basestring):
143 global_options = [global_options]
144 if '|' not in inc:
145 return (inc, global_options)
146 inc, opts = inc.split('|')
147 return (inc, parse_sync_options(opts) + global_options)
148
149
150def sync_helpers(include, src, dest, options=None):
151 if not os.path.isdir(dest):
152 os.mkdir(dest)
153
154 global_options = parse_sync_options(options)
155
156 for inc in include:
157 if isinstance(inc, str):
158 inc, opts = extract_options(inc, global_options)
159 sync(src, dest, inc, opts)
160 elif isinstance(inc, dict):
161 # could also do nested dicts here.
162 for k, v in inc.iteritems():
163 if isinstance(v, list):
164 for m in v:
165 inc, opts = extract_options(m, global_options)
166 sync(src, dest, '%s.%s' % (k, inc), opts)
167
168if __name__ == '__main__':
169 parser = optparse.OptionParser()
170 parser.add_option('-c', '--config', action='store', dest='config',
171 default=None, help='helper config file')
172 parser.add_option('-D', '--debug', action='store_true', dest='debug',
173 default=False, help='debug')
174 parser.add_option('-b', '--branch', action='store', dest='branch',
175 help='charm-helpers bzr branch (overrides config)')
176 parser.add_option('-d', '--destination', action='store', dest='dest_dir',
177 help='sync destination dir (overrides config)')
178 (opts, args) = parser.parse_args()
179
180 if opts.debug:
181 logging.basicConfig(level=logging.DEBUG)
182 else:
183 logging.basicConfig(level=logging.INFO)
184
185 if opts.config:
186 logging.info('Loading charm helper config from %s.' % opts.config)
187 config = parse_config(opts.config)
188 if not config:
189 logging.error('Could not parse config from %s.' % opts.config)
190 sys.exit(1)
191 else:
192 config = {}
193
194 if 'branch' not in config:
195 config['branch'] = CHARM_HELPERS_BRANCH
196 if opts.branch:
197 config['branch'] = opts.branch
198 if opts.dest_dir:
199 config['destination'] = opts.dest_dir
200
201 if 'destination' not in config:
202 logging.error('No destination dir. specified as option or config.')
203 sys.exit(1)
204
205 if 'include' not in config:
206 if not args:
207 logging.error('No modules to sync specified as option or config.')
208 sys.exit(1)
209 config['include'] = []
210 [config['include'].append(a) for a in args]
211
212 sync_options = None
213 if 'options' in config:
214 sync_options = config['options']
215 tmpd = tempfile.mkdtemp()
216 try:
217 checkout = clone_helpers(tmpd, config['branch'])
218 sync_helpers(config['include'], checkout, config['destination'],
219 options=sync_options)
220 except Exception, e:
221 logging.error("Could not sync: %s" % e)
222 raise e
223 finally:
224 logging.debug('Cleaning up %s' % tmpd)
225 shutil.rmtree(tmpd)
0226
=== added directory 'charmtools/templates/ansible/files/unit_tests'
=== added file 'charmtools/templates/ansible/files/unit_tests/test_hooks.py'
--- charmtools/templates/ansible/files/unit_tests/test_hooks.py 1970-01-01 00:00:00 +0000
+++ charmtools/templates/ansible/files/unit_tests/test_hooks.py 2014-09-09 18:56:48 +0000
@@ -0,0 +1,62 @@
1import unittest
2
3try:
4 import mock
5except ImportError:
6 raise ImportError(
7 "Please ensure both python-mock and python-nose are installed.")
8
9
10from hooks import hooks
11
12
13class InstallHookTestCase(unittest.TestCase):
14
15 def setUp(self):
16 super(InstallHookTestCase, self).setUp()
17
18 patcher = mock.patch('hooks.charmhelpers')
19 self.mock_charmhelpers = patcher.start()
20 self.addCleanup(patcher.stop)
21 self.mock_charmhelpers.core.hookenv.config.return_value = {
22 'install_deps_from_ppa': False,
23 }
24
25 patcher = mock.patch('charmhelpers.contrib.ansible.apply_playbook')
26 self.mock_apply_playbook = patcher.start()
27 self.addCleanup(patcher.stop)
28
29 def test_installs_ansible_support(self):
30 hooks.execute(['install'])
31
32 ansible = self.mock_charmhelpers.contrib.ansible
33 ansible.install_ansible_support.assert_called_once_with(
34 from_ppa=True)
35
36 def test_applies_install_playbook(self):
37 hooks.execute(['install'])
38
39 self.assertEqual([
40 mock.call('playbooks/site.yaml', tags=['install']),
41 ], self.mock_apply_playbook.call_args_list)
42
43
44class DefaultHooksTestCase(unittest.TestCase):
45
46 def setUp(self):
47 super(DefaultHooksTestCase, self).setUp()
48 patcher = mock.patch('charmhelpers.contrib.ansible.apply_playbook')
49 self.mock_apply_playbook = patcher.start()
50 self.addCleanup(patcher.stop)
51
52 def test_default_hooks(self):
53 """Most of the hooks let ansible do all the work."""
54 for hook in ('start', 'stop', 'config-changed'):
55 self.mock_apply_playbook.reset_mock()
56
57 hooks.execute([hook])
58
59 self.assertEqual([
60 mock.call('playbooks/site.yaml',
61 tags=[hook]),
62 ], self.mock_apply_playbook.call_args_list)
063
=== added file 'charmtools/templates/ansible/template.py'
--- charmtools/templates/ansible/template.py 1970-01-01 00:00:00 +0000
+++ charmtools/templates/ansible/template.py 2014-09-09 18:56:48 +0000
@@ -0,0 +1,88 @@
1#!/usr/bin/python
2#
3# Copyright (C) 2014 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import logging
19import os
20import os.path as path
21import time
22import shutil
23import subprocess
24import tempfile
25
26from Cheetah.Template import Template
27from stat import ST_MODE
28
29from charmtools.generators import (
30 CharmTemplate,
31)
32
33log = logging.getLogger(__name__)
34
35
36class AnsibleCharmTemplate(CharmTemplate):
37 skip_parsing = ['README.ex', 'Makefile']
38
39 def create_charm(self, config, output_dir):
40 self._copy_files(output_dir)
41
42 for root, dirs, files in os.walk(output_dir):
43 for outfile in files:
44 if outfile in self.skip_parsing:
45 continue
46
47 self._template_file(config, path.join(root, outfile))
48
49 self._cleanup_hooks(config, output_dir)
50 self._install_charmhelpers(output_dir)
51
52 def _copy_files(self, output_dir):
53 here = path.abspath(path.dirname(__file__))
54 template_dir = path.join(here, 'files')
55 if os.path.exists(output_dir):
56 shutil.rmtree(output_dir)
57 shutil.copytree(template_dir, output_dir)
58
59 def _template_file(self, config, outfile):
60 if path.islink(outfile):
61 return
62
63 mode = os.stat(outfile)[ST_MODE]
64 t = Template(file=outfile, searchList=(config))
65 o = tempfile.NamedTemporaryFile(
66 dir=path.dirname(outfile), delete=False)
67 os.chmod(o.name, mode)
68 o.write(str(t))
69 o.close()
70 backupname = outfile + str(time.time())
71 os.rename(outfile, backupname)
72 os.rename(o.name, outfile)
73 os.unlink(backupname)
74
75 def _cleanup_hooks(self, config, output_dir):
76 # Symlinks must be relative so that they are copied properly
77 # when output_dir is moved to it's final location.
78 for link in ['config-changed', 'install', 'start', 'stop',
79 'upgrade-charm']:
80 os.symlink('hooks.py', os.path.join(output_dir, 'hooks', link))
81
82 def _install_charmhelpers(self, output_dir):
83 helpers_dest = os.path.join(output_dir, 'lib', 'charmhelpers')
84 if not os.path.exists(helpers_dest):
85 os.makedirs(helpers_dest)
86
87 cmd = './scripts/charm_helpers_sync.py -c charm-helpers.yaml'
88 subprocess.check_call(cmd.split(), cwd=output_dir)
089
=== removed file 'charmtools/templates/python/config.yaml'
--- charmtools/templates/python/config.yaml 2014-05-27 21:23:41 +0000
+++ charmtools/templates/python/config.yaml 1970-01-01 00:00:00 +0000
@@ -1,5 +0,0 @@
1prompts:
2 symlink:
3 prompt: Symlink all hooks to one python source file? [yN]
4 default: n
5 type: boolean
60
=== removed directory 'charmtools/templates/python/files/hooks_symlinked'
=== removed file 'charmtools/templates/python/files/hooks_symlinked/hooks.py'
--- charmtools/templates/python/files/hooks_symlinked/hooks.py 2014-05-27 21:23:41 +0000
+++ charmtools/templates/python/files/hooks_symlinked/hooks.py 1970-01-01 00:00:00 +0000
@@ -1,54 +0,0 @@
1#!/usr/bin/python
2
3import os
4import sys
5
6sys.path.insert(0, os.path.join(os.environ['CHARM_DIR'], 'lib'))
7
8from charmhelpers.core import (
9 hookenv,
10 host,
11)
12
13hooks = hookenv.Hooks()
14log = hookenv.log
15
16SERVICE = '$metadata.package'
17
18
19@hooks.hook('install')
20def install():
21 log('Installing $metadata.package')
22
23
24@hooks.hook('config-changed')
25def config_changed():
26 config = hookenv.config()
27
28 for key in config:
29 if config.changed(key):
30 log("config['{}'] changed from {} to {}".format(
31 key, config.previous(key), config[key]))
32
33 config.save()
34 start()
35
36
37@hooks.hook('upgrade-charm')
38def upgrade_charm():
39 log('Upgrading $metadata.package')
40
41
42@hooks.hook('start')
43def start():
44 host.service_restart(SERVICE) or host.service_start(SERVICE)
45
46
47@hooks.hook('stop')
48def stop():
49 host.service_stop(SERVICE)
50
51
52if __name__ == "__main__":
53 # execute a hook based on the name the program is called by
54 hooks.execute(sys.argv)
550
=== modified file 'charmtools/templates/python/template.py'
--- charmtools/templates/python/template.py 2014-07-29 18:15:41 +0000
+++ charmtools/templates/python/template.py 2014-09-09 18:56:48 +0000
@@ -46,7 +46,6 @@
4646
47 self._template_file(config, path.join(root, outfile))47 self._template_file(config, path.join(root, outfile))
4848
49 self._cleanup_hooks(config, output_dir)
50 self._install_charmhelpers(output_dir)49 self._install_charmhelpers(output_dir)
5150
52 def _copy_files(self, output_dir):51 def _copy_files(self, output_dir):
@@ -72,21 +71,6 @@
72 os.rename(o.name, outfile)71 os.rename(o.name, outfile)
73 os.unlink(backupname)72 os.unlink(backupname)
7473
75 def _cleanup_hooks(self, config, output_dir):
76 rmdir = 'hooks' if config['symlink'] else 'hooks_symlinked'
77 shutil.rmtree(os.path.join(output_dir, rmdir))
78
79 if config['symlink']:
80 os.rename(
81 os.path.join(output_dir, 'hooks_symlinked'),
82 os.path.join(output_dir, 'hooks')
83 )
84 # Symlinks must be relative so that they are copied properly
85 # when output_dir is moved to it's final location.
86 for link in ['config-changed', 'install', 'start', 'stop',
87 'upgrade-charm']:
88 os.symlink('hooks.py', os.path.join(output_dir, 'hooks', link))
89
90 def _install_charmhelpers(self, output_dir):74 def _install_charmhelpers(self, output_dir):
91 helpers_dest = os.path.join(output_dir, 'lib', 'charmhelpers')75 helpers_dest = os.path.join(output_dir, 'lib', 'charmhelpers')
92 if not os.path.exists(helpers_dest):76 if not os.path.exists(helpers_dest):
9377
=== modified file 'setup.py'
--- setup.py 2014-06-23 14:44:59 +0000
+++ setup.py 2014-09-09 18:56:48 +0000
@@ -58,6 +58,7 @@
58 'charmtools.templates': [58 'charmtools.templates': [
59 'bash = charmtools.templates.bash:BashCharmTemplate',59 'bash = charmtools.templates.bash:BashCharmTemplate',
60 'python = charmtools.templates.python:PythonCharmTemplate',60 'python = charmtools.templates.python:PythonCharmTemplate',
61 'ansible = charmtools.templates.ansible:AnsibleCharmTemplate',
61 ]62 ]
62 },63 },
63)64)
6465
=== modified file 'tests/test_charm_proof.py'
--- tests/test_charm_proof.py 2014-06-17 15:44:04 +0000
+++ tests/test_charm_proof.py 2014-09-09 18:56:48 +0000
@@ -420,7 +420,10 @@
420 """Maintainers field happy path."""420 """Maintainers field happy path."""
421 linter = Mock()421 linter = Mock()
422 charm = {422 charm = {
423 'maintainers': ['Tester <tester@example.com>'],423 'maintainers': [
424 'Tester <tester@example.com>',
425 'Tester Joe H. <tester@example.com>',
426 ]
424 }427 }
425 validate_maintainer(charm, linter)428 validate_maintainer(charm, linter)
426 self.assertFalse(linter.err.called)429 self.assertFalse(linter.err.called)
427430
=== modified file 'tests_functional/create/test.sh'
--- tests_functional/create/test.sh 2014-06-12 00:21:24 +0000
+++ tests_functional/create/test.sh 2014-09-09 18:56:48 +0000
@@ -52,6 +52,9 @@
52echo ===== Testing python charm template =====52echo ===== Testing python charm template =====
53$PYTHON $TESTDIR/test_python_create.py53$PYTHON $TESTDIR/test_python_create.py
5454
55echo ===== Testing ansible charm template =====
56$PYTHON $TESTDIR/test_ansible_create.py
57
55echo ===== All tests passed! =====58echo ===== All tests passed! =====
56echo PASS59echo PASS
57set +e60set +e
5861
=== added file 'tests_functional/create/test_ansible_create.py'
--- tests_functional/create/test_ansible_create.py 1970-01-01 00:00:00 +0000
+++ tests_functional/create/test_ansible_create.py 2014-09-09 18:56:48 +0000
@@ -0,0 +1,101 @@
1#!/usr/bin/python
2
3# Copyright (C) 2014 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import os
19import shutil
20import tempfile
21import unittest
22
23from mock import patch
24from os.path import join
25from unittest import TestCase
26
27import pkg_resources
28import yaml
29
30from charmtools.create import (
31 main,
32)
33
34
35def flatten(path):
36 for root, dirs, files in os.walk(path):
37 for f in files:
38 yield join(root[len(path):], f).lstrip('/')
39
40
41class AnsibleCreateTest(TestCase):
42 maxDiff = None
43 def setUp(self):
44 self.tempdir = tempfile.mkdtemp()
45
46 def tearDown(self):
47 shutil.rmtree(self.tempdir)
48
49 def _expected_files(self):
50 static_files = list(flatten(pkg_resources.resource_filename(
51 'charmtools', 'templates/ansible/files')))
52 dynamic_files = [
53 'hooks/config-changed',
54 'hooks/install',
55 'hooks/start',
56 'hooks/stop',
57 'hooks/upgrade-charm',
58 'lib/charmhelpers/__init__.py',
59 'lib/charmhelpers/contrib/__init__.py',
60 'lib/charmhelpers/contrib/ansible/__init__.py',
61 'lib/charmhelpers/contrib/templating/__init__.py',
62 'lib/charmhelpers/contrib/templating/contexts.py',
63 'lib/charmhelpers/core/__init__.py',
64 'lib/charmhelpers/core/fstab.py',
65 'lib/charmhelpers/core/hookenv.py',
66 'lib/charmhelpers/core/host.py',
67 'lib/charmhelpers/core/services/__init__.py',
68 'lib/charmhelpers/core/services/base.py',
69 'lib/charmhelpers/core/services/helpers.py',
70 'lib/charmhelpers/core/templating.py',
71 'lib/charmhelpers/fetch/__init__.py',
72 'lib/charmhelpers/fetch/archiveurl.py',
73 'lib/charmhelpers/fetch/bzrurl.py',
74 ]
75 return sorted(static_files + dynamic_files)
76
77 @patch('charmtools.create.setup_parser')
78 def test_default(self, setup_parser):
79 """Test of `charm create -t ansible testcharm`"""
80 class args(object):
81 charmname = 'testcharm'
82 charmhome = self.tempdir
83 template = 'ansible'
84 accept_defaults = True
85 verbose = False
86
87 setup_parser.return_value.parse_args.return_value = args
88
89 main()
90
91 outputdir = join(self.tempdir, args.charmname)
92 actual_files = sorted(flatten(outputdir))
93 expected_files = self._expected_files()
94 metadata = yaml.load(open(join(outputdir, 'metadata.yaml'), 'r'))
95
96 self.assertEqual(expected_files, actual_files)
97 self.assertEqual(metadata['name'], args.charmname)
98
99
100if __name__ == '__main__':
101 unittest.main()
0102
=== modified file 'tests_functional/create/test_python_create.py'
--- tests_functional/create/test_python_create.py 2014-06-20 19:46:59 +0000
+++ tests_functional/create/test_python_create.py 2014-09-09 18:56:48 +0000
@@ -45,46 +45,22 @@
45 def tearDown(self):45 def tearDown(self):
46 shutil.rmtree(self.tempdir)46 shutil.rmtree(self.tempdir)
4747
48 def _expected_files(self, symlinked=False):48 def _expected_files(self):
49 static_files = list(flatten(pkg_resources.resource_filename(49 static_files = list(flatten(pkg_resources.resource_filename(
50 'charmtools', 'templates/python/files')))50 'charmtools', 'templates/python/files')))
51 static_files = [f for f in static_files
52 if not f.startswith('hooks_symlinked/')]
53 if symlinked:
54 static_files.append('hooks/hooks.py')
55 dynamic_files = [51 dynamic_files = [
56 'lib/charmhelpers/__init__.py',52 'lib/charmhelpers/__init__.py',
57 'lib/charmhelpers/core/__init__.py',53 'lib/charmhelpers/core/__init__.py',
58 'lib/charmhelpers/core/fstab.py',54 'lib/charmhelpers/core/fstab.py',
59 'lib/charmhelpers/core/hookenv.py',55 'lib/charmhelpers/core/hookenv.py',
60 'lib/charmhelpers/core/host.py',56 'lib/charmhelpers/core/host.py',
57 'lib/charmhelpers/core/services/__init__.py',
58 'lib/charmhelpers/core/services/base.py',
59 'lib/charmhelpers/core/services/helpers.py',
60 'lib/charmhelpers/core/templating.py',
61 ]61 ]
62 return sorted(static_files + dynamic_files)62 return sorted(static_files + dynamic_files)
6363
64 @patch('__builtin__.raw_input')
65 @patch('charmtools.create.setup_parser')
66 def test_interactive(self, setup_parser, raw_input_):
67 """Functional test of a full 'charm create' run."""
68 class args(object):
69 charmname = 'testcharm'
70 charmhome = self.tempdir
71 template = 'python'
72 accept_defaults = False
73 verbose = False
74
75 setup_parser.return_value.parse_args.return_value = args
76 raw_input_.side_effect = ['Y']
77
78 main()
79
80 outputdir = join(self.tempdir, args.charmname)
81 actual_files = sorted(flatten(outputdir))
82 expected_files = self._expected_files(symlinked=True)
83 metadata = yaml.load(open(join(outputdir, 'metadata.yaml'), 'r'))
84
85 self.assertEqual(expected_files, actual_files)
86 self.assertEqual(metadata['name'], args.charmname)
87
88 @patch('charmtools.create.setup_parser')64 @patch('charmtools.create.setup_parser')
89 def test_defaults(self, setup_parser):65 def test_defaults(self, setup_parser):
90 """Functional test of a full 'charm create' run."""66 """Functional test of a full 'charm create' run."""

Subscribers

People subscribed via source and target branches