Merge lp:~gandelman-a/charm-helpers/charm-helpers-sync into lp:charm-helpers
- charm-helpers-sync
- Merge into devel
Status: | Merged |
---|---|
Merged at revision: | 41 |
Proposed branch: | lp:~gandelman-a/charm-helpers/charm-helpers-sync |
Merge into: | lp:charm-helpers |
Diff against target: |
493 lines (+468/-0) 4 files modified
tests/tools/test_charm_helper_sync.py (+197/-0) tools/charm_helpers_sync/README (+89/-0) tools/charm_helpers_sync/charm_helpers_sync.py (+168/-0) tools/charm_helpers_sync/example-config.yaml (+14/-0) |
To merge this branch: | bzr merge lp:~gandelman-a/charm-helpers/charm-helpers-sync |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Matthew Wedgwood (community) | Approve | ||
Adam Gandelman (community) | Needs Resubmitting | ||
Review via email:
|
Commit message
Description of the change
This includes a little utility script for syncing parts of lp:charm-helpers into a local charm. It is meant to be used by charm authors while authoring a charm that depends on parts of charm-helpers. I've been using it heavily while working on a new charm lately, my workflow has been something like:
- Add a charm-helpers-
- Realize I need new functionality in charm-helpers, create my own branch at lp:~gandelman-a/charm-helpers. Use this as a working branch alongisde my WIP charm. Commit changes there, point my charm-helpers-
- Propose lp:~gandelman-a/charm-helpers for merging to lp:charm-helpers. After its merged, update my charm-helpers-
It's worked well for me so far and really helps prevent me from having a heavily modified fork of charm-helpers embedded in my charm.
- 33. By Adam Gandelman
-
Add tests, rename to charm_helpers_sync and make importable.
- 34. By Adam Gandelman
-
Update README with more a descriptive use case.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Adam Gandelman (gandelman-a) wrote : | # |
Hey Matthew-
This is intended to be used by charm authors during their offline development, as a way of embeding a specific revision of a charm helpers branch into their charm. I've updated the README accordingly and added some tests. Needed to rename the script and its home to get it importable during tests.
Thanks.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Matthew Wedgwood (mew) wrote : | # |
Thanks for the additions. +1
Preview Diff
1 | === added directory 'tests/tools' |
2 | === added file 'tests/tools/__init__.py' |
3 | === added file 'tests/tools/test_charm_helper_sync.py' |
4 | --- tests/tools/test_charm_helper_sync.py 1970-01-01 00:00:00 +0000 |
5 | +++ tests/tools/test_charm_helper_sync.py 2013-06-27 22:23:25 +0000 |
6 | @@ -0,0 +1,197 @@ |
7 | +import unittest |
8 | +from mock import call, patch |
9 | +import yaml |
10 | + |
11 | +import tools.charm_helpers_sync.charm_helpers_sync as sync |
12 | + |
13 | +INCLUDE = """ |
14 | +include: |
15 | + - core |
16 | + - contrib.openstack |
17 | + - contrib.storage |
18 | + - contrib.hahelpers: |
19 | + - utils |
20 | + - ceph_utils |
21 | + - cluster_utils |
22 | + - haproxy_utils |
23 | +""" |
24 | + |
25 | + |
26 | +class HelperSyncTests(unittest.TestCase): |
27 | + def test_clone_helpers(self): |
28 | + '''It properly branches the correct helpers branch''' |
29 | + with patch('subprocess.check_call') as check_call: |
30 | + sync.clone_helpers(work_dir='/tmp/foo', branch='lp:charm-helpers') |
31 | + check_call.assert_called_with(['bzr', 'branch', |
32 | + 'lp:charm-helpers', |
33 | + '/tmp/foo/charm-helpers']) |
34 | + |
35 | + def test_module_path(self): |
36 | + '''It converts a python module path to a filesystem path''' |
37 | + self.assertEquals(sync._module_path('some.test.module'), |
38 | + 'some/test/module') |
39 | + |
40 | + def test_src_path(self): |
41 | + '''It renders the correct path to module within charm-helpers tree''' |
42 | + path = sync._src_path(src='/tmp/charm-helpers', |
43 | + module='contrib.openstack') |
44 | + self.assertEquals('/tmp/charm-helpers/charmhelpers/contrib/openstack', |
45 | + path) |
46 | + |
47 | + def test_dest_path(self): |
48 | + '''It correctly finds the correct install path within a charm''' |
49 | + path = sync._dest_path(dest='/tmp/mycharm/hooks/charmhelpers', |
50 | + module='contrib.openstack') |
51 | + self.assertEquals('/tmp/mycharm/hooks/charmhelpers/contrib/openstack', |
52 | + path) |
53 | + |
54 | + @patch('__builtin__.open') |
55 | + @patch('os.path.exists') |
56 | + @patch('os.walk') |
57 | + def test_ensure_init(self, walk, exists, _open): |
58 | + '''It ensures all subdirectories of a parent are python importable''' |
59 | + # os walk |
60 | + # os.path.join |
61 | + # os.path.exists |
62 | + # open |
63 | + def _walk(path): |
64 | + yield ('/tmp/hooks/', ['helpers'], []) |
65 | + yield ('/tmp/hooks/helpers', ['foo'], []) |
66 | + yield ('/tmp/hooks/helpers/foo', [], []) |
67 | + walk.side_effect = _walk |
68 | + exists.return_value = False |
69 | + sync.ensure_init('hooks/helpers/foo/') |
70 | + ex = [call('/tmp/hooks/__init__.py', 'wb'), |
71 | + call('/tmp/hooks/helpers/__init__.py', 'wb'), |
72 | + call('/tmp/hooks/helpers/foo/__init__.py', 'wb')] |
73 | + for c in ex: |
74 | + self.assertIn(c, _open.call_args_list) |
75 | + |
76 | + @patch('tools.charm_helpers_sync.charm_helpers_sync.ensure_init') |
77 | + @patch('os.path.isfile') |
78 | + @patch('shutil.copy') |
79 | + @patch('os.makedirs') |
80 | + @patch('os.path.exists') |
81 | + def test_sync_pyfile(self, exists, mkdirs, copy, isfile, ensure_init): |
82 | + '''It correctly syncs a py src file from src to dest''' |
83 | + exists.return_value = False |
84 | + isfile.return_value = True |
85 | + sync.sync_pyfile('/tmp/charm-helpers/core/host', |
86 | + 'hooks/charmhelpers/core') |
87 | + mkdirs.assert_called_with('hooks/charmhelpers/core') |
88 | + copy_f = call('/tmp/charm-helpers/core/host.py', |
89 | + 'hooks/charmhelpers/core') |
90 | + copy_i = call('/tmp/charm-helpers/core/__init__.py', |
91 | + 'hooks/charmhelpers/core') |
92 | + self.assertIn(copy_f, copy.call_args_list) |
93 | + self.assertIn(copy_i, copy.call_args_list) |
94 | + ensure_init.assert_called_with('hooks/charmhelpers/core') |
95 | + |
96 | + @patch('os.path.isdir') |
97 | + @patch('os.path.isfile') |
98 | + def test_filter_dir(self, isfile, isdir): |
99 | + '''It filters non-python files and non-module dirs from sync source''' |
100 | + files = { |
101 | + 'bad_file.bin': 'f', |
102 | + 'some_dir': 'd', |
103 | + 'good_helper.py': 'f', |
104 | + 'good_helper2.py': 'f', |
105 | + 'good_helper3.py': 'f', |
106 | + 'bad_file.img': 'f', |
107 | + } |
108 | + |
109 | + def _isfile(f): |
110 | + try: |
111 | + return files[f.split('/').pop()] == 'f' |
112 | + except KeyError: |
113 | + return False |
114 | + |
115 | + def _isdir(f): |
116 | + try: |
117 | + return files[f.split('/').pop()] == 'd' |
118 | + except KeyError: |
119 | + return False |
120 | + |
121 | + isfile.side_effect = _isfile |
122 | + isdir.side_effect = _isdir |
123 | + result = sync._filter(dir='/tmp/charm-helpers/core', |
124 | + ls=files.iterkeys()) |
125 | + ex = ['bad_file.bin', 'bad_file.img', 'some_dir'] |
126 | + self.assertEquals(ex, result) |
127 | + |
128 | + @patch('tools.charm_helpers_sync.charm_helpers_sync._filter') |
129 | + @patch('tools.charm_helpers_sync.charm_helpers_sync.ensure_init') |
130 | + @patch('shutil.copytree') |
131 | + @patch('shutil.rmtree') |
132 | + @patch('os.path.exists') |
133 | + def test_sync_directory(self, exists, rmtree, copytree, ensure_init, |
134 | + _filter): |
135 | + '''It correctly syncs src directory to dest directory''' |
136 | + sync.sync_directory('/tmp/charm-helpers/charmhelpers/core', |
137 | + 'hooks/charmhelpers/core') |
138 | + exists.return_value = True |
139 | + rmtree.assert_called_with('hooks/charmhelpers/core') |
140 | + copytree.assert_called_with('/tmp/charm-helpers/charmhelpers/core', |
141 | + 'hooks/charmhelpers/core', ignore=_filter) |
142 | + ensure_init.assert_called_with('hooks/charmhelpers/core') |
143 | + |
144 | + @patch('os.path.isfile') |
145 | + def test_is_pyfile(self, isfile): |
146 | + '''It correctly identifies incomplete path to a py src file as such''' |
147 | + sync._is_pyfile('/tmp/charm-helpers/charmhelpers/core/host') |
148 | + isfile.assert_called_with( |
149 | + '/tmp/charm-helpers/charmhelpers/core/host.py' |
150 | + ) |
151 | + |
152 | + @patch('tools.charm_helpers_sync.charm_helpers_sync.sync_directory') |
153 | + @patch('os.path.isdir') |
154 | + def test_syncs_directory(self, is_dir, sync_dir): |
155 | + '''It correctly syncs a module directory''' |
156 | + is_dir.return_value = True |
157 | + sync.sync(src='/tmp/charm-helpers', |
158 | + dest='hooks/charmhelpers', |
159 | + module='contrib.openstack') |
160 | + |
161 | + sync_dir.assert_called_with( |
162 | + '/tmp/charm-helpers/charmhelpers/contrib/openstack', |
163 | + 'hooks/charmhelpers/contrib/openstack') |
164 | + |
165 | + @patch('tools.charm_helpers_sync.charm_helpers_sync.sync_pyfile') |
166 | + @patch('tools.charm_helpers_sync.charm_helpers_sync._is_pyfile') |
167 | + @patch('os.path.isdir') |
168 | + def test_syncs_file(self, is_dir, is_pyfile, sync_pyfile): |
169 | + '''It correctly syncs a module file''' |
170 | + is_dir.return_value = False |
171 | + is_pyfile.return_value = True |
172 | + sync.sync(src='/tmp/charm-helpers', |
173 | + dest='hooks/charmhelpers', |
174 | + module='contrib.openstack.utils') |
175 | + |
176 | + sync_pyfile.assert_called_with( |
177 | + '/tmp/charm-helpers/charmhelpers/contrib/openstack/utils', |
178 | + 'hooks/charmhelpers/contrib/openstack') |
179 | + |
180 | + @patch('tools.charm_helpers_sync.charm_helpers_sync.sync') |
181 | + @patch('os.path.isdir') |
182 | + def test_sync_helpers_from_config(self, isdir, _sync): |
183 | + '''It correctly syncs a list of included helpers''' |
184 | + include = yaml.load(INCLUDE)['include'] |
185 | + isdir.return_value = True |
186 | + sync.sync_helpers(include=include, |
187 | + src='/tmp/charm-helpers', |
188 | + |
189 | + dest='hooks/charmhelpers') |
190 | + mods = [ |
191 | + 'core', |
192 | + 'contrib.openstack', |
193 | + 'contrib.storage', |
194 | + 'contrib.hahelpers.utils', |
195 | + 'contrib.hahelpers.ceph_utils', |
196 | + 'contrib.hahelpers.cluster_utils', |
197 | + 'contrib.hahelpers.haproxy_utils' |
198 | + ] |
199 | + |
200 | + ex_calls = [] |
201 | + [ex_calls.append(call('/tmp/charm-helpers', 'hooks/charmhelpers', c)) |
202 | + for c in mods] |
203 | + self.assertEquals(ex_calls, _sync.call_args_list) |
204 | |
205 | === added directory 'tools' |
206 | === added file 'tools/__init__.py' |
207 | === added directory 'tools/charm_helpers_sync' |
208 | === added file 'tools/charm_helpers_sync/README' |
209 | --- tools/charm_helpers_sync/README 1970-01-01 00:00:00 +0000 |
210 | +++ tools/charm_helpers_sync/README 2013-06-27 22:23:25 +0000 |
211 | @@ -0,0 +1,89 @@ |
212 | +Script for synchronizing charm-helpers into a charm branch. |
213 | + |
214 | +This script is intended to be used by charm authors during the development |
215 | +of their charm. It allows authors to pull in bits of a charm-helpers source |
216 | +tree and embed directly into their charm, to be deployed with the rest of |
217 | +their hooks and charm payload. This script is not intended to be called |
218 | +by the hooks themselves, but instead by the charm author while they are |
219 | +hacking on a charm offline. Consider it a method of compiling specific |
220 | +revision of a charm-helpers branch into a given charm source tree. |
221 | + |
222 | +Some goals and benefits to using a sync tool to manage this process: |
223 | + |
224 | + - Reduces the burden of manually copying in upstream charm helpers code |
225 | + into a charm and helps ensure we can easily keep a specific charm's |
226 | + helper code up to date. |
227 | + |
228 | + - Allows authors to hack on their own working branch of charm-helpers, |
229 | + easily sync into their WIP charm. Any changes they've made to charm |
230 | + helpers can be upstreamed via a merge of their charm-helpers branch |
231 | + into lp:charm-helpers, ideally at the same time they are upstreaming |
232 | + the charm itself into the charm store. Separating charm helper |
233 | + development from charm development can help reduce cases where charms |
234 | + are shipping locally modified helpers. |
235 | + |
236 | + - Avoids the need to ship the *entire* lp:charm-helpers source tree with |
237 | + a charm. Authors can selectively pick and choose what subset of helpers |
238 | + to include to satisfy the goals of their charm. |
239 | + |
240 | +Loosely based on OpenStack's oslo-incubator: |
241 | + |
242 | + https://github.com/openstack/oslo-incubator.git |
243 | + |
244 | +Allows specifying a list of dependencies to sync in from a charm-helpers |
245 | +branch. Ideally, each charm should describe its requirements in a yaml |
246 | +config included in the charm, eg charm-helpers.yaml (NOTE: Example module |
247 | +layout as of 05/30/2013): |
248 | + |
249 | + $ cd my-charm |
250 | + $ cat >charm-helpers.yaml <<END |
251 | + destination: hooks/helpers |
252 | + branch: lp:charm-helpers |
253 | + include: |
254 | + - core |
255 | + - contrib.openstack |
256 | + - contrib.hahelpers: |
257 | + - ceph_utils |
258 | + END |
259 | + |
260 | +includes may be defined as entire module sub-directories, or as invidual |
261 | +.py files with in a module sub-directory. |
262 | + |
263 | +Charm author can then sync in and update helpers as needed. The following |
264 | +import all of charmhelpers.core + charmhelpers.contrib.openstack, and only |
265 | +ceph_utils.py from charmhelpers.contrib.hahelpers: |
266 | + |
267 | + $ charm-helper-sync -c charm-helpers.yaml |
268 | + $ find hooks/helpers/ |
269 | + hooks/helpers/ |
270 | + hooks/helpers/contrib |
271 | + hooks/helpers/contrib/openstack |
272 | + hooks/helpers/contrib/openstack/openstack_utils.py |
273 | + hooks/helpers/contrib/openstack/__init__.py |
274 | + hooks/helpers/contrib/hahelpers |
275 | + hooks/helpers/contrib/hahelpers/ceph_utils.py |
276 | + hooks/helpers/contrib/hahelpers/__init__.py |
277 | + hooks/helpers/contrib/__init__.py |
278 | + hooks/helpers/core |
279 | + hooks/helpers/core/hookenv.py |
280 | + hooks/helpers/core/host.py |
281 | + hooks/helpers/core/__init__.py |
282 | + hooks/helpers/__init__.py |
283 | + |
284 | + |
285 | +Script will create missing __init__.py's to ensure each subdirectory is |
286 | +importable, assuming the script is run from the charm's top-level directory. |
287 | + |
288 | +You may also override configured destination directory and source bzr |
289 | +branch: |
290 | + |
291 | + $ charm-helper-sync -b ~/src/bzr/charm-helpers-dev \ |
292 | + -d hooks/helpers-test \ |
293 | + -c charm-helpers.yaml |
294 | + |
295 | +Or not use a config file at all: |
296 | + $ charm-helper-sync -b lp:~gandelman-a/charm-helpers/fixes \ |
297 | + -d hooks/helpers core contrib.openstack contrib.hahelpers |
298 | + |
299 | +Script will create missing __init__.py's to ensure each subdirectory is |
300 | +importable, assuming the script is run from the charm's top-level directory. |
301 | |
302 | === added file 'tools/charm_helpers_sync/__init__.py' |
303 | === added file 'tools/charm_helpers_sync/charm_helpers_sync.py' |
304 | --- tools/charm_helpers_sync/charm_helpers_sync.py 1970-01-01 00:00:00 +0000 |
305 | +++ tools/charm_helpers_sync/charm_helpers_sync.py 2013-06-27 22:23:25 +0000 |
306 | @@ -0,0 +1,168 @@ |
307 | +#!/usr/bin/python |
308 | +# |
309 | +# Copyright 2013 Canonical Ltd. |
310 | + |
311 | +# Authors: |
312 | +# Adam Gandelman <adamg@ubuntu.com> |
313 | +# |
314 | + |
315 | +import logging |
316 | +import optparse |
317 | +import os |
318 | +import subprocess |
319 | +import shutil |
320 | +import sys |
321 | +import tempfile |
322 | +import yaml |
323 | + |
324 | +CHARM_HELPERS_BRANCH = 'lp:charm-helpers' |
325 | + |
326 | +def parse_config(conf_file): |
327 | + if not os.path.isfile(conf_file): |
328 | + logging.error('Invalid config file: %s.' % conf_file) |
329 | + return False |
330 | + return yaml.load(open(conf_file).read()) |
331 | + |
332 | +def clone_helpers(work_dir, branch): |
333 | + dest = os.path.join(work_dir, 'charm-helpers') |
334 | + logging.info('Checking out %s to %s.' % (branch, dest)) |
335 | + cmd = ['bzr', 'branch', branch, dest] |
336 | + subprocess.check_call(cmd) |
337 | + return dest |
338 | + |
339 | +def _filter(dir, ls): |
340 | + _filter = [] |
341 | + for f in ls: |
342 | + _f = os.path.join(dir, f) |
343 | + if (os.path.isfile(_f) and not _f.endswith('.py')): |
344 | + logging.debug('Not syncing files: %s' % f) |
345 | + _filter.append(f) |
346 | + elif (os.path.isdir(_f) and not |
347 | + os.path.isfile(os.path.join(_f, '__init__.py'))): |
348 | + logging.debug('Not syncing directory: %s' % f) |
349 | + _filter.append(f) |
350 | + return _filter |
351 | + |
352 | +def _module_path(module): |
353 | + return os.path.join(*module.split('.')) |
354 | + |
355 | +def _src_path(src, module): |
356 | + return os.path.join(src, 'charmhelpers', _module_path(module)) |
357 | + |
358 | +def _dest_path(dest, module): |
359 | + return os.path.join(dest, _module_path(module)) |
360 | + |
361 | +def _is_pyfile(path): |
362 | + return os.path.isfile(path + '.py') |
363 | + |
364 | +def ensure_init(path): |
365 | + ''' |
366 | + ensure directories leading up to path are importable, omitting |
367 | + parent directory, eg path='/hooks/helpers/foo'/: |
368 | + hooks/ |
369 | + hooks/helpers/__init__.py |
370 | + hooks/helpers/foo/__init__.py |
371 | + ''' |
372 | + for d, dirs, files in os.walk(os.path.join(*path.split('/')[:2])): |
373 | + _i = os.path.join(d, '__init__.py') |
374 | + if not os.path.exists(_i): |
375 | + logging.info('Adding missing __init__.py: %s' % _i) |
376 | + open(_i, 'wb').close() |
377 | + |
378 | +def sync_pyfile(src, dest): |
379 | + src = src + '.py' |
380 | + src_dir = os.path.dirname(src) |
381 | + logging.info('Syncing pyfile: %s -> %s.' % (src, dest)) |
382 | + if not os.path.exists(dest): |
383 | + os.makedirs(dest) |
384 | + shutil.copy(src, dest) |
385 | + if os.path.isfile(os.path.join(src_dir, '__init__.py')): |
386 | + shutil.copy(os.path.join(src_dir, '__init__.py'), |
387 | + dest) |
388 | + ensure_init(dest) |
389 | + |
390 | +def sync_directory(src, dest): |
391 | + if os.path.exists(dest): |
392 | + logging.debug('Removing existing directory: %s' % dest) |
393 | + shutil.rmtree(dest) |
394 | + logging.info('Syncing directory: %s -> %s.' % (src, dest)) |
395 | + shutil.copytree(src, dest, ignore=_filter) |
396 | + ensure_init(dest) |
397 | + |
398 | +def sync(src, dest, module): |
399 | + if os.path.isdir(_src_path(src, module)): |
400 | + sync_directory(_src_path(src, module), _dest_path(dest, module)) |
401 | + elif _is_pyfile(_src_path(src, module)): |
402 | + sync_pyfile(_src_path(src, module), |
403 | + os.path.dirname(_dest_path(dest, module))) |
404 | + else: |
405 | + logging.warn('Could not sync: %s. Neither a pyfile or directory, ' |
406 | + 'does it even exist?' % module) |
407 | + |
408 | +def sync_helpers(include, src, dest): |
409 | + if not os.path.isdir(dest): |
410 | + os.mkdir(dest) |
411 | + |
412 | + for inc in include: |
413 | + if isinstance(inc, str): |
414 | + sync(src, dest, inc) |
415 | + elif isinstance(inc, dict): |
416 | + # could also do nested dicts here. |
417 | + for k, v in inc.iteritems(): |
418 | + if isinstance(v, list): |
419 | + [sync(src, dest, '%s.%s' % (k, m)) for m in v] |
420 | + |
421 | +if __name__ == '__main__': |
422 | + parser = optparse.OptionParser() |
423 | + parser.add_option('-c', '--config', action='store', dest='config', |
424 | + default=None, help='helper config file') |
425 | + parser.add_option('-D', '--debug', action='store_true', dest='debug', |
426 | + default=False, help='debug') |
427 | + parser.add_option('-b', '--branch', action='store', dest='branch', |
428 | + help='charm-helpers bzr branch (overrides config)') |
429 | + parser.add_option('-d', '--destination', action='store', dest='dest_dir', |
430 | + help='sync destination dir (overrides config)') |
431 | + (opts, args) = parser.parse_args() |
432 | + |
433 | + if opts.debug: |
434 | + logging.basicConfig(level=logging.DEBUG) |
435 | + else: |
436 | + logging.basicConfig(level=logging.INFO) |
437 | + |
438 | + if opts.config: |
439 | + logging.info('Loading charm helper config from %s.' % opts.config) |
440 | + config = parse_config(opts.config) |
441 | + if not config: |
442 | + logging.error('Could not parse config from %s.' % opts.config) |
443 | + sys.exit(1) |
444 | + else: |
445 | + config = {} |
446 | + |
447 | + if 'branch' not in config: |
448 | + config['branch'] = CHARM_HELPERS_BRANCH |
449 | + if opts.branch: |
450 | + config['branch'] = opts.branch |
451 | + if opts.dest_dir: |
452 | + config['destination'] = opts.dest_dir |
453 | + |
454 | + if 'destination' not in config: |
455 | + logging.error('No destination dir. specified as option or config.') |
456 | + sys.exit(1) |
457 | + |
458 | + if 'include' not in config: |
459 | + if not args: |
460 | + logging.error('No modules to sync specified as option or config.') |
461 | + sys.exit(1) |
462 | + config['include'] = [] |
463 | + [config['include'].append(a) for a in args] |
464 | + |
465 | + tmpd = tempfile.mkdtemp() |
466 | + try: |
467 | + checkout = clone_helpers(tmpd, config['branch']) |
468 | + sync_helpers(config['include'], checkout, config['destination']) |
469 | + except Exception, e: |
470 | + logging.error("Could not sync: %s" % e) |
471 | + raise e |
472 | + finally: |
473 | + logging.debug('Cleaning up %s' % tmpd) |
474 | + shutil.rmtree(tmpd) |
475 | |
476 | === added file 'tools/charm_helpers_sync/example-config.yaml' |
477 | --- tools/charm_helpers_sync/example-config.yaml 1970-01-01 00:00:00 +0000 |
478 | +++ tools/charm_helpers_sync/example-config.yaml 2013-06-27 22:23:25 +0000 |
479 | @@ -0,0 +1,14 @@ |
480 | +# Import from remote bzr branch. |
481 | +branch: lp:charm-helpers |
482 | +# install helpers to ./hooks/charmhelpers |
483 | +destination: hooks/charmhelpers |
484 | +include: |
485 | + # include all of charmhelpers.core |
486 | + - core |
487 | + # all of charmhelpers.payload |
488 | + - payload |
489 | + # and a subset of charmhelpers.contrib.hahelpers |
490 | + - contrib.hahelpers: |
491 | + - openstack_common |
492 | + - ceph_utils |
493 | + - utils |
Adam, this looks great, thanks for submitting it.
I'm a little unclear - is this intended for use within a charm or externally during development? If it's the former, I think we'd want to discourage folks from installing charm-helpers onto their units, especially with the amount of development on it. In either case, could you update the README to be a little more explicit about the use case?
The one other thing I'd love to see is tests. As it's outside the core package, I'd much rather get this into the tree now so I think it's OK to merge if you don't have the time for them.
Thanks!