Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

switched 'argparse' to 'argh'; renamed vc commands: 'commit'->'checkp…

…oint', 'status'->'diff'
  • Loading branch information...
commit 14e8ccbb50e9e17b95a2f2a0d2cd0af5d90ca22b 1 parent 02fcf71
Mika Eloranta authored December 04, 2010
21  README.rst
Source Rendered
@@ -48,18 +48,23 @@ Installation
48 48
 NOTE: during installation the following packages and their dependencies are
49 49
 automatically installed from PyPI_:
50 50
 
51  
-* paramiko_ (SSH)
52  
-* boto_ (`Amazon EC2`_)
53 51
 * `path.py`_ (directory and file management)
54  
-* argparse_ (command-line argument parsing)
55  
-* cheetah_ (template language)
  52
+* Cheetah_ (template language)
  53
+* Argh_ (command-line argument parsing)
  54
+
  55
+Installing the following Python libraries will add optional functionality:
  56
+
  57
+* Paramiko_ (Remote node control using SSH)
  58
+* GitPython_ (Version controlling the repository with Git)
  59
+* Boto_ (`Amazon EC2`_ virtual machine provisioning)
56 60
 
57 61
 .. _`Amazon EC2`: http://aws.amazon.com/ec2/
58  
-.. _paramiko: http://pypi.python.org/pypi/paramiko
59  
-.. _boto: http://pypi.python.org/pypi/boto
  62
+.. _Paramiko: http://pypi.python.org/pypi/paramiko
  63
+.. _Boto: http://pypi.python.org/pypi/boto
60 64
 .. _`path.py`: http://pypi.python.org/pypi/path.py
61  
-.. _argparse: http://pypi.python.org/pypi/argparse
62  
-.. _cheetah: http://pypi.python.org/pypi/Cheetah
  65
+.. _Argh: http://pypi.python.org/pypi/argh
  66
+.. _GitPython: http://pypi.python.org/pypi/GitPython
  67
+.. _Cheetah: http://pypi.python.org/pypi/Cheetah
63 68
 
64 69
 Installation steps
65 70
 ------------------
12  examples/puppet/inst-puppet.sh
@@ -2,7 +2,7 @@
2 2
 
3 3
 set -e
4 4
 
5  
-AWS_KEYPAIR="mel-aws-us-east-1-mac"
  5
+AWS_KEYPAIR="aws-mel-fsc"
6 6
 REPO="$HOME/tmp/puppet"
7 7
 
8 8
 rm -rf $REPO
@@ -13,16 +13,12 @@ vc init
13 13
 
14 14
 add-config -cd ec2-deb6/ template/ec2-deb6 hacks
15 15
 set template\$ verify=bool:false
16  
-vc commit "added templates"
17  
-
18  
-add-node blah
19  
-add-config blah hacks -i template/ec2-deb6/hacks
20  
-add-node foobar -i blah
  16
+vc checkpoint "added templates"
21 17
 
22 18
 add-config -cd puppet-master/ software puppet-master-v1.0
23 19
 add-config -cd puppet-agent/ software puppet-agent-v1.0
24 20
 set software\$ verify=bool:false
25  
-vc commit "added software"
  21
+vc checkpoint "added software"
26 22
 
27 23
 add-node puppet/master -i template/ec2-deb6
28 24
 add-config puppet/master puppet-master -i software/puppet-master-v1.0
@@ -31,7 +27,7 @@ set puppet/master cloud.provider=aws-ec2 cloud.region=us-east-1 cloud.image=ami-
31 27
 add-node nodes/demo/server{id:02} -n2 -i template/ec2-deb6
32 28
 add-config nodes/demo/server puppet-agent -i software/puppet-agent-v1.0
33 29
 set nodes/demo/server cloud.provider=aws-ec2 cloud.region=us-east-1 cloud.image=ami-daf615b3 cloud.kernel=aki-6eaa4907 cloud.ramdisk=ari-42b95a2b cloud.type=m1.small cloud.key-pair=$AWS_KEYPAIR user=root
34  
-vc commit "added nodes"
  30
+vc checkpoint "added nodes"
35 31
 
36 32
 EOF
37 33
 
2  poni/core.py
@@ -272,7 +272,7 @@ def __init__(self, system, name, system_path, sub_count, extra=None):
272 272
 class ConfigMan:
273 273
     def __init__(self, root_dir, must_exist=True):
274 274
         # TODO: check repo.json from dir, option to start verification
275  
-        self.root_dir = root_dir
  275
+        self.root_dir = path(root_dir)
276 276
         self.system_root = self.root_dir / "system"
277 277
         self.config_path = self.root_dir / REPO_CONF_FILE
278 278
         self.node_cache = {}
582  poni/tool.py
@@ -11,7 +11,7 @@
11 11
 import sys
12 12
 import logging
13 13
 import shlex
14  
-import argparse
  14
+import argh
15 15
 import glob
16 16
 from path import path
17 17
 from . import config
@@ -26,259 +26,60 @@
26 26
 TOOL_NAME = "poni"
27 27
 
28 28
 
29  
-class Tool:
30  
-    """command-line tool"""
31  
-    def __init__(self, default_repo_path=None):
32  
-        self.log = logging.getLogger(TOOL_NAME)
33  
-        self.confman = None
34  
-        self.default_repo_path = default_repo_path
35  
-        self.parser = self.create_parser()
36  
-        self.sky = cloud.Sky()
  29
+def arg_full_match(method):
  30
+    wrap = argh.arg("-M", "--full-match", default=False, dest="full_match",
  31
+                    action="store_true", help="require full regexp match")
  32
+    return wrap(method)
37 33
 
38  
-    def create_parser(self):
39  
-        """Return an argparse parser object"""
40  
-        parser = argparse.ArgumentParser(prog=TOOL_NAME)
41  
-        parser.add_argument("-D", "--debug", dest="debug", default=False,
42  
-                            action="store_true", help="enable debug output")
43 34
 
44  
-        default_root = self.default_repo_path
45  
-        if not default_root:
46  
-            default_root = os.environ.get("%s_ROOT" % TOOL_NAME.upper())
  35
+def arg_verbose(method):
  36
+    wrap = argh.arg("-v", "--verbose", default=False, action="store_true",
  37
+                    help="verbose output")
  38
+    return wrap(method)
47 39
 
48  
-        if not default_root:
49  
-            default_root = str(path(os.environ["HOME"]) / (".%s" % TOOL_NAME))
50 40
 
51  
-        # parent parsers
52  
-        match_parent = argparse.ArgumentParser(add_help=False)
53  
-        match_parent.add_argument("-M", "--full-match", default=False,
54  
-                                  dest="full_match", action="store_true",
55  
-                                  help="require full regexp match")
  41
+def arg_flag(*args, **kwargs):
  42
+    return argh.arg(*args, default=False, action="store_true", **kwargs)
56 43
 
57  
-        verbose_parent = argparse.ArgumentParser(add_help=False)
58  
-        verbose_parent.add_argument("-v", "--verbose", default=False,
59  
-                                    action="store_true",
60  
-                                    help="verbose output")
61 44
 
62  
-        # generic arguments
63  
-        parser.add_argument(
64  
-            "-d", "--root-dir", dest="root_dir", default=default_root,
65  
-            metavar="DIR",
66  
-            help="repository root directory (default: %s)" % default_root)
67  
-
68  
-        # command sub-parser
69  
-        subparsers = parser.add_subparsers(dest="command",
70  
-                                           help="command to execute")
71  
-
72  
-        # init
73  
-        sub = subparsers.add_parser("init", help="init repository")
74  
-
75  
-        # script
76  
-        sub = subparsers.add_parser("script", help="run a script",
77  
-                                    parents=[verbose_parent])
78  
-        sub.add_argument('script', type=str, help='script file', nargs="?")
79  
-
80  
-        # add-system
81  
-        sub = subparsers.add_parser("add-system", help="add a sub-system")
82  
-        sub.add_argument('system', type=str, help='name of the system')
83  
-
84  
-        # add-node
85  
-        sub = subparsers.add_parser("add-node", help="add a new node",
86  
-                                    parents=[verbose_parent, match_parent])
87  
-        sub.add_argument('node', type=str, help='name of the node')
88  
-        sub.add_argument("-n", "--count", metavar="N..M", type=str,
89  
-                         default="1", help="number of nodes ('N' or 'N..M'")
90  
-        sub.add_argument("-H", "--host", metavar="HOST",
91  
-                         type=str, default="", dest="host",
92  
-                         help="host address")
93  
-        sub.add_argument("-i", "--inherit-node", metavar="NODE",
94  
-                         type=str, default="", dest="inherit_node",
95  
-                         help="inherit from node (regexp)")
96  
-        sub.add_argument("-c", "--copy-props", default=False,
97  
-                         action="store_true",
98  
-                         help="copy parent node's properties")
99  
-
100  
-        # list
101  
-        sub = subparsers.add_parser("list", help="list systems and nodes",
102  
-                                    parents=[match_parent])
103  
-        sub.add_argument('pattern', type=str, help='search pattern', nargs="?")
104  
-        sub.add_argument("-s", "--systems", dest="show_systems", default=False,
105  
-                         action="store_true", help="show systems")
106  
-        sub.add_argument("-c", "--config", dest="show_config", default=False,
107  
-                         action="store_true", help="show node configs")
108  
-        sub.add_argument("-n", "--config-prop", dest="show_config_prop",
109  
-                         default=False, action="store_true",
110  
-                         help="show node config properties")
111  
-        sub.add_argument("-C", "--controls", dest="show_controls",
112  
-                         default=False, action="store_true",
113  
-                         help="show node config control commands")
114  
-        sub.add_argument("-t", "--tree", dest="show_tree", default=False,
115  
-                         action="store_true", help="show node tree")
116  
-        sub.add_argument("-p", "--node-prop", dest="show_node_prop",
117  
-                         default=False, action="store_true",
118  
-                         help="show node properties")
119  
-        sub.add_argument("-o", "--cloud", dest="show_cloud_prop",
120  
-                         default=False, action="store_true",
121  
-                         help="show node cloud properties")
122  
-        sub.add_argument("-q", "--query-status", dest="query_status",
123  
-                         default=False, action="store_true",
124  
-                         help="query and show cloud node status")
125  
-        sub.add_argument("-i", "--inherits", dest="show_inherits",
126  
-                         default=False, action="store_true",
127  
-                         help="show node and config inheritances")
128  
-
129  
-        # add-config
130  
-        sub = subparsers.add_parser("add-config",
131  
-                                    help="add a config to node(s)",
132  
-                                    parents=[verbose_parent, match_parent])
133  
-        sub.add_argument('nodes', type=str, help='target nodes (regexp)')
134  
-        sub.add_argument('config', type=str, help='name of the config')
135  
-        sub.add_argument("-i", "--inherit", metavar="CONFIG",
136  
-                         type=str, default="", dest="inherit_config",
137  
-                         help="inherit from config (regexp)")
138  
-        sub.add_argument("-d", "--copy-dir", metavar="DIR",
139  
-                         type=str, default="", dest="copy_dir",
140  
-                         help="copy config files from DIR")
141  
-        sub.add_argument("-c", "--create-node", default=False,
142  
-                         action="store_true",
143  
-                         help="create node if it does not exist")
144  
-
145  
-        # verify
146  
-        sub = subparsers.add_parser("verify", help="verify local node configs",
147  
-                                    parents=[match_parent])
148  
-        sub.add_argument('nodes', type=str, help='target nodes (regexp)',
149  
-                         nargs="?")
150  
-
151  
-        # show
152  
-        sub = subparsers.add_parser("show", help="show node configs",
153  
-                                    parents=[verbose_parent, match_parent])
154  
-        sub.add_argument('nodes', type=str, help='target nodes (regexp)',
155  
-                         nargs="?")
156  
-        sub.add_argument("-d", "--show-dynamic", dest="show_dynamic",
157  
-                         default=False, action="store_true",
158  
-                         help="show dynamic configuration")
159  
-
160  
-        sub = subparsers.add_parser("deploy", help="deploy node configs",
161  
-                                    parents=[verbose_parent, match_parent])
162  
-        sub.add_argument('nodes', type=str, help='target nodes (regexp)',
163  
-                         nargs="?")
164  
-
165  
-        # control
166  
-        sub = subparsers.add_parser(
167  
-            "control", help="run control command over node configs")
168  
-        sub.add_argument('nodes', type=str, help='target nodes (regexp)')
169  
-        sub.add_argument('configs', type=str, help='target configs (regexp)')
170  
-        sub.add_argument('control', type=str, help='name of command')
171  
-        sub.add_argument('arg', type=str, help='command arg', nargs="*")
172  
-
173  
-        # remote
174  
-        remote = subparsers.add_parser("remote", help="run remote commands")
175  
-        remote_sub = remote.add_subparsers(dest="rcmd",
176  
-                                           help="command to execute")
177  
-
178  
-        # remote exec
179  
-        r_exec = remote_sub.add_parser("exec", help="run shell command",
180  
-                                       parents=[verbose_parent, match_parent])
181  
-        r_exec.add_argument('nodes', type=str, help='target nodes (regexp)')
182  
-        r_exec.add_argument('cmd', type=str, help='command to execute')
183  
-
184  
-        # remote shell
185  
-        r_shell = remote_sub.add_parser("shell", help="interactive shell",
186  
-                                        parents=[verbose_parent, match_parent])
187  
-        r_shell.add_argument('nodes', type=str, help='target nodes (regexp)')
188  
-
189  
-        # audit
190  
-        sub = subparsers.add_parser("audit", help="audit active node configs",
191  
-                                    parents=[verbose_parent, match_parent])
192  
-        sub.add_argument('nodes', type=str, help='target nodes (regexp)',
193  
-                         nargs="?")
194  
-        sub.add_argument("-d", "--diff", dest="show_diff", default=False,
195  
-                         action="store_true", help="show config diffs")
196  
-
197  
-        sub = subparsers.add_parser("set", help="set system/node properties",
198  
-                                    parents=[verbose_parent, match_parent])
199  
-        sub.add_argument('target', type=str,
200  
-                         help='target systems/nodes (regexp)')
201  
-        sub.add_argument('property', type=str, nargs="+",
202  
-                         help='property and value ("prop=value")')
203  
-
204  
-        # import
205  
-        sub = subparsers.add_parser("import", help="import nodes/configs",
206  
-                                    parents=[verbose_parent])
207  
-        sub.add_argument('source', type=path, help='source dir/file',
208  
-                         nargs="+")
209  
-
210  
-        # cloud
211  
-        cloud_p = subparsers.add_parser("cloud", help="manage cloud nodes")
212  
-        cloud_sub = cloud_p.add_subparsers(dest="ccmd",
213  
-                                           help="command to execute")
214  
-
215  
-        # cloud init
216  
-        sub = cloud_sub.add_parser("init",
217  
-                                   help="reserve a cloud image for nodes",
218  
-                                   parents=[match_parent])
219  
-        sub.add_argument('target', type=str,
220  
-                         help='target systems/nodes (regexp)')
221  
-        sub.add_argument("--reinit", dest="reinit", default=False,
222  
-                         action="store_true", help="re-initialize cloud image")
223  
-        sub.add_argument("--wait", dest="wait", default=False,
224  
-                         action="store_true",
225  
-                         help="wait for instance to start")
226  
-
227  
-        # cloud update
228  
-        sub = cloud_sub.add_parser("update",
229  
-                                   help="update cloud instance properties",
230  
-                                   parents=[match_parent])
231  
-        sub.add_argument('target', type=str,
232  
-                         help='target systems/nodes (regexp)')
233  
-
234  
-        # cloud terminate
235  
-        sub = cloud_sub.add_parser("terminate",
236  
-                                   help="terminate cloud instances",
237  
-                                   parents=[match_parent])
238  
-        sub.add_argument('target', type=str,
239  
-                         help='target systems/nodes (regexp)')
240  
-
241  
-        # cloud wait
242  
-        sub = cloud_sub.add_parser(
243  
-            "wait", help="wait cloud instances to reach a state",
244  
-            parents=[match_parent])
245  
-        sub.add_argument('target', type=str,
246  
-                         help='target systems/nodes (regexp)')
247  
-        sub.add_argument('--state', type=str, default="running",
248  
-                         help="target instance state, default: 'running'")
249  
-
250  
-        # vc
251  
-        vc_p = subparsers.add_parser("vc", help="version control")
252  
-        vc_sub = vc_p.add_subparsers(dest="vcmd",
253  
-                                     help="command to execute")
254  
-
255  
-        # vc init
256  
-        sub = vc_sub.add_parser("init", help="init version control in repo")
257  
-
258  
-        # vc status
259  
-        sub = vc_sub.add_parser("status", help="show working tree status")
260  
-
261  
-        # vc commit
262  
-        sub = vc_sub.add_parser("commit", help="commit all changes")
263  
-        sub.add_argument('message', type=str,
264  
-                         help='commit message')
265  
-
266  
-        return parser
  45
+class Tool:
  46
+    """command-line tool"""
  47
+    def __init__(self, default_repo_path=None):
  48
+        self.log = logging.getLogger(TOOL_NAME)
  49
+        self.default_repo_path = default_repo_path
  50
+        self.sky = cloud.Sky()
  51
+        self.parser = self.create_parser()
267 52
 
  53
+    @argh.alias("add-system")
  54
+    @argh.arg('system', type=str, help='system name')
268 55
     def handle_add_system(self, arg):
269  
-        system_dir = self.confman.create_system(arg.system)
  56
+        """add a sub-system"""
  57
+        confman = core.ConfigMan(arg.root_dir)
  58
+        system_dir = confman.create_system(arg.system)
270 59
         self.log.debug("created: %s", system_dir)
271 60
 
  61
+    @argh.alias("init")
272 62
     def handle_init(self, arg):
273  
-        self.confman.init_repo()
  63
+        """init repository"""
  64
+        confman = core.ConfigMan(arg.root_dir, must_exist=False)
  65
+        confman.init_repo()
274 66
 
  67
+    @argh.alias("import")
  68
+    @arg_verbose
  69
+    @argh.arg('source', type=path, help='source dir/file', nargs="+")
275 70
     def handle_import(self, arg):
  71
+        """import nodes/configs"""
  72
+        confman = core.ConfigMan(arg.root_dir)
276 73
         for glob_pattern in arg.source:
277 74
             for source_path in glob.glob(glob_pattern):
278 75
                 source = importer.get_importer(source_path)
279  
-                source.import_to(self.confman, verbose=arg.verbose)
  76
+                source.import_to(confman, verbose=arg.verbose)
280 77
 
  78
+    @argh.alias("script")
  79
+    @arg_verbose
  80
+    @argh.arg('script', type=str, help='script file', nargs="?")
281 81
     def handle_script(self, arg):
  82
+        """run commands from a script file"""
282 83
         try:
283 84
             if arg.script:
284 85
                 lines = file(arg.script).readlines()
@@ -287,26 +88,40 @@ def handle_script(self, arg):
287 88
         except (OSError, IOError), error:
288 89
             raise errors.Error("%s: %s" % (error.__class__.__name__, error))
289 90
 
290  
-        def wrap(arg):
291  
-            if " " in arg:
292  
-                return repr(arg)
  91
+        def wrap(args):
  92
+            if " " in args:
  93
+                return repr(args)
293 94
             else:
294  
-                return arg
  95
+                return args
  96
+
  97
+        def set_repo_path(sub_arg):
  98
+            sub_arg.root_dir = arg.root_dir
295 99
 
296 100
         for line in lines:
297 101
             args = shlex.split(line, comments=True)
298 102
             if not args:
299 103
                 continue
300 104
 
301  
-            sub_arg = self.parser.parse_args(args)
302 105
             if arg.verbose:
303 106
                 print "$ " + " ".join(wrap(a) for a in args)
304 107
 
305  
-            self.run_one(sub_arg)
306  
-
  108
+            self.parser.dispatch(argv=args, pre_call=set_repo_path)
  109
+
  110
+    @argh.alias("add-config")
  111
+    @arg_verbose
  112
+    @arg_full_match
  113
+    @argh.arg('nodes', type=str, help='target nodes (regexp)')
  114
+    @argh.arg('config', type=str, help='name of the config')
  115
+    @argh.arg("-i", "--inherit", metavar="CONFIG", type=str, default="",
  116
+              dest="inherit_config", help="inherit from config (regexp)")
  117
+    @argh.arg("-d", "--copy-dir", metavar="DIR", type=str, default="",
  118
+              dest="copy_dir", help="copy config files from DIR")
  119
+    @arg_flag("-c", "--create-node", help="create node if it does not exist")
307 120
     def handle_add_config(self, arg):
  121
+        """add a config to node(s)"""
  122
+        confman = core.ConfigMan(arg.root_dir)
308 123
         if arg.inherit_config:
309  
-            configs = list(self.confman.find_config(arg.inherit_config))
  124
+            configs = list(confman.find_config(arg.inherit_config))
310 125
             if len(configs) == 0:
311 126
                 raise errors.UserError(
312 127
                     "pattern %r does not match any configs" % (
@@ -327,11 +142,11 @@ def handle_add_config(self, arg):
327 142
             parent_config_name = None
328 143
 
329 144
         updates = []
330  
-        nodes = list(self.confman.find(arg.nodes, full_match=arg.full_match))
  145
+        nodes = list(confman.find(arg.nodes, full_match=arg.full_match))
331 146
         if arg.create_node and (not nodes):
332 147
             # node does not exist, create it as requested
333  
-            self.confman.create_node(arg.nodes)
334  
-            nodes = self.confman.find(arg.nodes, full_match=True)
  148
+            confman.create_node(arg.nodes)
  149
+            nodes = confman.find(arg.nodes, full_match=True)
335 150
 
336 151
         for node in nodes:
337 152
             existing = list(c for c in node.iter_configs()
@@ -354,23 +169,36 @@ def handle_add_config(self, arg):
354 169
             self.log.info("config %r added to: %s", arg.config,
355 170
                           ", ".join(updates))
356 171
 
  172
+    @argh.alias("exec")
  173
+    @arg_verbose
  174
+    @arg_full_match
  175
+    @argh.arg('nodes', type=str, help='target nodes (regexp)')
  176
+    @argh.arg('cmd', type=str, help='command to execute')
357 177
     def handle_remote_exec(self, arg):
  178
+        """run a shell-command"""
  179
+        confman = core.ConfigMan(arg.root_dir)
358 180
         def rexec(arg, node, remote):
359 181
             return remote.execute(arg.cmd)
360 182
 
361 183
         rexec.doc = "exec: %r" % arg.cmd
362  
-        return self.remote_op(arg, rexec)
  184
+        return self.remote_op(confman, arg, rexec)
363 185
 
  186
+    @argh.alias("shell")
  187
+    @arg_verbose
  188
+    @arg_full_match
  189
+    @argh.arg('nodes', type=str, help='target nodes (regexp)')
364 190
     def handle_remote_shell(self, arg):
  191
+        """start an interactive shell session"""
  192
+        confman = core.ConfigMan(arg.root_dir)
365 193
         def rshell(arg, node, remote):
366 194
             remote.shell()
367 195
 
368 196
         rshell.doc = "shell"
369  
-        self.remote_op(arg, rshell)
  197
+        self.remote_op(confman, arg, rshell)
370 198
 
371  
-    def remote_op(self, arg, op):
  199
+    def remote_op(self, confman, arg, op):
372 200
         ret = 0
373  
-        for node in self.confman.find(arg.nodes, full_match=arg.full_match):
  201
+        for node in confman.find(arg.nodes, full_match=arg.full_match):
374 202
             if not node.get("host"):
375 203
                 continue
376 204
 
@@ -405,31 +233,45 @@ def handle_control(self, arg):
405 233
                 if control_func:
406 234
                     control_func()
407 235
 
  236
+    @argh.alias("init")
408 237
     def handle_vc_init(self, arg):
409  
-        if self.confman.vc:
  238
+        """init version control in repo"""
  239
+        confman = core.ConfigMan(arg.root_dir)
  240
+        if confman.vc:
410 241
             raise errors.UserError(
411 242
                 "version control already initialized in this repo")
412 243
 
413  
-        self.confman.vc = vc.GitVersionControl(self.confman.root_dir,
414  
-                                               init=True)
  244
+        confman.vc = vc.GitVersionControl(confman.root_dir, init=True)
415 245
 
416  
-    def require_vc(self):
417  
-        if not self.confman.vc:
  246
+    def require_vc(self, confman):
  247
+        if not confman.vc:
418 248
             raise errors.UserError(
419 249
                 "version control not initialized in this repo")
420 250
 
421  
-    def handle_vc_status(self, arg):
422  
-        self.require_vc()
423  
-        for out in self.confman.vc.status():
  251
+    @argh.alias("diff")
  252
+    def handle_vc_diff(self, arg):
  253
+        """show repository working status diff"""
  254
+        confman = core.ConfigMan(arg.root_dir)
  255
+        self.require_vc(confman)
  256
+        for out in confman.vc.status():
424 257
             print out,
425 258
 
426  
-    def handle_vc_commit(self, arg):
427  
-        self.require_vc()
428  
-        self.confman.vc.commit_all(arg.message)
429  
-
  259
+    @argh.alias("checkpoint")
  260
+    @argh.arg('message', type=str, help='commit message')
  261
+    def handle_vc_checkpoint(self, arg):
  262
+        """commit all locally added and changed files in the repository"""
  263
+        confman = core.ConfigMan(arg.root_dir)
  264
+        self.require_vc(confman)
  265
+        confman.vc.commit_all(arg.message)
  266
+
  267
+    @argh.alias("terminate")
  268
+    @arg_full_match
  269
+    @argh.arg('target', type=str, help='target systems/nodes (regexp)')
430 270
     def handle_cloud_terminate(self, arg):
  271
+        """terminate cloud instances"""
  272
+        confman = core.ConfigMan(arg.root_dir)
431 273
         count = 0
432  
-        for node in self.confman.find(arg.target, full_match=arg.full_match):
  274
+        for node in confman.find(arg.target, full_match=arg.full_match):
433 275
             cloud_prop = node.get("cloud", {})
434 276
             if cloud_prop.get("instance"):
435 277
                 provider = self.sky.get_provider(cloud_prop)
@@ -439,9 +281,14 @@ def handle_cloud_terminate(self, arg):
439 281
 
440 282
         self.log.info("%s instances terminated", count)
441 283
 
  284
+    @argh.alias("update")
  285
+    @arg_full_match
  286
+    @argh.arg('target', type=str, help='target systems/nodes (regexp)')
442 287
     def handle_cloud_update(self, arg):
  288
+        """update node cloud instance properties"""
  289
+        confman = core.ConfigMan(arg.root_dir)
443 290
         nodes = []
444  
-        for node in self.confman.find(arg.target, full_match=arg.full_match):
  291
+        for node in confman.find(arg.target, full_match=arg.full_match):
445 292
             cloud_prop = node.get("cloud", {})
446 293
             if not cloud_prop.get("instance"):
447 294
                 continue
@@ -461,19 +308,33 @@ def handle_cloud_update(self, arg):
461 308
                 self.log.info("%s: updated: %s", node.name, change_str)
462 309
                 node.save()
463 310
 
  311
+    @argh.alias("wait")
  312
+    @arg_full_match
  313
+    @argh.arg('target', type=str, help='target systems/nodes (regexp)')
  314
+    @argh.arg('--state', type=str, default="running",
  315
+              help="target instance state, default: 'running'")
464 316
     def handle_cloud_wait(self, arg):
465  
-        return self.cloud_op(arg, False)
466  
-
  317
+        """wait cloud instances to reach a specific running state"""
  318
+        confman = core.ConfigMan(arg.root_dir)
  319
+        return self.cloud_op(confman, arg, False)
  320
+
  321
+    @argh.alias("init")
  322
+    @arg_full_match
  323
+    @argh.arg("target", type=str, help="target systems/nodes (regexp)")
  324
+    @arg_flag("--reinit", dest="reinit", help="re-initialize cloud image")
  325
+    @arg_flag("--wait", dest="wait", help="wait for instance to start")
467 326
     def handle_cloud_init(self, arg):
468  
-        return self.cloud_op(arg, True)
  327
+        """reserve and start a cloud instance for nodes"""
  328
+        confman = core.ConfigMan(arg.root_dir)
  329
+        return self.cloud_op(confman, arg, True)
469 330
 
470  
-    def cloud_op(self, arg, start):
  331
+    def cloud_op(self, confman, arg, start):
471 332
         nodes = []
472 333
 
473 334
         def printable(dict_obj):
474 335
             return ", ".join(("%s=%r" % item) for item in dict_obj.iteritems())
475 336
 
476  
-        for node in self.confman.find(arg.target, full_match=arg.full_match):
  337
+        for node in confman.find(arg.target, full_match=arg.full_match):
477 338
             cloud_prop = node.get("cloud", {})
478 339
             if not cloud_prop:
479 340
                 continue
@@ -531,13 +392,20 @@ def printable(dict_obj):
531 392
                     #self.log.info("%s update: %s", node.name,
532 393
                     #              printable(node_update))
533 394
 
  395
+    @argh.alias("set")
  396
+    @arg_verbose
  397
+    @arg_full_match
  398
+    @argh.arg('target', type=str, help='target systems/nodes (regexp)')
  399
+    @argh.arg('property', type=str, nargs="+", help="'name=[type:]value'")
534 400
     def handle_set(self, arg):
  401
+        """set system/node properties"""
  402
+        confman = core.ConfigMan(arg.root_dir)
535 403
         props = dict(util.parse_prop(p) for p in arg.property)
536 404
         logger = logging.info if arg.verbose else logging.debug
537 405
         changed_items = []
538 406
         found = False
539  
-        for item in self.confman.find(arg.target, systems=True,
540  
-                                      full_match=arg.full_match):
  407
+        for item in confman.find(arg.target, systems=True,
  408
+                                 full_match=arg.full_match):
541 409
             found = True
542 410
             changes = item.set_properties(props)
543 411
             for key, old_value, new_value in changes:
@@ -557,7 +425,7 @@ def handle_set(self, arg):
557 425
 
558 426
     def collect_all(self, manager):
559 427
         items = []
560  
-        for item in self.confman.find("."):
  428
+        for item in manager.confman.find("."):
561 429
             item.collect(manager)
562 430
             items.append(item)
563 431
 
@@ -568,8 +436,8 @@ def collect_all(self, manager):
568 436
 
569 437
         return items
570 438
 
571  
-    def verify_op(self, target, full_match=False, **verify_options):
572  
-        manager = config.Manager(self.confman)
  439
+    def verify_op(self, confman, target, full_match=False, **verify_options):
  440
+        manager = config.Manager(confman)
573 441
         self.collect_all(manager)
574 442
 
575 443
         if target:
@@ -586,23 +454,51 @@ def target_filter(item):
586 454
         manager.verify(callback=target_filter, **verify_options)
587 455
         return manager
588 456
 
  457
+    @argh.alias("show")
  458
+    @arg_verbose
  459
+    @arg_full_match
  460
+    @argh.arg('nodes', type=str, help='target nodes (regexp)', nargs="?")
  461
+    @arg_flag("-d", "--show-dynamic", dest="show_dynamic",
  462
+              help="show dynamic configuration")
589 463
     def handle_show(self, arg):
590  
-        manager = self.verify_op(arg.nodes, show=(not arg.show_dynamic),
  464
+        """render and show node config files"""
  465
+        confman = core.ConfigMan(arg.root_dir)
  466
+        manager = self.verify_op(confman, arg.nodes,
  467
+                                 show=(not arg.show_dynamic),
591 468
                                  full_match=arg.full_match)
592 469
         if arg.show_dynamic:
593 470
             for item in manager.dynamic_conf:
594 471
                 print item
595 472
 
  473
+    @argh.alias("deploy")
  474
+    @arg_verbose
  475
+    @arg_full_match
  476
+    @argh.arg('nodes', type=str, help='target nodes (regexp)', nargs="?")
596 477
     def handle_deploy(self, arg):
597  
-        self.verify_op(arg.nodes, show=False, deploy=True, verbose=arg.verbose,
598  
-                       full_match=arg.full_match)
599  
-
  478
+        """deploy node configs"""
  479
+        confman = core.ConfigMan(arg.root_dir)
  480
+        self.verify_op(confman, arg.nodes, show=False, deploy=True,
  481
+                       verbose=arg.verbose, full_match=arg.full_match)
  482
+
  483
+    @argh.alias("audit")
  484
+    @arg_verbose
  485
+    @arg_full_match
  486
+    @argh.arg('nodes', type=str, help='target nodes (regexp)', nargs="?")
  487
+    @arg_flag("-d", "--diff", dest="show_diff", help="show config diffs")
600 488
     def handle_audit(self, arg):
601  
-        self.verify_op(arg.nodes, show=False, deploy=False, audit=True,
602  
-                       show_diff=arg.show_diff, full_match=arg.full_match)
  489
+        """audit active node configs"""
  490
+        confman = core.ConfigMan(arg.root_dir)
  491
+        self.verify_op(confman, arg.nodes, show=False, deploy=False,
  492
+                       audit=True, show_diff=arg.show_diff,
  493
+                       full_match=arg.full_match)
603 494
 
  495
+    @argh.alias("verify")
  496
+    @arg_full_match
  497
+    @argh.arg('nodes', type=str, help='target nodes (regexp)', nargs="?")
604 498
     def handle_verify(self, arg):
605  
-        manager = self.verify_op(arg.nodes, show=False,
  499
+        """verify local node configs"""
  500
+        confman = core.ConfigMan(arg.root_dir)
  501
+        manager = self.verify_op(confman, arg.nodes, show=False,
606 502
                                  full_match=arg.full_match)
607 503
 
608 504
         if manager.error_count:
@@ -614,10 +510,24 @@ def handle_verify(self, arg):
614 510
         else:
615 511
             self.log.info("all [%d] files ok", len(manager.files))
616 512
 
  513
+    @argh.alias("add-node")
  514
+    @arg_verbose
  515
+    @arg_full_match
  516
+    @argh.arg('node', type=str,
  517
+              help="name of the node, '{id}' is replaced with the node number")
  518
+    @argh.arg("-n", "--count", metavar="N..M", type=str, default="1",
  519
+              help="number of nodes ('N' or 'N..M')")
  520
+    @argh.arg("-H", "--host", metavar="HOST", type=str, default="",
  521
+              dest="host", help="host address")
  522
+    @argh.arg("-i", "--inherit-node", metavar="NODE", type=str, default="",
  523
+              dest="inherit_node", help="inherit from node (regexp)")
  524
+    @arg_flag("-c", "--copy-props", help="copy parent node's properties")
617 525
     def handle_add_node(self, arg):
  526
+        """add a new node"""
  527
+        confman = core.ConfigMan(arg.root_dir)
618 528
         if arg.inherit_node:
619  
-            nodes = list(self.confman.find(arg.inherit_node,
620  
-                                           full_match=arg.full_match))
  529
+            nodes = list(confman.find(arg.inherit_node,
  530
+                                      full_match=arg.full_match))
621 531
             if len(nodes) == 0:
622 532
                 raise errors.UserError(
623 533
                     "pattern %r does not match any nodes" % (arg.inherit_node))
@@ -639,7 +549,7 @@ def handle_add_node(self, arg):
639 549
         for n in range(n, m):
640 550
             node_name = arg.node.format(id=n)
641 551
             host = arg.host.format(id=n)
642  
-            node_spec = self.confman.create_node(
  552
+            node_spec = confman.create_node(
643 553
                 node_name, host=host, parent_node_name=parent_node_name,
644 554
                 copy_props=arg.copy_props)
645 555
 
@@ -677,7 +587,26 @@ def color_path(self, output, item, name, is_config=False, is_node=False):
677 587
 
678 588
         return name
679 589
 
  590
+    @argh.alias("list")
  591
+    @arg_full_match
  592
+    @argh.arg('pattern', type=str, help='search pattern', nargs="?")
  593
+    @arg_flag("-s", "--systems", dest="show_systems", help="show systems")
  594
+    @arg_flag("-c", "--config", dest="show_config", help="show node configs")
  595
+    @arg_flag("-n", "--config-prop", dest="show_config_prop",
  596
+              help="show node config properties")
  597
+    @arg_flag("-C", "--controls", dest="show_controls",
  598
+              help="show node config control commands")
  599
+    @arg_flag("-t", "--tree", dest="show_tree", help="show node tree")
  600
+    @arg_flag("-p", "--node-prop", dest="show_node_prop",
  601
+              help="show node properties")
  602
+    @arg_flag("-o", "--cloud", dest="show_cloud_prop",
  603
+              help="show node cloud properties")
  604
+    @arg_flag("-q", "--query-status", help="query and show cloud node status")
  605
+    @arg_flag("-i", "--inherits", dest="show_inherits",
  606
+              help="show node and config inheritances")
680 607
     def handle_list(self, arg):
  608
+        """list systems and nodes"""
  609
+        confman = core.ConfigMan(arg.root_dir)
681 610
         output = colors.Output(sys.stdout)
682 611
         format_str = "%8s %s"
683 612
         HINDENT = 4 * " "
@@ -692,8 +621,8 @@ def norm_name(depth, name):
692 621
 
693 622
             return (INDENT * (depth-1)) + name
694 623
 
695  
-        for item in self.confman.find(arg.pattern, systems=arg.show_systems,
696  
-                                      full_match=arg.full_match):
  624
+        for item in confman.find(arg.pattern, systems=arg.show_systems,
  625
+                                 full_match=arg.full_match):
697 626
             name = norm_name(item["depth"], item.name)
698 627
             name = self.color_path(output, item, name)
699 628
 
@@ -731,7 +660,7 @@ def norm_name(depth, name):
731 660
 
732 661
                     if arg.show_config_prop:
733 662
                         output.sendline(format_str % ("confprop", "%s%s%s" % (
734  
-                                    HINDENT, INDENT * (item["depth"] - 1),
  663
+                                    HINDENT, INDENT * (item["depth"]),
735 664
                                     output.color_items(conf.iteritems()))))
736 665
 
737 666
 
@@ -749,18 +678,54 @@ def norm_name(depth, name):
749 678
                                              INDENT * (item["depth"] - 1),
750 679
                                              status)))
751 680
 
752  
-    def run(self, args=None):
753  
-        """
754  
-        Run a single command given as an 'args' list.
  681
+    def create_parser(self):
  682
+        default_root = self.default_repo_path
  683
+        if not default_root:
  684
+            default_root = os.environ.get("%s_ROOT" % TOOL_NAME.upper())
  685
+
  686
+        if not default_root:
  687
+            default_root = str(path(os.environ["HOME"]) / (".%s" % TOOL_NAME))
  688
+
  689
+        parser = argh.ArghParser()
  690
+        parser.add_argument("-D", "--debug", dest="debug", default=False,
  691
+                            action="store_true", help="enable debug output")
  692
+        parser.add_argument(
  693
+            "-d", "--root-dir", dest="root_dir", default=default_root,
  694
+            metavar="DIR",
  695
+            help="repository root directory (default: %s)" % default_root)
755 696
 
756  
-        Returns a non-zero integer on errors.
757  
-        """
758  
-        arg = self.parser.parse_args(args)
759  
-        must_exist = (arg.command not in ["script", "init"])
  697
+        parser.add_commands([
  698
+            self.handle_list, self.handle_add_system, self.handle_init,
  699
+            self.handle_import, self.handle_script, self.handle_add_config,
  700
+            self.handle_set, self.handle_show, self.handle_deploy,
  701
+            self.handle_audit, self.handle_verify, self.handle_add_node,
  702
+            ])
  703
+
  704
+        parser.add_commands([
  705
+                self.handle_cloud_init, self.handle_cloud_terminate,
  706
+                self.handle_cloud_update, self.handle_cloud_wait,
  707
+                ],
  708
+                            namespace="cloud", title="cloud operations",
  709
+                            help="command to execute")
  710
+
  711
+        parser.add_commands([
  712
+                self.handle_remote_exec, self.handle_remote_shell,
  713
+                ],
  714
+                            namespace="remote", title="remote operations",
  715
+                            help="command to execute")
  716
+
  717
+        parser.add_commands([
  718
+                self.handle_vc_init, self.handle_vc_diff,
  719
+                self.handle_vc_checkpoint,
  720
+                ],
  721
+                            namespace="vc", title="version-control operations",
  722
+                            help="command to execute")
760 723
 
761  
-        try:
762  
-            self.confman = core.ConfigMan(path(arg.root_dir),
763  
-                                          must_exist=must_exist)
  724
+        return parser
  725
+
  726
+    def run(self, args=None):
  727
+        def adjust_logging(arg):
  728
+            """tune the logging before executing commands"""
764 729
             if arg.debug:
765 730
                 logging.getLogger().setLevel(logging.DEBUG)
766 731
             else:
@@ -772,28 +737,13 @@ def run(self, args=None):
772 737
                 boto_logger = logging.getLogger('boto')
773 738
                 boto_logger.setLevel(logging.CRITICAL)
774 739
 
775  
-            return self.run_one(arg)
  740
+        try:
  741
+            exit_code = self.parser.dispatch(argv=args, pre_call=adjust_logging)
776 742
         except errors.Error, error:
777 743
             self.log.error("%s: %s", error.__class__.__name__, error)
778 744
             return -1
779  
-        finally:
780  
-            if self.confman:
781  
-                self.confman.cleanup()
782  
-
783  
-
784  
-    def run_one(self, arg):
785  
-        """Run a single command specified in the already parsed 'arg' object"""
786  
-        if arg.command == "remote":
787  
-            handler_name = "handle_remote_%s" % arg.rcmd.replace("-", "_")
788  
-        elif arg.command == "cloud":
789  
-            handler_name = "handle_cloud_%s" % arg.ccmd.replace("-", "_")
790  
-        elif arg.command == "vc":
791  
-            handler_name = "handle_vc_%s" % arg.vcmd.replace("-", "_")
792  
-        else:
793  
-            handler_name = "handle_%s" % arg.command.replace("-", "_")
794 745
 
795  
-        op = getattr(self, handler_name)
796  
-        return op(arg)
  746
+        return exit_code
797 747
 
798 748
     def main(self):
799 749
         """Setup logging and run a single command specified by sys.argv"""
2  setup.py
@@ -8,7 +8,7 @@
8 8
 if new_dir:
9 9
     os.chdir(new_dir)
10 10
 
11  
-depends = ["path.py", "paramiko", "cheetah", "boto", "GitPython"]
  11
+depends = ["path.py", "paramiko", "cheetah", "boto", "GitPython", "argh"]
12 12
 try:
13 13
     import json
14 14
 except ImportError:
2  tests/test_cmd_basic.py
@@ -13,7 +13,7 @@ def add_actions(self):
13 13
 class TestCommands(Helper):
14 14
     def init_repo(self):
15 15
         repo = self.temp_file()
16  
-        poni = tool.Tool(default_repo_path=str(repo))
  16
+        poni = tool.Tool(default_repo_path=repo)
17 17
         assert not poni.run(["init"])
18 18
         config = json.load(file(repo / "repo.json"))
19 19
         assert isinstance(config, dict)

0 notes on commit 14e8ccb

Please sign in to comment.
Something went wrong with that request. Please try again.