Merge lp:~jameinel/juju-ci-tools/common-main into lp:juju-ci-tools

Proposed by John A Meinel
Status: Rejected
Rejected by: Aaron Bentley
Proposed branch: lp:~jameinel/juju-ci-tools/common-main
Merge into: lp:juju-ci-tools
Diff against target: 125 lines (+97/-0)
2 files modified
jujupy.py (+59/-0)
test_jujupy.py (+38/-0)
To merge this branch: bzr merge lp:~jameinel/juju-ci-tools/common-main
Reviewer Review Type Date Requested Status
Aaron Bentley Pending
Review via email: mp+262306@code.launchpad.net

Description of the change

This is a request-for-comments about some work I started doing.

Specifically, I started to write a new assess_* module and realized that I was copy & pasting a lot of boilerplate code around parsing arguments and setting up the environment and saving logs if there are any problems.

So I wanted to factor this out into a common helper that can just get reused.

I wanted to check if people are in favor of this before I went too far on it.

One big potential concern is that I'm more comfortable adding 'named' arguments rather than positional arguments, but some of them are going to still be required (environment name, log directory, etc).
I can put them as positional arguments, but then we'd want to figure out how to do that in a common fashion. (Is juju_path always the first position on the command line, or is it always the first position after the command's custom args, etc.)
I went with passing in an ArgumentParser rather than just a custom args func because I would then also need to start passing in all the other help text that you want to have unique to the script (the __init__ to ArgumentParser takes a lot of that stuff).

I don't think this is ready to land as is, because nothing is using it. The actual 'standard_main' function isn't directly tested, because it triggers make_client and bootstrap, etc. But I noticed the main() functions of other scripts weren't tested either, and at least this way there would be 1 untested code path, instead of 10 of them.

To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) wrote :

Hi John. Been ages since I reviewed your code!

I'm not comfortable with mandatory "optional" arguments like --environment. It's clearer if mandatory arguments are positional. The argparse docs note: Required options are generally considered bad form because users expect options to be optional, and thus they should be avoided when possible.

See also inline comments.

Revision history for this message
Aaron Bentley (abentley) wrote :

I believe this is superseded by BootstrapManager.

Unmerged revisions

991. By John A Meinel

Start working on a common 'main' implementation.

I was about to copy and paste main and parse_args for yet another assess function.
It seemed useful to have a common implementation so we don't have to touch them all the time.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'jujupy.py'
2--- jujupy.py 2015-06-15 19:14:14 +0000
3+++ jujupy.py 2015-06-18 09:16:03 +0000
4@@ -996,3 +996,62 @@
5 self.last_group = group
6 self.ticks = 0
7 self.wrap_offset = lead_length if lead_length < self.wrap_width else 0
8+
9+
10+def add_standard_args(parser):
11+ """Add standard arguments to a python Argument parser"""
12+ parser.add_argument(
13+ '--debug', action='store_true', default=False,
14+ help='Use --debug juju logging.')
15+ parser.add_argument(
16+ '--juju-path', default='/usr/bin', help='Directory your juju binary lives in.')
17+ parser.add_argument(
18+ '--environment', '-e', help='Juju environment name to run tests in.')
19+ parser.add_argument('--logs', default='./logs', help='Directory to store logs in.')
20+ parser.add_argument('--temp-env-name', default=None,
21+ help='Temporary environment name to use for this test.')
22+
23+
24+def standard_main(action_func, parser):
25+ """A helpful main function that handles setting up an environment and client.
26+
27+ This handles all the boilerplate setup and teardown and log saving that all
28+ scripts should have. You supply the actual actions to be run on the environment,
29+ but if there are any errors the logs will be gathered, etc.
30+
31+ :param action_func: A function that takes a 'client' as a parameter and
32+ runs whatever is necessary on the environment.
33+ :param parser: An ArgumentParser that has been initialized with whatever
34+ custom arguments you need to support. We will also add support for the
35+ standard parameters that we need for make_client() and handling saving
36+ of log files, etc.
37+ """
38+ add_standard_args(parser)
39+ args = parser.parse_args()
40+ log_dir = args.logs
41+
42+ client = make_client(
43+ args.juju_path, args.debug, args.environment, args.temp_env_name)
44+ client.destroy_environment()
45+ juju_home = get_juju_home()
46+ bootstrap_host = None
47+ try:
48+ with temp_bootstrap_env(juju_home, client):
49+ client.bootstrap()
50+ bootstrap_host = get_machine_dns_name(client, 0)
51+ client.get_status(60)
52+ # This is the standard boilerplate. Bootstrap an environment, run
53+ # status to make sure that we are up and running.
54+ action_func(client)
55+ except Exception as e:
56+ logging.exception(e)
57+ try:
58+ if bootstrap_host is None:
59+ bootstrap_host = parse_new_state_server_from_error(e)
60+ dump_env_logs(client, bootstrap_host, log_dir)
61+ except Exception as e:
62+ print_now("exception while dumping logs:\n")
63+ logging.exception(e)
64+ sys.exit(1)
65+ finally:
66+ client.destroy_environment()
67
68=== modified file 'test_jujupy.py'
69--- test_jujupy.py 2015-06-15 19:14:14 +0000
70+++ test_jujupy.py 2015-06-18 09:16:03 +0000
71@@ -1,5 +1,9 @@
72 __metaclass__ = type
73
74+from argparse import (
75+ ArgumentParser,
76+ Namespace,
77+)
78 from collections import defaultdict
79 from contextlib import contextmanager
80 from datetime import (
81@@ -27,6 +31,7 @@
82 NoSuchEnvironment,
83 )
84 from jujupy import (
85+ add_standard_args,
86 CannotConnectEnv,
87 Environment,
88 EnvJujuClient,
89@@ -2398,3 +2403,36 @@
90 def test_parse_new_state_server_from_error_no_output(self):
91 address = parse_new_state_server_from_error(Exception())
92 self.assertIs(None, address)
93+
94+
95+class TestAddStandardArgs(TestCase):
96+
97+ def setUp(self):
98+ super(TestAddStandardArgs, self).setUp()
99+ self.parser = ArgumentParser('test parser')
100+ add_standard_args(self.parser)
101+
102+ def assertArgs(self, args, **changes):
103+ namespace = Namespace(
104+ juju_path='/usr/bin', environment=None, logs='./logs',
105+ temp_env_name=None, debug=False)
106+ for key, value in changes.items():
107+ setattr(namespace, key, value)
108+ args = self.parser.parse_args(args)
109+ self.assertEqual(args, namespace)
110+
111+ def test_no_args(self):
112+ self.assertArgs([]) # No arg changes
113+
114+ def test_environment(self):
115+ self.assertArgs(["-e", "foo"], environment='foo')
116+ self.assertArgs(["--environment", "bar"], environment='bar')
117+
118+ def test_debug(self):
119+ self.assertArgs(["--debug"], debug=True)
120+
121+ def test_juju_path(self):
122+ self.assertArgs(["--juju-path", "/home/foo/bar"], juju_path='/home/foo/bar')
123+
124+ def test_log_path(self):
125+ self.assertArgs(["--logs", "/home/foo/logs"], logs='/home/foo/logs')

Subscribers

People subscribed via source and target branches