Merge lp:~billy-olsen/charms/trusty/rabbitmq-server/ch-sync-cli-fix into lp:~openstack-charmers-archive/charms/trusty/rabbitmq-server/next
- Trusty Tahr (14.04)
- ch-sync-cli-fix
- Merge into next
Status: | Merged |
---|---|
Merged at revision: | 104 |
Proposed branch: | lp:~billy-olsen/charms/trusty/rabbitmq-server/ch-sync-cli-fix |
Merge into: | lp:~openstack-charmers-archive/charms/trusty/rabbitmq-server/next |
Diff against target: |
424 lines (+366/-2) 8 files modified
charm-helpers-tests.yaml (+1/-0) hooks/charmhelpers/core/hookenv.py (+16/-1) tests/charmhelpers/cli/__init__.py (+195/-0) tests/charmhelpers/cli/benchmark.py (+36/-0) tests/charmhelpers/cli/commands.py (+32/-0) tests/charmhelpers/cli/host.py (+31/-0) tests/charmhelpers/cli/unitdata.py (+39/-0) tests/charmhelpers/core/hookenv.py (+16/-1) |
To merge this branch: | bzr merge lp:~billy-olsen/charms/trusty/rabbitmq-server/ch-sync-cli-fix |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Edward Hope-Morley | Approve | ||
Ryan Beisner (community) | Approve | ||
Review via email: mp+266619@code.launchpad.net |
Commit message
Description of the change
uosci-testing-bot (uosci-testing-bot) wrote : | # |
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #6843 rabbitmq-
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5546 rabbitmq-
AMULET FAIL: amulet-test failed
AMULET Results (max last 2 lines):
make: *** [functional_test] Error 1
ERROR:root:Make target returned non-zero.
Full amulet test output: http://
Build: http://
- 104. By Billy Olsen
-
Include cli in the rmq amulet
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #7376 rabbitmq-
LINT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #6844 rabbitmq-
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5548 rabbitmq-
AMULET FAIL: amulet-test failed
AMULET Results (max last 2 lines):
make: *** [functional_test] Error 1
ERROR:root:Make target returned non-zero.
Full amulet test output: http://
Build: http://
Ryan Beisner (1chb1n) wrote : | # |
I would suggest merging this, even though the amulet test fails. It was previously failing that functional test.
This merge proposal resolves an import error, introduced by a charm-helpers CLI.
Ryan Beisner (1chb1n) wrote : | # |
PS FYI, this is the only os-charm which had an amulet test affected by the CLI helper addition.
Ryan Beisner (1chb1n) wrote : | # |
Regarding the pre-existing test failure, we will still need to address that separately.
Edward Hope-Morley (hopem) wrote : | # |
Lets get this landed. It does not touch any code outside of amulet tests (which are broken anyway).
Preview Diff
1 | === modified file 'charm-helpers-tests.yaml' |
2 | --- charm-helpers-tests.yaml 2015-04-13 22:11:34 +0000 |
3 | +++ charm-helpers-tests.yaml 2015-07-31 22:01:37 +0000 |
4 | @@ -2,4 +2,5 @@ |
5 | branch: lp:charm-helpers |
6 | include: |
7 | - core |
8 | + - cli |
9 | - contrib.ssl |
10 | |
11 | === modified file 'hooks/charmhelpers/core/hookenv.py' |
12 | --- hooks/charmhelpers/core/hookenv.py 2015-07-31 13:11:07 +0000 |
13 | +++ hooks/charmhelpers/core/hookenv.py 2015-07-31 22:01:37 +0000 |
14 | @@ -34,7 +34,22 @@ |
15 | import tempfile |
16 | from subprocess import CalledProcessError |
17 | |
18 | -from charmhelpers.cli import cmdline |
19 | +try: |
20 | + from charmhelpers.cli import cmdline |
21 | +except ImportError as e: |
22 | + # due to the anti-pattern of partially synching charmhelpers directly |
23 | + # into charms, it's possible that charmhelpers.cli is not available; |
24 | + # if that's the case, they don't really care about using the cli anyway, |
25 | + # so mock it out |
26 | + if str(e) == 'No module named cli': |
27 | + class cmdline(object): |
28 | + @classmethod |
29 | + def subcommand(cls, *args, **kwargs): |
30 | + def _wrap(func): |
31 | + return func |
32 | + return _wrap |
33 | + else: |
34 | + raise |
35 | |
36 | import six |
37 | if not six.PY3: |
38 | |
39 | === added directory 'tests/charmhelpers/cli' |
40 | === added file 'tests/charmhelpers/cli/__init__.py' |
41 | --- tests/charmhelpers/cli/__init__.py 1970-01-01 00:00:00 +0000 |
42 | +++ tests/charmhelpers/cli/__init__.py 2015-07-31 22:01:37 +0000 |
43 | @@ -0,0 +1,195 @@ |
44 | +# Copyright 2014-2015 Canonical Limited. |
45 | +# |
46 | +# This file is part of charm-helpers. |
47 | +# |
48 | +# charm-helpers is free software: you can redistribute it and/or modify |
49 | +# it under the terms of the GNU Lesser General Public License version 3 as |
50 | +# published by the Free Software Foundation. |
51 | +# |
52 | +# charm-helpers is distributed in the hope that it will be useful, |
53 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
54 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
55 | +# GNU Lesser General Public License for more details. |
56 | +# |
57 | +# You should have received a copy of the GNU Lesser General Public License |
58 | +# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
59 | + |
60 | +import inspect |
61 | +import argparse |
62 | +import sys |
63 | + |
64 | +from six.moves import zip |
65 | + |
66 | +from charmhelpers.core import unitdata |
67 | + |
68 | + |
69 | +class OutputFormatter(object): |
70 | + def __init__(self, outfile=sys.stdout): |
71 | + self.formats = ( |
72 | + "raw", |
73 | + "json", |
74 | + "py", |
75 | + "yaml", |
76 | + "csv", |
77 | + "tab", |
78 | + ) |
79 | + self.outfile = outfile |
80 | + |
81 | + def add_arguments(self, argument_parser): |
82 | + formatgroup = argument_parser.add_mutually_exclusive_group() |
83 | + choices = self.supported_formats |
84 | + formatgroup.add_argument("--format", metavar='FMT', |
85 | + help="Select output format for returned data, " |
86 | + "where FMT is one of: {}".format(choices), |
87 | + choices=choices, default='raw') |
88 | + for fmt in self.formats: |
89 | + fmtfunc = getattr(self, fmt) |
90 | + formatgroup.add_argument("-{}".format(fmt[0]), |
91 | + "--{}".format(fmt), action='store_const', |
92 | + const=fmt, dest='format', |
93 | + help=fmtfunc.__doc__) |
94 | + |
95 | + @property |
96 | + def supported_formats(self): |
97 | + return self.formats |
98 | + |
99 | + def raw(self, output): |
100 | + """Output data as raw string (default)""" |
101 | + if isinstance(output, (list, tuple)): |
102 | + output = '\n'.join(map(str, output)) |
103 | + self.outfile.write(str(output)) |
104 | + |
105 | + def py(self, output): |
106 | + """Output data as a nicely-formatted python data structure""" |
107 | + import pprint |
108 | + pprint.pprint(output, stream=self.outfile) |
109 | + |
110 | + def json(self, output): |
111 | + """Output data in JSON format""" |
112 | + import json |
113 | + json.dump(output, self.outfile) |
114 | + |
115 | + def yaml(self, output): |
116 | + """Output data in YAML format""" |
117 | + import yaml |
118 | + yaml.safe_dump(output, self.outfile) |
119 | + |
120 | + def csv(self, output): |
121 | + """Output data as excel-compatible CSV""" |
122 | + import csv |
123 | + csvwriter = csv.writer(self.outfile) |
124 | + csvwriter.writerows(output) |
125 | + |
126 | + def tab(self, output): |
127 | + """Output data in excel-compatible tab-delimited format""" |
128 | + import csv |
129 | + csvwriter = csv.writer(self.outfile, dialect=csv.excel_tab) |
130 | + csvwriter.writerows(output) |
131 | + |
132 | + def format_output(self, output, fmt='raw'): |
133 | + fmtfunc = getattr(self, fmt) |
134 | + fmtfunc(output) |
135 | + |
136 | + |
137 | +class CommandLine(object): |
138 | + argument_parser = None |
139 | + subparsers = None |
140 | + formatter = None |
141 | + exit_code = 0 |
142 | + |
143 | + def __init__(self): |
144 | + if not self.argument_parser: |
145 | + self.argument_parser = argparse.ArgumentParser(description='Perform common charm tasks') |
146 | + if not self.formatter: |
147 | + self.formatter = OutputFormatter() |
148 | + self.formatter.add_arguments(self.argument_parser) |
149 | + if not self.subparsers: |
150 | + self.subparsers = self.argument_parser.add_subparsers(help='Commands') |
151 | + |
152 | + def subcommand(self, command_name=None): |
153 | + """ |
154 | + Decorate a function as a subcommand. Use its arguments as the |
155 | + command-line arguments""" |
156 | + def wrapper(decorated): |
157 | + cmd_name = command_name or decorated.__name__ |
158 | + subparser = self.subparsers.add_parser(cmd_name, |
159 | + description=decorated.__doc__) |
160 | + for args, kwargs in describe_arguments(decorated): |
161 | + subparser.add_argument(*args, **kwargs) |
162 | + subparser.set_defaults(func=decorated) |
163 | + return decorated |
164 | + return wrapper |
165 | + |
166 | + def test_command(self, decorated): |
167 | + """ |
168 | + Subcommand is a boolean test function, so bool return values should be |
169 | + converted to a 0/1 exit code. |
170 | + """ |
171 | + decorated._cli_test_command = True |
172 | + return decorated |
173 | + |
174 | + def no_output(self, decorated): |
175 | + """ |
176 | + Subcommand is not expected to return a value, so don't print a spurious None. |
177 | + """ |
178 | + decorated._cli_no_output = True |
179 | + return decorated |
180 | + |
181 | + def subcommand_builder(self, command_name, description=None): |
182 | + """ |
183 | + Decorate a function that builds a subcommand. Builders should accept a |
184 | + single argument (the subparser instance) and return the function to be |
185 | + run as the command.""" |
186 | + def wrapper(decorated): |
187 | + subparser = self.subparsers.add_parser(command_name) |
188 | + func = decorated(subparser) |
189 | + subparser.set_defaults(func=func) |
190 | + subparser.description = description or func.__doc__ |
191 | + return wrapper |
192 | + |
193 | + def run(self): |
194 | + "Run cli, processing arguments and executing subcommands." |
195 | + arguments = self.argument_parser.parse_args() |
196 | + argspec = inspect.getargspec(arguments.func) |
197 | + vargs = [] |
198 | + kwargs = {} |
199 | + for arg in argspec.args: |
200 | + vargs.append(getattr(arguments, arg)) |
201 | + if argspec.varargs: |
202 | + vargs.extend(getattr(arguments, argspec.varargs)) |
203 | + if argspec.keywords: |
204 | + for kwarg in argspec.keywords.items(): |
205 | + kwargs[kwarg] = getattr(arguments, kwarg) |
206 | + output = arguments.func(*vargs, **kwargs) |
207 | + if getattr(arguments.func, '_cli_test_command', False): |
208 | + self.exit_code = 0 if output else 1 |
209 | + output = '' |
210 | + if getattr(arguments.func, '_cli_no_output', False): |
211 | + output = '' |
212 | + self.formatter.format_output(output, arguments.format) |
213 | + if unitdata._KV: |
214 | + unitdata._KV.flush() |
215 | + |
216 | + |
217 | +cmdline = CommandLine() |
218 | + |
219 | + |
220 | +def describe_arguments(func): |
221 | + """ |
222 | + Analyze a function's signature and return a data structure suitable for |
223 | + passing in as arguments to an argparse parser's add_argument() method.""" |
224 | + |
225 | + argspec = inspect.getargspec(func) |
226 | + # we should probably raise an exception somewhere if func includes **kwargs |
227 | + if argspec.defaults: |
228 | + positional_args = argspec.args[:-len(argspec.defaults)] |
229 | + keyword_names = argspec.args[-len(argspec.defaults):] |
230 | + for arg, default in zip(keyword_names, argspec.defaults): |
231 | + yield ('--{}'.format(arg),), {'default': default} |
232 | + else: |
233 | + positional_args = argspec.args |
234 | + |
235 | + for arg in positional_args: |
236 | + yield (arg,), {} |
237 | + if argspec.varargs: |
238 | + yield (argspec.varargs,), {'nargs': '*'} |
239 | |
240 | === added file 'tests/charmhelpers/cli/benchmark.py' |
241 | --- tests/charmhelpers/cli/benchmark.py 1970-01-01 00:00:00 +0000 |
242 | +++ tests/charmhelpers/cli/benchmark.py 2015-07-31 22:01:37 +0000 |
243 | @@ -0,0 +1,36 @@ |
244 | +# Copyright 2014-2015 Canonical Limited. |
245 | +# |
246 | +# This file is part of charm-helpers. |
247 | +# |
248 | +# charm-helpers is free software: you can redistribute it and/or modify |
249 | +# it under the terms of the GNU Lesser General Public License version 3 as |
250 | +# published by the Free Software Foundation. |
251 | +# |
252 | +# charm-helpers is distributed in the hope that it will be useful, |
253 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
254 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
255 | +# GNU Lesser General Public License for more details. |
256 | +# |
257 | +# You should have received a copy of the GNU Lesser General Public License |
258 | +# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
259 | + |
260 | +from . import cmdline |
261 | +from charmhelpers.contrib.benchmark import Benchmark |
262 | + |
263 | + |
264 | +@cmdline.subcommand(command_name='benchmark-start') |
265 | +def start(): |
266 | + Benchmark.start() |
267 | + |
268 | + |
269 | +@cmdline.subcommand(command_name='benchmark-finish') |
270 | +def finish(): |
271 | + Benchmark.finish() |
272 | + |
273 | + |
274 | +@cmdline.subcommand_builder('benchmark-composite', description="Set the benchmark composite score") |
275 | +def service(subparser): |
276 | + subparser.add_argument("value", help="The composite score.") |
277 | + subparser.add_argument("units", help="The units the composite score represents, i.e., 'reads/sec'.") |
278 | + subparser.add_argument("direction", help="'asc' if a lower score is better, 'desc' if a higher score is better.") |
279 | + return Benchmark.set_composite_score |
280 | |
281 | === added file 'tests/charmhelpers/cli/commands.py' |
282 | --- tests/charmhelpers/cli/commands.py 1970-01-01 00:00:00 +0000 |
283 | +++ tests/charmhelpers/cli/commands.py 2015-07-31 22:01:37 +0000 |
284 | @@ -0,0 +1,32 @@ |
285 | +# Copyright 2014-2015 Canonical Limited. |
286 | +# |
287 | +# This file is part of charm-helpers. |
288 | +# |
289 | +# charm-helpers is free software: you can redistribute it and/or modify |
290 | +# it under the terms of the GNU Lesser General Public License version 3 as |
291 | +# published by the Free Software Foundation. |
292 | +# |
293 | +# charm-helpers is distributed in the hope that it will be useful, |
294 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
295 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
296 | +# GNU Lesser General Public License for more details. |
297 | +# |
298 | +# You should have received a copy of the GNU Lesser General Public License |
299 | +# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
300 | + |
301 | +""" |
302 | +This module loads sub-modules into the python runtime so they can be |
303 | +discovered via the inspect module. In order to prevent flake8 from (rightfully) |
304 | +telling us these are unused modules, throw a ' # noqa' at the end of each import |
305 | +so that the warning is suppressed. |
306 | +""" |
307 | + |
308 | +from . import CommandLine # noqa |
309 | + |
310 | +""" |
311 | +Import the sub-modules which have decorated subcommands to register with chlp. |
312 | +""" |
313 | +import host # noqa |
314 | +import benchmark # noqa |
315 | +import unitdata # noqa |
316 | +from charmhelpers.core import hookenv # noqa |
317 | |
318 | === added file 'tests/charmhelpers/cli/host.py' |
319 | --- tests/charmhelpers/cli/host.py 1970-01-01 00:00:00 +0000 |
320 | +++ tests/charmhelpers/cli/host.py 2015-07-31 22:01:37 +0000 |
321 | @@ -0,0 +1,31 @@ |
322 | +# Copyright 2014-2015 Canonical Limited. |
323 | +# |
324 | +# This file is part of charm-helpers. |
325 | +# |
326 | +# charm-helpers is free software: you can redistribute it and/or modify |
327 | +# it under the terms of the GNU Lesser General Public License version 3 as |
328 | +# published by the Free Software Foundation. |
329 | +# |
330 | +# charm-helpers is distributed in the hope that it will be useful, |
331 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
332 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
333 | +# GNU Lesser General Public License for more details. |
334 | +# |
335 | +# You should have received a copy of the GNU Lesser General Public License |
336 | +# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
337 | + |
338 | +from . import cmdline |
339 | +from charmhelpers.core import host |
340 | + |
341 | + |
342 | +@cmdline.subcommand() |
343 | +def mounts(): |
344 | + "List mounts" |
345 | + return host.mounts() |
346 | + |
347 | + |
348 | +@cmdline.subcommand_builder('service', description="Control system services") |
349 | +def service(subparser): |
350 | + subparser.add_argument("action", help="The action to perform (start, stop, etc...)") |
351 | + subparser.add_argument("service_name", help="Name of the service to control") |
352 | + return host.service |
353 | |
354 | === added file 'tests/charmhelpers/cli/unitdata.py' |
355 | --- tests/charmhelpers/cli/unitdata.py 1970-01-01 00:00:00 +0000 |
356 | +++ tests/charmhelpers/cli/unitdata.py 2015-07-31 22:01:37 +0000 |
357 | @@ -0,0 +1,39 @@ |
358 | +# Copyright 2014-2015 Canonical Limited. |
359 | +# |
360 | +# This file is part of charm-helpers. |
361 | +# |
362 | +# charm-helpers is free software: you can redistribute it and/or modify |
363 | +# it under the terms of the GNU Lesser General Public License version 3 as |
364 | +# published by the Free Software Foundation. |
365 | +# |
366 | +# charm-helpers is distributed in the hope that it will be useful, |
367 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
368 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
369 | +# GNU Lesser General Public License for more details. |
370 | +# |
371 | +# You should have received a copy of the GNU Lesser General Public License |
372 | +# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
373 | + |
374 | +from . import cmdline |
375 | +from charmhelpers.core import unitdata |
376 | + |
377 | + |
378 | +@cmdline.subcommand_builder('unitdata', description="Store and retrieve data") |
379 | +def unitdata_cmd(subparser): |
380 | + nested = subparser.add_subparsers() |
381 | + get_cmd = nested.add_parser('get', help='Retrieve data') |
382 | + get_cmd.add_argument('key', help='Key to retrieve the value of') |
383 | + get_cmd.set_defaults(action='get', value=None) |
384 | + set_cmd = nested.add_parser('set', help='Store data') |
385 | + set_cmd.add_argument('key', help='Key to set') |
386 | + set_cmd.add_argument('value', help='Value to store') |
387 | + set_cmd.set_defaults(action='set') |
388 | + |
389 | + def _unitdata_cmd(action, key, value): |
390 | + if action == 'get': |
391 | + return unitdata.kv().get(key) |
392 | + elif action == 'set': |
393 | + unitdata.kv().set(key, value) |
394 | + unitdata.kv().flush() |
395 | + return '' |
396 | + return _unitdata_cmd |
397 | |
398 | === modified file 'tests/charmhelpers/core/hookenv.py' |
399 | --- tests/charmhelpers/core/hookenv.py 2015-07-31 13:11:07 +0000 |
400 | +++ tests/charmhelpers/core/hookenv.py 2015-07-31 22:01:37 +0000 |
401 | @@ -34,7 +34,22 @@ |
402 | import tempfile |
403 | from subprocess import CalledProcessError |
404 | |
405 | -from charmhelpers.cli import cmdline |
406 | +try: |
407 | + from charmhelpers.cli import cmdline |
408 | +except ImportError as e: |
409 | + # due to the anti-pattern of partially synching charmhelpers directly |
410 | + # into charms, it's possible that charmhelpers.cli is not available; |
411 | + # if that's the case, they don't really care about using the cli anyway, |
412 | + # so mock it out |
413 | + if str(e) == 'No module named cli': |
414 | + class cmdline(object): |
415 | + @classmethod |
416 | + def subcommand(cls, *args, **kwargs): |
417 | + def _wrap(func): |
418 | + return func |
419 | + return _wrap |
420 | + else: |
421 | + raise |
422 | |
423 | import six |
424 | if not six.PY3: |
charm_lint_check #7375 rabbitmq- server- next for billy-olsen mp266619
LINT OK: passed
Build: http:// 10.245. 162.77: 8080/job/ charm_lint_ check/7375/