[libvirt] [jenkins-ci PATCH v5 1/5] lcitool: use subparsers for commands

Daniel P. Berrangé berrange at redhat.com
Tue Feb 26 11:00:42 UTC 2019


Currently only a single global parser is used for all commands. This
means that every command accepts every argument which is undesirable as
users don't know what to pass. It also prevents the parser from
generating useful help information.

Python's argparse module supports multi-command binaries in a very easy
way by adding subparsers, each of which has their own distinct
arguments. It can then generate suitable help text on a per command
basis. This also means commands can use positional arguments for data
items that are always passed.

Signed-off-by: Daniel P. Berrangé <berrange at redhat.com>
---
 guests/README.markdown |  18 +++---
 guests/lcitool         | 129 +++++++++++++++++++++++------------------
 2 files changed, 80 insertions(+), 67 deletions(-)

diff --git a/guests/README.markdown b/guests/README.markdown
index ac9e4f6..6363bc9 100644
--- a/guests/README.markdown
+++ b/guests/README.markdown
@@ -10,38 +10,38 @@ Usage and examples
 
 There are two steps to bringing up a guest:
 
-* `lcitool -a install -h $guest` will perform an unattended installation
+* `lcitool install $guest` will perform an unattended installation
   of `$guest`. Not all guests can be installed this way: see the "FreeBSD"
   section below;
 
-* `lcitool -a update -h $guest -p $project` will go through all the
+* `lcitool update $guest $project` will go through all the
   post-installation configuration steps required to make the newly-created
   guest usable and ready to be used for building `$project`;
 
 Once those steps have been performed, maintainance will involve running:
 
-    lcitool -a update -h $guest -p $project
+    lcitool update $guest $project
 
 periodically to ensure the guest configuration is sane and all installed
 packages are updated.
 
 To get a list of known guests and projects, run
 
-    lcitool -a hosts
+    lcitool hosts
 
 and
 
-    lcitool -a projects
+    lcitool projects
 
 respectively. You can run operations involving multiple guests and projects
 at once by providing a list on the command line: for example, running
 
-    lcitool -a update -h '*fedora*' -p '*osinfo*'
+    lcitool update '*fedora*' '*osinfo*'
 
 will update all Fedora guests and get them ready to build libosinfo and
 related projects, while running
 
-    lcitool -a update -h all -p 'libvirt,libvirt+mingw*'
+    lcitool update all 'libvirt,libvirt+mingw*'
 
 will update all hosts and prepare them to build libvirt both as a native
 library and, where supported, as a Windows library using MinGW.
@@ -49,7 +49,7 @@ library and, where supported, as a Windows library using MinGW.
 Once hosts have been prepared following the steps above, you can use
 `lcitool` to perform builds as well: for example, running
 
-    lcitool -a build -h '*debian*' -p libvirt-python
+    lcitool build '*debian*' libvirt-python
 
 will fetch libvirt-python's `master` branch from the upstream repository
 and build it on all Debian hosts.
@@ -58,7 +58,7 @@ You can add more git repositories by tweaking the `git_urls` dictionary
 defined in `playbooks/build/jobs/defaults.yml` and then build arbitrary
 branches out of those with
 
-    lcitool -a build -h all -p libvirt -g github/cool-feature
+    lcitool build -g github/cool-feature all libvirt
 
 
 Host setup
diff --git a/guests/lcitool b/guests/lcitool
index c2dec81..ec467b7 100755
--- a/guests/lcitool
+++ b/guests/lcitool
@@ -304,45 +304,66 @@ class Application:
 
         self._parser = argparse.ArgumentParser(
             conflict_handler="resolve",
-            formatter_class=argparse.RawDescriptionHelpFormatter,
             description="libvirt CI guest management tool",
-            epilog=textwrap.dedent("""
-                common actions:
-                  install  perform unattended host installation
-                  update   prepare hosts and keep them updated
-                  build    build projects on hosts
+        )
 
-                informational actions:
-                  hosts     list all known hosts
-                  projects  list all known projects
+        subparsers = self._parser.add_subparsers(metavar="ACTION")
+        subparsers.required = True
 
-                uncommon actions:
-                  dockerfile  generate Dockerfile (doesn't access the host)
+        def add_hosts_arg(parser):
+            parser.add_argument(
+                "hosts",
+                help="list of hosts to act on (accepts globs)",
+            )
 
-                glob patterns are supported for HOSTS and PROJECTS
-            """),
-        )
-        self._parser.add_argument(
-            "-a",
-            metavar="ACTION",
-            required=True,
-            help="action to perform (see below)",
-        )
-        self._parser.add_argument(
-            "-h",
-            metavar="HOSTS",
-            help="list of hosts to act on",
-        )
-        self._parser.add_argument(
-            "-p",
-            metavar="PROJECTS",
-            help="list of projects to consider",
-        )
-        self._parser.add_argument(
-            "-g",
-            metavar="GIT_REVISION",
-            help="git revision to build (remote/branch)",
-        )
+        def add_projects_arg(parser):
+            parser.add_argument(
+                "projects",
+                help="list of projects to consider (accepts globs)",
+            )
+
+        def add_gitrev_arg(parser):
+            parser.add_argument(
+                "-g", "--git-revision",
+                help="git revision to build (remote/branch)",
+            )
+
+        installparser = subparsers.add_parser(
+            "install", help="perform unattended host installation")
+        installparser.set_defaults(func=self._action_install)
+
+        add_hosts_arg(installparser)
+
+        updateparser = subparsers.add_parser(
+            "update", help="prepare hosts and keep them updated")
+        updateparser.set_defaults(func=self._action_update)
+
+        add_hosts_arg(updateparser)
+        add_projects_arg(updateparser)
+        add_gitrev_arg(updateparser)
+
+        buildparser = subparsers.add_parser(
+            "build", help="build projects on hosts")
+        buildparser.set_defaults(func=self._action_build)
+
+        add_hosts_arg(buildparser)
+        add_projects_arg(buildparser)
+        add_gitrev_arg(buildparser)
+
+        hostsparser = subparsers.add_parser(
+            "hosts", help="list all known hosts")
+        hostsparser.set_defaults(func=self._action_hosts)
+
+        projectsparser = subparsers.add_parser(
+            "projects", help="list all known projects")
+        projectsparser.set_defaults(func=self._action_projects)
+
+        dockerfileparser = subparsers.add_parser(
+            "dockerfile", help="generate Dockerfile (doesn't access the host)")
+        dockerfileparser.set_defaults(func=self._action_dockerfile)
+
+        add_hosts_arg(dockerfileparser)
+        add_projects_arg(dockerfileparser)
 
     def _execute_playbook(self, playbook, hosts, projects, git_revision):
         base = Util.get_base()
@@ -403,20 +424,20 @@ class Application:
             raise Error(
                 "Failed to run {} on '{}': {}".format(playbook, hosts, ex))
 
-    def _action_hosts(self, _hosts, _projects, _revision):
+    def _action_hosts(self, args):
         for host in self._inventory.expand_pattern("all"):
             print(host)
 
-    def _action_projects(self, _hosts, _projects, _revision):
+    def _action_projects(self, args):
         for project in self._projects.expand_pattern("all"):
             print(project)
 
-    def _action_install(self, hosts, _projects, _revision):
+    def _action_install(self, args):
         base = Util.get_base()
 
         flavor = self._config.get_flavor()
 
-        for host in self._inventory.expand_pattern(hosts):
+        for host in self._inventory.expand_pattern(args.hosts):
             facts = self._inventory.get_facts(host)
 
             # Both memory size and disk size are stored as GiB in the
@@ -483,16 +504,18 @@ class Application:
             except Exception as ex:
                 raise Error("Failed to install '{}': {}".format(host, ex))
 
-    def _action_update(self, hosts, projects, git_revision):
-        self._execute_playbook("update", hosts, projects, git_revision)
+    def _action_update(self, args):
+        self._execute_playbook("update", args.hosts, args.projects,
+                               args.git_revision)
 
-    def _action_build(self, hosts, projects, git_revision):
-        self._execute_playbook("build", hosts, projects, git_revision)
+    def _action_build(self, args):
+        self._execute_playbook("build", args.hosts, args.projects,
+                               args.git_revision)
 
-    def _action_dockerfile(self, hosts, projects, _revision):
+    def _action_dockerfile(self, args):
         mappings = self._projects.get_mappings()
 
-        hosts = self._inventory.expand_pattern(hosts)
+        hosts = self._inventory.expand_pattern(args.hosts)
         if len(hosts) > 1:
             raise Error("Can't generate Dockerfile for multiple hosts")
         host = hosts[0]
@@ -506,7 +529,7 @@ class Application:
         if package_format not in ["deb", "rpm"]:
             raise Error("Host {} doesn't support Dockerfiles".format(host))
 
-        projects = self._projects.expand_pattern(projects)
+        projects = self._projects.expand_pattern(args.projects)
         for project in projects:
             if project not in facts["projects"]:
                 raise Error(
@@ -572,18 +595,8 @@ class Application:
                 """).format(**varmap))
 
     def run(self):
-        cmdline = self._parser.parse_args()
-        action = cmdline.a
-        hosts = cmdline.h
-        projects = cmdline.p
-        git_revision = cmdline.g
-
-        method = "_action_{}".format(action.replace("-", "_"))
-
-        if hasattr(self, method):
-            getattr(self, method).__call__(hosts, projects, git_revision)
-        else:
-            raise Error("Invalid action '{}'".format(action))
+        args = self._parser.parse_args()
+        args.func(args)
 
 
 if __name__ == "__main__":
-- 
2.20.1




More information about the libvir-list mailing list