Merge lp:~michael.nelson/charm-helpers/create-hook-symlinks into lp:charm-helpers

Proposed by Michael Nelson
Status: Work in progress
Proposed branch: lp:~michael.nelson/charm-helpers/create-hook-symlinks
Merge into: lp:charm-helpers
Prerequisite: lp:~michael.nelson/charm-helpers/ansible-detect-hooks
Diff against target: 193 lines (+91/-13)
2 files modified
charmhelpers/contrib/ansible/__init__.py (+44/-10)
tests/contrib/ansible/test_ansible.py (+47/-3)
To merge this branch: bzr merge lp:~michael.nelson/charm-helpers/create-hook-symlinks
Reviewer Review Type Date Requested Status
charmers Pending
Review via email: mp+216143@code.launchpad.net

Commit message

Don't require charm author to create symlinks for each hook used in the playbook.

Description of the change

Can't currently get this to work, as python-support is installed with ansible, so when the install hook runs, it doesn't seem possible to import ansible.utils in the same process, as sys.path hasn't been setup by python-support.

Tried manually adding /usr/share/pyshared but it's not enough (and a dangerous path).

To post a comment you must log in.
147. By Michael Nelson

Update available_tags during hook execution if it couldn't be set at init time.

148. By Michael Nelson

Ensure pymodules on the path.

149. By Michael Nelson

Use /usr/share/pyshared

Unmerged revisions

149. By Michael Nelson

Use /usr/share/pyshared

148. By Michael Nelson

Ensure pymodules on the path.

147. By Michael Nelson

Update available_tags during hook execution if it couldn't be set at init time.

146. By Michael Nelson

Set required symlinks based on playbook tags.

145. By Michael Nelson

Deprecation warning.

144. By Michael Nelson

Hook-up with get_tags_for_playbook.

143. By Michael Nelson

Add get_tags_for_playbook.

142. By Michael Nelson

Add available tags to module - need to populate during install.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/contrib/ansible/__init__.py'
2--- charmhelpers/contrib/ansible/__init__.py 2014-05-05 13:21:40 +0000
3+++ charmhelpers/contrib/ansible/__init__.py 2014-05-05 13:21:40 +0000
4@@ -67,7 +67,9 @@
5 [2] http://www.ansibleworks.com/docs/modules.html
6 """
7 import os
8+import contextlib
9 import subprocess
10+import sys
11 import warnings
12
13 import charmhelpers.contrib.templating.contexts
14@@ -84,6 +86,17 @@
15 available_tags = set([])
16
17
18+@contextlib.contextmanager
19+def chdir(dir_name):
20+ """Context manager for changing cwd for short symlinks."""
21+ try:
22+ saved_path = os.getcwd()
23+ os.chdir(dir_name)
24+ yield
25+ finally:
26+ os.chdir(saved_path)
27+
28+
29 def install_ansible_support(from_ppa=True):
30 """Installs the ansible package.
31
32@@ -130,6 +143,12 @@
33 Discussion whether --list-tags should be a feature of ansible at
34 http://goo.gl/6gXd50
35 """
36+ # The first install hook run is before ansible is installed. Ansible
37+ # is installed into pymodules (it depends on python-support) which is
38+ # on the python path *next* time python runs, but not yet this time.
39+ ansible_install_dir = '/usr/share/pyshared'
40+ if ansible_install_dir not in sys.path:
41+ sys.path.append(ansible_install_dir)
42 import ansible.utils
43 import ansible.callbacks
44 import ansible.playbook
45@@ -192,27 +211,42 @@
46
47 self.playbook_path = playbook_path
48
49+ if default_hooks is not None:
50+ warnings.warn(
51+ "The use of default_hooks is deprecated. Ansible is now "
52+ "used to query your playbook for available tags.",
53+ DeprecationWarning)
54+
55 # The hooks decorator is created at module load time, which on the
56 # first run, will be before ansible is itself installed.
57 try:
58 available_tags.update(get_tags_for_playbook(playbook_path))
59 except ImportError:
60- available_tags.add('install')
61+ pass
62
63- if default_hooks is not None:
64- warnings.warn(
65- "The use of default_hooks is deprecated. Ansible is now "
66- "used to query your playbook for available tags.",
67- DeprecationWarning)
68 noop = lambda *args, **kwargs: None
69+ if 'install' not in available_tags:
70+ self.register('install', noop)
71 for hook in available_tags:
72 self.register(hook, noop)
73
74 def execute(self, args):
75 """Execute the hook followed by the playbook using the hook as tag."""
76 super(AnsibleHooks, self).execute(args)
77- hook_name = os.path.basename(args[0])
78-
79- if hook_name in available_tags:
80+ current_hook_name = os.path.basename(args[0])
81+
82+ if len(available_tags) == 0:
83+ available_tags.update(get_tags_for_playbook(self.playbook_path))
84+
85+ # Ensure all the symlinked hooks are set in the hooks dir,
86+ # so ansible charms don't need to add the symlink whenever
87+ # adding support for a new hook.
88+ if current_hook_name in ['install', 'upgrade-charm']:
89+ with chdir(os.path.join(charm_dir, 'hooks')):
90+ for hook_name in available_tags:
91+ if not os.path.exists(hook_name):
92+ os.symlink('hooks.py', hook_name)
93+
94+ if current_hook_name in available_tags:
95 charmhelpers.contrib.ansible.apply_playbook(
96- self.playbook_path, tags=[hook_name])
97+ self.playbook_path, tags=[current_hook_name])
98
99=== modified file 'tests/contrib/ansible/test_ansible.py'
100--- tests/contrib/ansible/test_ansible.py 2014-05-05 13:21:40 +0000
101+++ tests/contrib/ansible/test_ansible.py 2014-05-05 13:21:40 +0000
102@@ -72,6 +72,23 @@
103 'public-address': '123.123.123.123',
104 }
105
106+ def make_hooks_dir(self):
107+ charm_dir = tempfile.mkdtemp()
108+ self.addCleanup(shutil.rmtree, charm_dir)
109+ patcher = mock.patch.object(charmhelpers.contrib.ansible,
110+ 'charm_dir', charm_dir)
111+ patcher.start()
112+ self.addCleanup(patcher.stop)
113+
114+ hooks_dir = os.path.join(charm_dir, 'hooks')
115+ os.mkdir(os.path.join(charm_dir, 'hooks'))
116+ with open(os.path.join(hooks_dir, 'hooks.py'), 'w+'):
117+ pass
118+
119+ with charmhelpers.contrib.ansible.chdir(hooks_dir):
120+ os.symlink('hooks.py', 'install')
121+ return hooks_dir
122+
123 def setUp(self):
124 super(ApplyPlaybookTestCases, self).setUp()
125
126@@ -133,6 +150,10 @@
127 self.addCleanup(patcher.stop)
128 self.mock_get_tags_for_playbook.return_value = []
129
130+ self.hooks_dir = self.make_hooks_dir()
131+
132+ charmhelpers.contrib.ansible.available_tags = set([])
133+
134 def test_calls_ansible_playbook(self):
135 charmhelpers.contrib.ansible.apply_playbook(
136 'playbooks/dependencies.yaml')
137@@ -226,7 +247,11 @@
138 def test_install_hook(self):
139 """Install hook is run even though ansible wasn't available
140 when trying to query for available tags."""
141- self.mock_get_tags_for_playbook.side_effect = ImportError
142+ side_effects = [ImportError, ['install', 'start', 'stop']]
143+
144+ def side_effects_cb(*args):
145+ return side_effects.pop()
146+ self.mock_get_tags_for_playbook.side_effect = side_effects_cb
147 hooks = charmhelpers.contrib.ansible.AnsibleHooks('my/playbook.yaml')
148
149 hooks.execute(['install'])
150@@ -236,7 +261,11 @@
151 '--tags', 'install'])
152
153 def test_register_overrides_existing_hook(self):
154- self.mock_get_tags_for_playbook.side_effect = ImportError
155+ side_effects = [ImportError, ['install', 'start', 'stop']]
156+
157+ def side_effects_cb(*args):
158+ return side_effects.pop()
159+ self.mock_get_tags_for_playbook.side_effect = side_effects_cb
160 hooks = charmhelpers.contrib.ansible.AnsibleHooks('my/playbook.yaml')
161 install_hook = mock.Mock()
162 hooks.register('install', install_hook)
163@@ -251,7 +280,7 @@
164 def test_using_default_hooks_raises_deprecation_warning(self):
165 with warnings.catch_warnings(record=True) as warns:
166 warnings.simplefilter("always")
167- hooks = charmhelpers.contrib.ansible.AnsibleHooks(
168+ charmhelpers.contrib.ansible.AnsibleHooks(
169 'my/playbook.yaml', default_hooks=['foo'])
170
171 self.assertEqual(len(warns), 1)
172@@ -260,6 +289,21 @@
173 self.assertIn("default_hooks is deprecated",
174 unicode(warning.message))
175
176+ def test_install_hook_creates_missing_symlinks(self):
177+ side_effects = [ImportError, ['start', 'stop']]
178+
179+ def side_effects_cb(*args):
180+ return side_effects.pop()
181+
182+ self.mock_get_tags_for_playbook.side_effect = side_effects_cb
183+ hooks = charmhelpers.contrib.ansible.AnsibleHooks('my/playbook.yaml')
184+ hooks.register('install', mock.Mock())
185+
186+ hooks.execute(['install'])
187+
188+ self.assertEqual(set(['install', 'start', 'stop', 'hooks.py']),
189+ set(os.listdir(self.hooks_dir)))
190+
191
192 class GetTagsForPlaybookTestCases(unittest.TestCase):
193 """Verify that get_tags_for_playbook follows the current ansible contract.

Subscribers

People subscribed via source and target branches