Merge lp:~michael.nelson/charm-helpers/ansible-tags into lp:charm-helpers
- ansible-tags
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Michael Nelson |
Approved revision: | 100 |
Merged at revision: | 98 |
Proposed branch: | lp:~michael.nelson/charm-helpers/ansible-tags |
Merge into: | lp:charm-helpers |
Diff against target: |
627 lines (+332/-208) 7 files modified
charmhelpers/contrib/ansible/__init__.py (+66/-3) charmhelpers/contrib/saltstack/__init__.py (+3/-50) charmhelpers/contrib/templating/contexts.py (+64/-0) setup.py (+1/-0) tests/contrib/ansible/test_ansible.py (+30/-0) tests/contrib/saltstack/test_saltstates.py (+1/-155) tests/contrib/templating/test_contexts.py (+167/-0) |
To merge this branch: | bzr merge lp:~michael.nelson/charm-helpers/ansible-tags |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
James Page | Approve | ||
Review via email: mp+194324@code.launchpad.net |
Commit message
Add tags to the ansible support and provide AnsibleHooks() helper.
Description of the change
This branch:
* adds the ability to specify tags when playing an ansible playbook
* adds an AnsibleHooks helper which can be used instead of the hookenv.Hooks helper, which works just like the hookenv.Hooks but additionally plays the configured playbook using the hook name as a tag, like this:
{{{
# Create the hooks helper, passing a list of hooks which will be
# handled by default by running all the sections of the playbook
# tagged with the hook name.
hooks = charmhelpers.
playbook_
default_hooks=[
'start',
'stop',
])
# All the tasks within the specified playbook tagged with 'install'
# will be run automatically after the call to install_
@hooks.hook()
def install():
charmhelper
}}}
minimizing the amount of procedural code in hooks.py to be tested and maintained.
* then a big chunk is just moving the juju_state_to_yaml helper and its tests out of saltstack (recommendation from last MP, as it's now used by both the saltstack and ansible helpers).
See line 534 of the (private, sorry) MP diff here for an example of the ansible hooks being used [1]
[1] https:/
Preview Diff
1 | === modified file 'charmhelpers/contrib/ansible/__init__.py' |
2 | --- charmhelpers/contrib/ansible/__init__.py 2013-08-21 09:10:58 +0000 |
3 | +++ charmhelpers/contrib/ansible/__init__.py 2013-11-07 14:38:44 +0000 |
4 | @@ -95,7 +95,70 @@ |
5 | hosts_file.write('localhost ansible_connection=local') |
6 | |
7 | |
8 | -def apply_playbook(playbook): |
9 | - charmhelpers.contrib.saltstack.juju_state_to_yaml( |
10 | +def apply_playbook(playbook, tags=None): |
11 | + tags = tags or [] |
12 | + tags = ",".join(tags) |
13 | + charmhelpers.contrib.templating.contexts.juju_state_to_yaml( |
14 | ansible_vars_path, namespace_separator='__') |
15 | - subprocess.check_call(['ansible-playbook', '-c', 'local', playbook]) |
16 | + call = [ |
17 | + 'ansible-playbook', |
18 | + '-c', |
19 | + 'local', |
20 | + playbook, |
21 | + ] |
22 | + if tags: |
23 | + call.extend(['--tags', '{}'.format(tags)]) |
24 | + subprocess.check_call(call) |
25 | + |
26 | + |
27 | +class AnsibleHooks(charmhelpers.core.hookenv.Hooks): |
28 | + """Run a playbook with the hook-name as the tag. |
29 | + |
30 | + This helper builds on the standard hookenv.Hooks helper, |
31 | + but additionally runs the playbook with the hook-name specified |
32 | + using --tags (ie. running all the tasks tagged with the hook-name). |
33 | + |
34 | + Example: |
35 | + hooks = AnsibleHooks(playbook_path='playbooks/my_machine_state.yaml') |
36 | + |
37 | + # All the tasks within my_machine_state.yaml tagged with 'install' |
38 | + # will be run automatically after do_custom_work() |
39 | + @hooks.hook() |
40 | + def install(): |
41 | + do_custom_work() |
42 | + |
43 | + # For most of your hooks, you won't need to do anything other |
44 | + # than run the tagged tasks for the hook: |
45 | + @hooks.hook('config-changed', 'start', 'stop') |
46 | + def just_use_playbook(): |
47 | + pass |
48 | + |
49 | + # As a convenience, you can avoid the above noop function by specifying |
50 | + # the hooks which are handled by ansible-only and they'll be registered |
51 | + # for you: |
52 | + # hooks = AnsibleHooks( |
53 | + # 'playbooks/my_machine_state.yaml', |
54 | + # default_hooks=['config-changed', 'start', 'stop']) |
55 | + |
56 | + if __name__ == "__main__": |
57 | + # execute a hook based on the name the program is called by |
58 | + hooks.execute(sys.argv) |
59 | + """ |
60 | + |
61 | + def __init__(self, playbook_path, default_hooks=None): |
62 | + """Register any hooks handled by ansible.""" |
63 | + super(AnsibleHooks, self).__init__() |
64 | + |
65 | + self.playbook_path = playbook_path |
66 | + |
67 | + default_hooks = default_hooks or [] |
68 | + noop = lambda *args, **kwargs: None |
69 | + for hook in default_hooks: |
70 | + self.register(hook, noop) |
71 | + |
72 | + def execute(self, args): |
73 | + """Execute the hook followed by the playbook using the hook as tag.""" |
74 | + super(AnsibleHooks, self).execute(args) |
75 | + hook_name = os.path.basename(args[0]) |
76 | + charmhelpers.contrib.ansible.apply_playbook( |
77 | + self.playbook_path, tags=[hook_name]) |
78 | |
79 | === modified file 'charmhelpers/contrib/saltstack/__init__.py' |
80 | --- charmhelpers/contrib/saltstack/__init__.py 2013-08-07 23:14:14 +0000 |
81 | +++ charmhelpers/contrib/saltstack/__init__.py 2013-11-07 14:38:44 +0000 |
82 | @@ -61,15 +61,13 @@ |
83 | # |
84 | # Authors: |
85 | # Charm Helpers Developers <juju@lists.ubuntu.com> |
86 | -import os |
87 | import subprocess |
88 | -import yaml |
89 | |
90 | +import charmhelpers.contrib.templating.contexts |
91 | import charmhelpers.core.host |
92 | import charmhelpers.core.hookenv |
93 | |
94 | |
95 | -charm_dir = os.environ.get('CHARM_DIR', '') |
96 | salt_grains_path = '/etc/salt/grains' |
97 | |
98 | |
99 | @@ -94,56 +92,11 @@ |
100 | |
101 | def update_machine_state(state_path): |
102 | """Update the machine state using the provided state declaration.""" |
103 | - juju_state_to_yaml(salt_grains_path) |
104 | + charmhelpers.contrib.templating.contexts.juju_state_to_yaml( |
105 | + salt_grains_path) |
106 | subprocess.check_call([ |
107 | 'salt-call', |
108 | '--local', |
109 | 'state.template', |
110 | state_path, |
111 | ]) |
112 | - |
113 | - |
114 | -def juju_state_to_yaml(yaml_path, namespace_separator=':'): |
115 | - """Update the juju config and state in a yaml file. |
116 | - |
117 | - This includes any current relation-get data, and the charm |
118 | - directory. |
119 | - """ |
120 | - config = charmhelpers.core.hookenv.config() |
121 | - |
122 | - # Add the charm_dir which we will need to refer to charm |
123 | - # file resources etc. |
124 | - config['charm_dir'] = charm_dir |
125 | - config['local_unit'] = charmhelpers.core.hookenv.local_unit() |
126 | - |
127 | - # Add any relation data prefixed with the relation type. |
128 | - relation_type = charmhelpers.core.hookenv.relation_type() |
129 | - if relation_type is not None: |
130 | - relation_data = charmhelpers.core.hookenv.relation_get() |
131 | - relation_data = dict( |
132 | - ("{relation_type}{namespace_separator}{key}".format( |
133 | - relation_type=relation_type.replace('-', '_'), |
134 | - key=key, |
135 | - namespace_separator=namespace_separator), val) |
136 | - for key, val in relation_data.items()) |
137 | - config.update(relation_data) |
138 | - |
139 | - # Don't use non-standard tags for unicode which will not |
140 | - # work when salt uses yaml.load_safe. |
141 | - yaml.add_representer(unicode, lambda dumper, |
142 | - value: dumper.represent_scalar( |
143 | - u'tag:yaml.org,2002:str', value)) |
144 | - |
145 | - yaml_dir = os.path.dirname(yaml_path) |
146 | - if not os.path.exists(yaml_dir): |
147 | - os.makedirs(yaml_dir) |
148 | - |
149 | - if os.path.exists(yaml_path): |
150 | - with open(yaml_path, "r") as existing_vars_file: |
151 | - existing_vars = yaml.load(existing_vars_file.read()) |
152 | - else: |
153 | - existing_vars = {} |
154 | - |
155 | - existing_vars.update(config) |
156 | - with open(yaml_path, "w+") as fp: |
157 | - fp.write(yaml.dump(existing_vars)) |
158 | |
159 | === added file 'charmhelpers/contrib/templating/contexts.py' |
160 | --- charmhelpers/contrib/templating/contexts.py 1970-01-01 00:00:00 +0000 |
161 | +++ charmhelpers/contrib/templating/contexts.py 2013-11-07 14:38:44 +0000 |
162 | @@ -0,0 +1,64 @@ |
163 | +# Copyright 2013 Canonical Ltd. |
164 | +# |
165 | +# Authors: |
166 | +# Charm Helpers Developers <juju@lists.ubuntu.com> |
167 | +"""A helper to create a yaml cache of config with namespaced relation data.""" |
168 | +import os |
169 | +import yaml |
170 | + |
171 | +import charmhelpers.core.hookenv |
172 | + |
173 | + |
174 | +charm_dir = os.environ.get('CHARM_DIR', '') |
175 | + |
176 | + |
177 | +def juju_state_to_yaml(yaml_path, namespace_separator=':'): |
178 | + """Update the juju config and state in a yaml file. |
179 | + |
180 | + This includes any current relation-get data, and the charm |
181 | + directory. |
182 | + |
183 | + This function was created for the ansible and saltstack |
184 | + support, as those libraries can use a yaml file to supply |
185 | + context to templates, but it may be useful generally to |
186 | + create and update an on-disk cache of all the config, including |
187 | + previous relation data. |
188 | + """ |
189 | + config = charmhelpers.core.hookenv.config() |
190 | + |
191 | + # Add the charm_dir which we will need to refer to charm |
192 | + # file resources etc. |
193 | + config['charm_dir'] = charm_dir |
194 | + config['local_unit'] = charmhelpers.core.hookenv.local_unit() |
195 | + |
196 | + # Add any relation data prefixed with the relation type. |
197 | + relation_type = charmhelpers.core.hookenv.relation_type() |
198 | + if relation_type is not None: |
199 | + relation_data = charmhelpers.core.hookenv.relation_get() |
200 | + relation_data = dict( |
201 | + ("{relation_type}{namespace_separator}{key}".format( |
202 | + relation_type=relation_type.replace('-', '_'), |
203 | + key=key, |
204 | + namespace_separator=namespace_separator), val) |
205 | + for key, val in relation_data.items()) |
206 | + config.update(relation_data) |
207 | + |
208 | + # Don't use non-standard tags for unicode which will not |
209 | + # work when salt uses yaml.load_safe. |
210 | + yaml.add_representer(unicode, lambda dumper, |
211 | + value: dumper.represent_scalar( |
212 | + u'tag:yaml.org,2002:str', value)) |
213 | + |
214 | + yaml_dir = os.path.dirname(yaml_path) |
215 | + if not os.path.exists(yaml_dir): |
216 | + os.makedirs(yaml_dir) |
217 | + |
218 | + if os.path.exists(yaml_path): |
219 | + with open(yaml_path, "r") as existing_vars_file: |
220 | + existing_vars = yaml.load(existing_vars_file.read()) |
221 | + else: |
222 | + existing_vars = {} |
223 | + |
224 | + existing_vars.update(config) |
225 | + with open(yaml_path, "w+") as fp: |
226 | + fp.write(yaml.dump(existing_vars)) |
227 | |
228 | === modified file 'setup.py' |
229 | --- setup.py 2013-08-01 01:40:14 +0000 |
230 | +++ setup.py 2013-11-07 14:38:44 +0000 |
231 | @@ -27,6 +27,7 @@ |
232 | "charmhelpers.contrib.saltstack", |
233 | "charmhelpers.contrib.hahelpers", |
234 | "charmhelpers.contrib.jujugui", |
235 | + "charmhelpers.contrib.templating", |
236 | ], |
237 | 'scripts': [ |
238 | "bin/chlp", |
239 | |
240 | === modified file 'tests/contrib/ansible/test_ansible.py' |
241 | --- tests/contrib/ansible/test_ansible.py 2013-08-21 09:10:58 +0000 |
242 | +++ tests/contrib/ansible/test_ansible.py 2013-11-07 14:38:44 +0000 |
243 | @@ -132,3 +132,33 @@ |
244 | "wsgi_file__relation_key1": "relation_value1", |
245 | "wsgi_file__relation_key2": "relation_value2", |
246 | }, result) |
247 | + |
248 | + def test_calls_with_tags(self): |
249 | + charmhelpers.contrib.ansible.apply_playbook( |
250 | + 'playbooks/complete-state.yaml', tags=['install', 'somethingelse']) |
251 | + |
252 | + self.mock_subprocess.check_call.assert_called_once_with([ |
253 | + 'ansible-playbook', '-c', 'local', 'playbooks/complete-state.yaml', |
254 | + '--tags', 'install,somethingelse' ]) |
255 | + |
256 | + def test_hooks_executes_playbook_with_tag(self): |
257 | + hooks = charmhelpers.contrib.ansible.AnsibleHooks('my/playbook.yaml') |
258 | + foo = mock.MagicMock() |
259 | + hooks.register('foo', foo) |
260 | + |
261 | + hooks.execute(['foo']) |
262 | + |
263 | + self.assertEqual(foo.call_count, 1) |
264 | + self.mock_subprocess.check_call.assert_called_once_with([ |
265 | + 'ansible-playbook', '-c', 'local', 'my/playbook.yaml', |
266 | + '--tags', 'foo' ]) |
267 | + |
268 | + def test_specifying_ansible_handled_hooks(self): |
269 | + hooks = charmhelpers.contrib.ansible.AnsibleHooks( |
270 | + 'my/playbook.yaml', default_hooks=['start', 'stop']) |
271 | + |
272 | + hooks.execute(['start']) |
273 | + |
274 | + self.mock_subprocess.check_call.assert_called_once_with([ |
275 | + 'ansible-playbook', '-c', 'local', 'my/playbook.yaml', |
276 | + '--tags', 'start' ]) |
277 | |
278 | === modified file 'tests/contrib/saltstack/test_saltstates.py' |
279 | --- tests/contrib/saltstack/test_saltstates.py 2013-08-21 09:10:58 +0000 |
280 | +++ tests/contrib/saltstack/test_saltstates.py 2013-11-07 14:38:44 +0000 |
281 | @@ -3,11 +3,7 @@ |
282 | # Authors: |
283 | # Charm Helpers Developers <juju@lists.ubuntu.com> |
284 | import mock |
285 | -import os |
286 | -import shutil |
287 | -import tempfile |
288 | import unittest |
289 | -import yaml |
290 | |
291 | import charmhelpers.contrib.saltstack |
292 | |
293 | @@ -56,7 +52,7 @@ |
294 | self.mock_subprocess = patcher.start() |
295 | self.addCleanup(patcher.stop) |
296 | |
297 | - patcher = mock.patch('charmhelpers.contrib.saltstack.' |
298 | + patcher = mock.patch('charmhelpers.contrib.templating.contexts.' |
299 | 'juju_state_to_yaml') |
300 | self.mock_config_2_grains = patcher.start() |
301 | self.addCleanup(patcher.stop) |
302 | @@ -77,153 +73,3 @@ |
303 | 'states/install.yaml') |
304 | |
305 | self.mock_config_2_grains.assert_called_once_with('/etc/salt/grains') |
306 | - |
307 | - |
308 | -class JujuConfig2GrainsTestCase(unittest.TestCase): |
309 | - def setUp(self): |
310 | - super(JujuConfig2GrainsTestCase, self).setUp() |
311 | - |
312 | - # Hookenv patches (a single patch to hookenv doesn't work): |
313 | - patcher = mock.patch('charmhelpers.core.hookenv.config') |
314 | - self.mock_config = patcher.start() |
315 | - self.addCleanup(patcher.stop) |
316 | - patcher = mock.patch('charmhelpers.core.hookenv.relation_get') |
317 | - self.mock_relation_get = patcher.start() |
318 | - self.mock_relation_get.return_value = {} |
319 | - self.addCleanup(patcher.stop) |
320 | - patcher = mock.patch('charmhelpers.core.hookenv.relation_type') |
321 | - self.mock_relation_type = patcher.start() |
322 | - self.mock_relation_type.return_value = None |
323 | - self.addCleanup(patcher.stop) |
324 | - patcher = mock.patch('charmhelpers.core.hookenv.local_unit') |
325 | - self.mock_local_unit = patcher.start() |
326 | - self.addCleanup(patcher.stop) |
327 | - |
328 | - # patches specific to this test class. |
329 | - etc_dir = tempfile.mkdtemp() |
330 | - self.addCleanup(shutil.rmtree, etc_dir) |
331 | - self.grain_path = os.path.join(etc_dir, 'salt', 'grains') |
332 | - |
333 | - patcher = mock.patch.object(charmhelpers.contrib.saltstack, |
334 | - 'charm_dir', '/tmp/charm_dir') |
335 | - patcher.start() |
336 | - self.addCleanup(patcher.stop) |
337 | - |
338 | - def test_output_with_empty_relation(self): |
339 | - self.mock_config.return_value = { |
340 | - 'group_code_owner': 'webops_deploy', |
341 | - 'user_code_runner': 'ubunet', |
342 | - } |
343 | - self.mock_local_unit.return_value = "click-index/3" |
344 | - |
345 | - charmhelpers.contrib.saltstack.juju_state_to_yaml(self.grain_path) |
346 | - |
347 | - with open(self.grain_path, 'r') as grain_file: |
348 | - result = yaml.load(grain_file.read()) |
349 | - self.assertEqual({ |
350 | - "charm_dir": "/tmp/charm_dir", |
351 | - "group_code_owner": "webops_deploy", |
352 | - "user_code_runner": "ubunet", |
353 | - "local_unit": "click-index/3", |
354 | - }, result) |
355 | - |
356 | - def test_output_with_no_relation(self): |
357 | - self.mock_config.return_value = { |
358 | - 'group_code_owner': 'webops_deploy', |
359 | - 'user_code_runner': 'ubunet', |
360 | - } |
361 | - self.mock_local_unit.return_value = "click-index/3" |
362 | - self.mock_relation_get.return_value = None |
363 | - |
364 | - charmhelpers.contrib.saltstack.juju_state_to_yaml(self.grain_path) |
365 | - |
366 | - with open(self.grain_path, 'r') as grain_file: |
367 | - result = yaml.load(grain_file.read()) |
368 | - self.assertEqual({ |
369 | - "charm_dir": "/tmp/charm_dir", |
370 | - "group_code_owner": "webops_deploy", |
371 | - "user_code_runner": "ubunet", |
372 | - "local_unit": "click-index/3", |
373 | - }, result) |
374 | - |
375 | - def test_output_with_relation(self): |
376 | - self.mock_config.return_value = { |
377 | - 'group_code_owner': 'webops_deploy', |
378 | - 'user_code_runner': 'ubunet', |
379 | - } |
380 | - self.mock_relation_type.return_value = 'wsgi-file' |
381 | - self.mock_relation_get.return_value = { |
382 | - 'relation_key1': 'relation_value1', |
383 | - 'relation_key2': 'relation_value2', |
384 | - } |
385 | - self.mock_local_unit.return_value = "click-index/3" |
386 | - |
387 | - charmhelpers.contrib.saltstack.juju_state_to_yaml(self.grain_path) |
388 | - |
389 | - with open(self.grain_path, 'r') as grain_file: |
390 | - result = yaml.load(grain_file.read()) |
391 | - self.assertEqual({ |
392 | - "charm_dir": "/tmp/charm_dir", |
393 | - "group_code_owner": "webops_deploy", |
394 | - "user_code_runner": "ubunet", |
395 | - "wsgi_file:relation_key1": "relation_value1", |
396 | - "wsgi_file:relation_key2": "relation_value2", |
397 | - "local_unit": "click-index/3", |
398 | - }, result) |
399 | - |
400 | - def test_relation_with_separator(self): |
401 | - self.mock_config.return_value = { |
402 | - 'group_code_owner': 'webops_deploy', |
403 | - 'user_code_runner': 'ubunet', |
404 | - } |
405 | - self.mock_relation_type.return_value = 'wsgi-file' |
406 | - self.mock_relation_get.return_value = { |
407 | - 'relation_key1': 'relation_value1', |
408 | - 'relation_key2': 'relation_value2', |
409 | - } |
410 | - self.mock_local_unit.return_value = "click-index/3" |
411 | - |
412 | - charmhelpers.contrib.saltstack.juju_state_to_yaml( |
413 | - self.grain_path, namespace_separator='__') |
414 | - |
415 | - with open(self.grain_path, 'r') as grain_file: |
416 | - result = yaml.load(grain_file.read()) |
417 | - self.assertEqual({ |
418 | - "charm_dir": "/tmp/charm_dir", |
419 | - "group_code_owner": "webops_deploy", |
420 | - "user_code_runner": "ubunet", |
421 | - "wsgi_file__relation_key1": "relation_value1", |
422 | - "wsgi_file__relation_key2": "relation_value2", |
423 | - "local_unit": "click-index/3", |
424 | - }, result) |
425 | - |
426 | - def test_updates_existing_values(self): |
427 | - """Data stored in grains is retained. |
428 | - |
429 | - This may be helpful so that templates can access information |
430 | - from relations outside the current context. |
431 | - """ |
432 | - os.makedirs(os.path.dirname(self.grain_path)) |
433 | - with open(self.grain_path, 'w+') as grain_file: |
434 | - grain_file.write(yaml.dump({ |
435 | - 'solr:hostname': 'example.com', |
436 | - 'user_code_runner': 'oldvalue', |
437 | - })) |
438 | - |
439 | - self.mock_config.return_value = charmhelpers.core.hookenv.Serializable({ |
440 | - 'group_code_owner': 'webops_deploy', |
441 | - 'user_code_runner': 'newvalue', |
442 | - }) |
443 | - self.mock_local_unit.return_value = "click-index/3" |
444 | - |
445 | - charmhelpers.contrib.saltstack.juju_state_to_yaml(self.grain_path) |
446 | - |
447 | - with open(self.grain_path, 'r') as grain_file: |
448 | - result = yaml.load(grain_file.read()) |
449 | - self.assertEqual({ |
450 | - "charm_dir": "/tmp/charm_dir", |
451 | - "group_code_owner": "webops_deploy", |
452 | - "user_code_runner": "newvalue", |
453 | - "local_unit": "click-index/3", |
454 | - "solr:hostname": "example.com", |
455 | - }, result) |
456 | |
457 | === added file 'tests/contrib/templating/test_contexts.py' |
458 | --- tests/contrib/templating/test_contexts.py 1970-01-01 00:00:00 +0000 |
459 | +++ tests/contrib/templating/test_contexts.py 2013-11-07 14:38:44 +0000 |
460 | @@ -0,0 +1,167 @@ |
461 | +# Copyright 2013 Canonical Ltd. |
462 | +# |
463 | +# Authors: |
464 | +# Charm Helpers Developers <juju@lists.ubuntu.com> |
465 | +import mock |
466 | +import os |
467 | +import shutil |
468 | +import tempfile |
469 | +import unittest |
470 | +import yaml |
471 | + |
472 | +import charmhelpers.contrib.templating |
473 | + |
474 | + |
475 | +class JujuState2YamlTestCase(unittest.TestCase): |
476 | + def setUp(self): |
477 | + super(JujuState2YamlTestCase, self).setUp() |
478 | + |
479 | + # Hookenv patches (a single patch to hookenv doesn't work): |
480 | + patcher = mock.patch('charmhelpers.core.hookenv.config') |
481 | + self.mock_config = patcher.start() |
482 | + self.addCleanup(patcher.stop) |
483 | + patcher = mock.patch('charmhelpers.core.hookenv.relation_get') |
484 | + self.mock_relation_get = patcher.start() |
485 | + self.mock_relation_get.return_value = {} |
486 | + self.addCleanup(patcher.stop) |
487 | + patcher = mock.patch('charmhelpers.core.hookenv.relation_type') |
488 | + self.mock_relation_type = patcher.start() |
489 | + self.mock_relation_type.return_value = None |
490 | + self.addCleanup(patcher.stop) |
491 | + patcher = mock.patch('charmhelpers.core.hookenv.local_unit') |
492 | + self.mock_local_unit = patcher.start() |
493 | + self.addCleanup(patcher.stop) |
494 | + |
495 | + # patches specific to this test class. |
496 | + etc_dir = tempfile.mkdtemp() |
497 | + self.addCleanup(shutil.rmtree, etc_dir) |
498 | + self.context_path = os.path.join(etc_dir, 'some', 'context') |
499 | + |
500 | + patcher = mock.patch.object(charmhelpers.contrib.templating.contexts, |
501 | + 'charm_dir', '/tmp/charm_dir') |
502 | + patcher.start() |
503 | + self.addCleanup(patcher.stop) |
504 | + |
505 | + def test_output_with_empty_relation(self): |
506 | + self.mock_config.return_value = { |
507 | + 'group_code_owner': 'webops_deploy', |
508 | + 'user_code_runner': 'ubunet', |
509 | + } |
510 | + self.mock_local_unit.return_value = "click-index/3" |
511 | + |
512 | + charmhelpers.contrib.templating.contexts.juju_state_to_yaml( |
513 | + self.context_path) |
514 | + |
515 | + with open(self.context_path, 'r') as context_file: |
516 | + result = yaml.load(context_file.read()) |
517 | + self.assertEqual({ |
518 | + "charm_dir": "/tmp/charm_dir", |
519 | + "group_code_owner": "webops_deploy", |
520 | + "user_code_runner": "ubunet", |
521 | + "local_unit": "click-index/3", |
522 | + }, result) |
523 | + |
524 | + def test_output_with_no_relation(self): |
525 | + self.mock_config.return_value = { |
526 | + 'group_code_owner': 'webops_deploy', |
527 | + 'user_code_runner': 'ubunet', |
528 | + } |
529 | + self.mock_local_unit.return_value = "click-index/3" |
530 | + self.mock_relation_get.return_value = None |
531 | + |
532 | + charmhelpers.contrib.templating.contexts.juju_state_to_yaml( |
533 | + self.context_path) |
534 | + |
535 | + with open(self.context_path, 'r') as context_file: |
536 | + result = yaml.load(context_file.read()) |
537 | + self.assertEqual({ |
538 | + "charm_dir": "/tmp/charm_dir", |
539 | + "group_code_owner": "webops_deploy", |
540 | + "user_code_runner": "ubunet", |
541 | + "local_unit": "click-index/3", |
542 | + }, result) |
543 | + |
544 | + def test_output_with_relation(self): |
545 | + self.mock_config.return_value = { |
546 | + 'group_code_owner': 'webops_deploy', |
547 | + 'user_code_runner': 'ubunet', |
548 | + } |
549 | + self.mock_relation_type.return_value = 'wsgi-file' |
550 | + self.mock_relation_get.return_value = { |
551 | + 'relation_key1': 'relation_value1', |
552 | + 'relation_key2': 'relation_value2', |
553 | + } |
554 | + self.mock_local_unit.return_value = "click-index/3" |
555 | + |
556 | + charmhelpers.contrib.templating.contexts.juju_state_to_yaml( |
557 | + self.context_path) |
558 | + |
559 | + with open(self.context_path, 'r') as context_file: |
560 | + result = yaml.load(context_file.read()) |
561 | + self.assertEqual({ |
562 | + "charm_dir": "/tmp/charm_dir", |
563 | + "group_code_owner": "webops_deploy", |
564 | + "user_code_runner": "ubunet", |
565 | + "wsgi_file:relation_key1": "relation_value1", |
566 | + "wsgi_file:relation_key2": "relation_value2", |
567 | + "local_unit": "click-index/3", |
568 | + }, result) |
569 | + |
570 | + def test_relation_with_separator(self): |
571 | + self.mock_config.return_value = { |
572 | + 'group_code_owner': 'webops_deploy', |
573 | + 'user_code_runner': 'ubunet', |
574 | + } |
575 | + self.mock_relation_type.return_value = 'wsgi-file' |
576 | + self.mock_relation_get.return_value = { |
577 | + 'relation_key1': 'relation_value1', |
578 | + 'relation_key2': 'relation_value2', |
579 | + } |
580 | + self.mock_local_unit.return_value = "click-index/3" |
581 | + |
582 | + charmhelpers.contrib.templating.contexts.juju_state_to_yaml( |
583 | + self.context_path, namespace_separator='__') |
584 | + |
585 | + with open(self.context_path, 'r') as context_file: |
586 | + result = yaml.load(context_file.read()) |
587 | + self.assertEqual({ |
588 | + "charm_dir": "/tmp/charm_dir", |
589 | + "group_code_owner": "webops_deploy", |
590 | + "user_code_runner": "ubunet", |
591 | + "wsgi_file__relation_key1": "relation_value1", |
592 | + "wsgi_file__relation_key2": "relation_value2", |
593 | + "local_unit": "click-index/3", |
594 | + }, result) |
595 | + |
596 | + def test_updates_existing_values(self): |
597 | + """Data stored in the context file is retained. |
598 | + |
599 | + This may be helpful so that templates can access information |
600 | + from relations outside the current execution environment. |
601 | + """ |
602 | + os.makedirs(os.path.dirname(self.context_path)) |
603 | + with open(self.context_path, 'w+') as context_file: |
604 | + context_file.write(yaml.dump({ |
605 | + 'solr:hostname': 'example.com', |
606 | + 'user_code_runner': 'oldvalue', |
607 | + })) |
608 | + |
609 | + self.mock_config.return_value = charmhelpers.core.hookenv.Serializable({ |
610 | + 'group_code_owner': 'webops_deploy', |
611 | + 'user_code_runner': 'newvalue', |
612 | + }) |
613 | + self.mock_local_unit.return_value = "click-index/3" |
614 | + |
615 | + charmhelpers.contrib.templating.contexts.juju_state_to_yaml( |
616 | + self.context_path) |
617 | + |
618 | + with open(self.context_path, 'r') as context_file: |
619 | + result = yaml.load(context_file.read()) |
620 | + self.assertEqual({ |
621 | + "charm_dir": "/tmp/charm_dir", |
622 | + "group_code_owner": "webops_deploy", |
623 | + "user_code_runner": "newvalue", |
624 | + "local_unit": "click-index/3", |
625 | + "solr:hostname": "example.com", |
626 | + }, result) |
627 | + |
LGTM