[elbe-devel] [PATCH 2/2] elbepack: control: migrate to argparse

Thomas Weißschuh thomas.weissschuh at linutronix.de
Wed Jul 10 12:32:49 CEST 2024


argparse has various advantages of optparse:

* Autogenerated command synopsis.
* Required arguments.
* Flexible argument types.
* Subparsers.

Furthermore optparse is deprecated since Python 3.2 (2011).

Replace the custom action registry with a simple dispatch table, which
is both less code and easier to understand.

Signed-off-by: Thomas Weißschuh <thomas.weissschuh at linutronix.de>
---
 elbepack/commands/control.py | 146 ++++----
 elbepack/soapclient.py       | 775 ++++++++++++++-----------------------------
 2 files changed, 322 insertions(+), 599 deletions(-)

diff --git a/elbepack/commands/control.py b/elbepack/commands/control.py
index feed0c71b3cd..d0ecb9c286e1 100644
--- a/elbepack/commands/control.py
+++ b/elbepack/commands/control.py
@@ -2,125 +2,128 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
 # SPDX-FileCopyrightText: 2014-2017 Linutronix GmbH
 
+import argparse
 import socket
 import sys
 from http.client import BadStatusLine
-from optparse import OptionGroup, OptionParser
 from urllib.error import URLError
 
 from suds import WebFault
 
+from elbepack.cli import add_arguments_from_decorated_function
 from elbepack.config import cfg
 from elbepack.elbexml import ValidationMode
-from elbepack.soapclient import ClientAction, ElbeSoapClient
+from elbepack.soapclient import ElbeSoapClient, client_actions
 
 
 def run_command(argv):
 
-    oparser = OptionParser(usage='usage: elbe control [options] <command>')
+    aparser = argparse.ArgumentParser(prog='elbe control')
 
-    oparser.add_option('--host', dest='host', default=cfg['soaphost'],
-                       help='Ip or hostname of elbe-daemon.')
+    aparser.add_argument('--host', dest='host', default=cfg['soaphost'],
+                         help='Ip or hostname of elbe-daemon.')
 
-    oparser.add_option('--port', dest='port', default=cfg['soapport'],
-                       help='Port of soap itf on elbe-daemon.')
+    aparser.add_argument('--port', dest='port', default=cfg['soapport'],
+                         help='Port of soap itf on elbe-daemon.')
 
-    oparser.add_option('--pass', dest='passwd', default=cfg['elbepass'],
-                       help='Password (default is foo).')
+    aparser.add_argument('--pass', dest='passwd', default=cfg['elbepass'],
+                         help='Password (default is foo).')
 
-    oparser.add_option('--user', dest='user', default=cfg['elbeuser'],
-                       help='Username (default is root).')
+    aparser.add_argument('--user', dest='user', default=cfg['elbeuser'],
+                         help='Username (default is root).')
 
-    oparser.add_option(
+    aparser.add_argument(
         '--retries',
         dest='retries',
-        default='10',
+        type=int,
+        default=10,
         help='How many times to retry the connection to the server before '
              'giving up (default is 10 times, yielding 10 seconds).')
 
-    oparser.add_option(
+    aparser.add_argument(
         '--build-bin',
         action='store_true',
         dest='build_bin',
         default=False,
         help='Build binary repository CDROM, for exact reproduction.')
 
-    oparser.add_option('--build-sources', action='store_true',
-                       dest='build_sources', default=False,
-                       help='Build source CDROM')
+    aparser.add_argument('--build-sources', action='store_true',
+                         dest='build_sources', default=False,
+                         help='Build source CDROM')
 
-    oparser.add_option(
+    aparser.add_argument(
         '--skip-pbuilder',
         action='store_true',
         dest='skip_pbuilder',
         default=False,
         help="skip pbuilder section of XML (don't build packages)")
 
-    oparser.add_option('--output',
-                       dest='output', default=None,
-                       help='Output files to <directory>')
+    aparser.add_argument('--output',
+                         dest='output', default=None,
+                         help='Output files to <directory>')
 
-    oparser.add_option('--matches', dest='matches', default=False,
-                       help='Select files based on wildcard expression.')
+    aparser.add_argument('--matches', dest='matches', default=False,
+                         help='Select files based on wildcard expression.')
 
-    oparser.add_option('--pbuilder-only', action='store_true',
-                       dest='pbuilder_only', default=False,
-                       help='Only list/download pbuilder Files')
+    aparser.add_argument('--pbuilder-only', action='store_true',
+                         dest='pbuilder_only', default=False,
+                         help='Only list/download pbuilder Files')
 
-    oparser.add_option('--cpuset', default=-1, type='int',
-                       help='Limit cpuset of pbuilder commands (bitmask)'
-                            '(defaults to -1 for all CPUs)')
+    aparser.add_argument('--cpuset', default=-1, type=int,
+                         help='Limit cpuset of pbuilder commands (bitmask)'
+                              '(defaults to -1 for all CPUs)')
 
-    oparser.add_option('--profile', dest='profile', default='',
-                       help='Make pbuilder commands build the specified profile')
+    aparser.add_argument('--profile', dest='profile', default='',
+                         help='Make pbuilder commands build the specified profile')
 
-    oparser.add_option('--cross', dest='cross', default=False,
-                       action='store_true',
-                       help='Creates an environment for crossbuilding if '
-                            'combined with create. Combined with build it'
-                            ' will use this environment.')
+    aparser.add_argument('--cross', dest='cross', default=False,
+                         action='store_true',
+                         help='Creates an environment for crossbuilding if '
+                              'combined with create. Combined with build it'
+                              ' will use this environment.')
 
-    oparser.add_option('--no-ccache', dest='noccache', default=False,
-                       action='store_true',
-                       help="Deactivates the compiler cache 'ccache'")
+    aparser.add_argument('--no-ccache', dest='noccache', default=False,
+                         action='store_true',
+                         help="Deactivates the compiler cache 'ccache'")
 
-    oparser.add_option('--ccache-size', dest='ccachesize', default='10G',
-                       action='store', type='string',
-                       help='set a limit for the compiler cache size '
-                            '(should be a number followed by an optional '
-                            'suffix: k, M, G, T. Use 0 for no limit.)')
+    aparser.add_argument('--ccache-size', dest='ccachesize', default='10G',
+                         action='store', type=str,
+                         help='set a limit for the compiler cache size '
+                              '(should be a number followed by an optional '
+                              'suffix: k, M, G, T. Use 0 for no limit.)')
 
-    devel = OptionGroup(
-        oparser,
+    devel = aparser.add_argument_group(
         'options for elbe developers',
         "Caution: Don't use these options in a productive environment")
-    devel.add_option('--skip-urlcheck', action='store_true',
-                     dest='url_validation', default=ValidationMode.CHECK_ALL,
-                     help='Skip URL Check inside initvm')
+    devel.add_argument('--skip-urlcheck', action='store_true',
+                       dest='url_validation', default=ValidationMode.CHECK_ALL,
+                       help='Skip URL Check inside initvm')
 
-    devel.add_option('--debug', action='store_true',
-                     dest='debug', default=False,
-                     help='Enable debug mode.')
+    devel.add_argument('--debug', action='store_true',
+                       dest='debug', default=False,
+                       help='Enable debug mode.')
 
-    (opt, args) = oparser.parse_args(argv)
+    subparsers = aparser.add_subparsers(required=True)
 
-    if not args:
-        print('elbe control - no subcommand given', file=sys.stderr)
-        ClientAction.print_actions()
-        return
+    for action_name, do_action in client_actions.items():
+        action_parser = subparsers.add_parser(action_name)
+        action_parser.set_defaults(func=do_action)
+        add_arguments_from_decorated_function(action_parser, do_action)
+
+    args = aparser.parse_args(argv)
+    args.parser = aparser
 
     try:
         control = ElbeSoapClient(
-            opt.host,
-            opt.port,
-            opt.user,
-            opt.passwd,
-            debug=opt.debug,
-            retries=int(
-                opt.retries))
+            args.host,
+            args.port,
+            args.user,
+            args.passwd,
+            debug=args.debug,
+            retries=args.retries)
     except URLError:
         print(
-            f'Failed to connect to Soap server {opt.host}:{opt.port}\n',
+            f'Failed to connect to Soap server {args.host}:{args.port}\n',
             file=sys.stderr)
         print('', file=sys.stderr)
         print('Check, whether the initvm is actually running.', file=sys.stderr)
@@ -128,7 +131,7 @@ def run_command(argv):
         sys.exit(13)
     except socket.error:
         print(
-            f'Failed to connect to Soap server {opt.host}:{opt.port}\n',
+            f'Failed to connect to Soap server {args.host}:{args.port}\n',
             file=sys.stderr)
         print('', file=sys.stderr)
         print(
@@ -138,7 +141,7 @@ def run_command(argv):
         sys.exit(14)
     except BadStatusLine:
         print(
-            f'Failed to connect to Soap server {opt.host}:{opt.port}\n',
+            f'Failed to connect to Soap server {args.host}:{args.port}\n',
             file=sys.stderr)
         print('', file=sys.stderr)
         print('Check, whether the initvm is actually running.', file=sys.stderr)
@@ -146,14 +149,7 @@ def run_command(argv):
         sys.exit(15)
 
     try:
-        action = ClientAction(args[0])
-    except KeyError:
-        print('elbe control - unknown subcommand', file=sys.stderr)
-        ClientAction.print_actions()
-        sys.exit(25)
-
-    try:
-        action.execute(control, opt, args[1:])
+        args.func(control, args)
     except WebFault as e:
         print('Server returned error:', file=sys.stderr)
         print('', file=sys.stderr)
diff --git a/elbepack/soapclient.py b/elbepack/soapclient.py
index a3bab5865a18..21b3aed26e91 100644
--- a/elbepack/soapclient.py
+++ b/elbepack/soapclient.py
@@ -19,6 +19,7 @@ import debian.deb822
 from suds import WebFault
 from suds.client import Client
 
+from elbepack.cli import add_argument
 from elbepack.config import cfg
 from elbepack.elbexml import ElbeXML, ValidationMode
 from elbepack.version import elbe_version
@@ -125,593 +126,319 @@ class ElbeSoapClient:
             part = part + 1
 
 
-class ClientAction:
-    actiondict = {}
-
-    @classmethod
-    def register(cls, action):
-        cls.actiondict[action.tag] = action
-
-    @classmethod
-    def print_actions(cls):
-        print('available subcommands are:', file=sys.stderr)
-        for a in cls.actiondict:
-            print(f'   {a}', file=sys.stderr)
-
-    def __new__(cls, node):
-        action = cls.actiondict[node]
-        return object.__new__(action)
-
-    def __init__(self, node):
-        self.node = node
-
-    @staticmethod
-    def upload_file(append, build_dir, filename):
-
-        size = 1024 * 1024
-
-        with open(filename, 'rb') as f:
-
-            while True:
-
-                bin_data = f.read(size)
-                data = binascii.b2a_base64(bin_data)
-
-                if not isinstance(data, str):
-                    data = data.decode('ascii')
-
-                append(build_dir, data)
-
-                if len(bin_data) != size:
-                    break
-
-    def execute(self, _client, _opt, _args):
-        raise NotImplementedError('execute() not implemented')
-
-
-class RemoveLogAction(ClientAction):
-
-    tag = 'rm_log'
-
-    def execute(self, client, _opt, args):
-        if len(args) != 1:
-            print('usage: elbe control rm_log <project_dir>', file=sys.stderr)
-            sys.exit(172)
-
-        builddir = args[0]
-        client.service.rm_log(builddir)
-
-
-ClientAction.register(RemoveLogAction)
-
-
-class ListProjectsAction(ClientAction):
-
-    tag = 'list_projects'
-
-    def execute(self, client, _opt, _args):
-        projects = client.service.list_projects()
-
-        try:
-            for p in projects.SoapProject:
-                print(
-                    f'{p.builddir}\t{p.name}\t{p.version}\t{p.status}\t'
-                    f'{p.edit}')
-        except AttributeError:
-            print('No projects configured in initvm')
-
-
-ClientAction.register(ListProjectsAction)
-
-
-class ListUsersAction(ClientAction):
-
-    tag = 'list_users'
-
-    def execute(self, client, _opt, _args):
-        users = client.service.list_users()
-
-        for u in users.string:
-            print(u)
-
-
-ClientAction.register(ListUsersAction)
-
-
-class AddUserAction(ClientAction):
-    tag = 'add_user'
-
-    def execute(self, client, _opt, args):
-        if len(args) != 4:
-            print(
-                'usage: elbe control add_user <name> <fullname> <password> <email>',
-                file=sys.stderr)
-            sys.exit(173)
-
-        name = args[0]
-        fullname = args[1]
-        password = args[2]
-        email = args[3]
-
-        try:
-            client.service.add_user(name, fullname, password, email, False)
-        except WebFault as e:
-            if not hasattr(e.fault, 'faultstring'):
-                raise
-
-            if not e.fault.faultstring.endswith('already exists in the database'):
-                raise
-
-            # when we get here, the user we wanted to create already exists.
-            # that is fine, and we dont need to do anything now.
-
-
-ClientAction.register(AddUserAction)
-
-
-class CreateProjectAction(ClientAction):
-
-    tag = 'create_project'
-
-    def execute(self, client, _opt, _args):
-
-        uuid = client.service.new_project()
-        print(uuid)
-
-
-ClientAction.register(CreateProjectAction)
-
-
-class ResetProjectAction(ClientAction):
-
-    tag = 'reset_project'
-
-    def execute(self, client, _opt, args):
-        if len(args) != 1:
-            print(
-                'usage: elbe control reset_project <project_dir>',
-                file=sys.stderr)
-            sys.exit(174)
-
-        builddir = args[0]
-        client.service.reset_project(builddir)
-
-
-ClientAction.register(ResetProjectAction)
-
-
-class DeleteProjectAction(ClientAction):
-
-    tag = 'del_project'
-
-    def execute(self, client, _opt, args):
-        if len(args) != 1:
-            print(
-                'usage: elbe control del_project <project_dir>',
-                file=sys.stderr)
-            sys.exit(175)
-
-        builddir = args[0]
-        client.service.del_project(builddir)
-
-
-ClientAction.register(DeleteProjectAction)
-
-
-class SetXmlAction(ClientAction):
-
-    tag = 'set_xml'
-
-    def execute(self, client, _opt, args):
-        if len(args) != 2:
-            print(
-                'usage: elbe control set_xml <project_dir> <xml>',
-                file=sys.stderr)
-            sys.exit(176)
-
-        builddir = args[0]
-        filename = args[1]
-
-        try:
-            x = ElbeXML(
-                filename,
-                skip_validate=True,
-                url_validation=ValidationMode.NO_CHECK)
-        except IOError:
-            print(f'{filename} is not a valid elbe xml file')
-            sys.exit(177)
-
-        if not x.has('target'):
-            print("<target> is missing, this file can't be built in an initvm",
-                  file=sys.stderr)
-            sys.exit(178)
-
-        size = 1024 * 1024
-        part = 0
-        with open(filename, 'rb') as fp:
-            while True:
-
-                xml_base64 = binascii.b2a_base64(fp.read(size))
-
-                if not isinstance(xml_base64, str):
-                    xml_base64 = xml_base64.decode('ascii')
-
-                # finish upload
-                if len(xml_base64) == 1:
-                    part = client.service.upload_file(builddir,
-                                                      'source.xml',
-                                                      xml_base64,
-                                                      -1)
-                else:
-                    part = client.service.upload_file(builddir,
-                                                      'source.xml',
-                                                      xml_base64,
-                                                      part)
-                if part == -1:
-                    print('project busy, upload not allowed')
-                    return part
-                if part == -2:
-                    print('upload of xml finished')
-                    return 0
-
-
-ClientAction.register(SetXmlAction)
-
-
-class BuildAction(ClientAction):
-
-    tag = 'build'
-
-    def execute(self, client, opt, args):
-        if len(args) != 1:
-            print('usage: elbe control build <project_dir>', file=sys.stderr)
-            sys.exit(179)
-
-        builddir = args[0]
-        client.service.build(builddir, opt.build_bin, opt.build_sources,
-                             opt.skip_pbuilder)
-
-
-ClientAction.register(BuildAction)
+def _add_project_dir_argument(f):
+    return add_argument('project_dir')(f)
 
 
-class BuildSysrootAction(ClientAction):
+def _client_action_upload_file(append, build_dir, filename):
+    size = 1024 * 1024
 
-    tag = 'build_sysroot'
+    with open(filename, 'rb') as f:
 
-    def execute(self, client, _opt, args):
-        if len(args) != 1:
-            print(
-                'usage: elbe control build-sysroot <project_dir>',
-                file=sys.stderr)
-            sys.exit(180)
-
-        builddir = args[0]
-        client.service.build_sysroot(builddir)
-
-
-ClientAction.register(BuildSysrootAction)
-
-
-class BuildSDKAction(ClientAction):
-
-    tag = 'build_sdk'
-
-    def execute(self, client, _opt, args):
-        if len(args) != 1:
-            print(
-                'usage: elbe control build-sdk <project_dir>',
-                file=sys.stderr)
-            sys.exit(181)
-
-        builddir = args[0]
-        client.service.build_sdk(builddir)
-
-
-ClientAction.register(BuildSDKAction)
-
-
-class BuildCDROMsAction(ClientAction):
-
-    tag = 'build_cdroms'
-
-    def execute(self, client, opt, args):
-        if len(args) != 1:
-            print(
-                'usage: elbe control build-cdroms [--build-bin|--build-sources] <project_dir>',
-                file=sys.stderr)
-            sys.exit(182)
-
-        if (not opt.build_bin) and (not opt.build_sources):
-            print(
-                'usage: elbe control build-cdroms [--build-bin|--build-sources] <project_dir>',
-                file=sys.stderr)
-            sys.exit(183)
-
-        builddir = args[0]
-        client.service.build_cdroms(builddir, opt.build_bin, opt.build_sources)
-
-
-ClientAction.register(BuildCDROMsAction)
-
-
-class GetFileAction(ClientAction):
-
-    tag = 'get_file'
-
-    def execute(self, client, opt, args):
-        if len(args) != 2:
-            print(
-                'usage: elbe control get_file <project_dir> <file>',
-                file=sys.stderr)
-            sys.exit(184)
-
-        builddir = args[0]
-        filename = args[1]
-        dst_fname = filename
-
-        if opt.output:
-            dst = os.path.abspath(opt.output)
-            os.makedirs(dst, exist_ok=True)
-            dst_fname = str(os.path.join(dst, filename)).encode()
-
-        client.download_file(builddir, filename, dst_fname)
-        print(f'{dst_fname} saved')
-
-
-ClientAction.register(GetFileAction)
-
-
-class BuildChrootAction(ClientAction):
-
-    tag = 'build_chroot_tarball'
-
-    def execute(self, client, _opt, args):
-        if len(args) != 1:
-            print(
-                'usage: elbe control build_chroot_tarball <project_dir>',
-                file=sys.stderr)
-            sys.exit(185)
-
-        builddir = args[0]
-
-        client.service.build_chroot_tarball(builddir)
-
-
-ClientAction.register(BuildChrootAction)
-
-
-class DumpFileAction(ClientAction):
-
-    tag = 'dump_file'
-
-    def execute(self, client, _opt, args):
-        if len(args) != 2:
-            print(
-                'usage: elbe control dump_file <project_dir> <file>',
-                file=sys.stderr)
-            sys.exit(186)
-
-        builddir = args[0]
-        filename = args[1]
-
-        part = 0
         while True:
-            ret = client.service.get_file(builddir, filename, part)
-            if ret == 'FileNotFound':
-                print(ret, file=sys.stderr)
-                sys.exit(187)
-            if ret == 'EndOfFile':
-                return
-
-            os.write(sys.stdout.fileno(), binascii.a2b_base64(ret))
-            part = part + 1
-
-
-ClientAction.register(DumpFileAction)
-
-
-class GetFilesAction(ClientAction):
-
-    tag = 'get_files'
-
-    def execute(self, client, opt, args):
-        if len(args) != 1:
-            print(
-                'usage: elbe control get_files <project_dir>',
-                file=sys.stderr)
-            sys.exit(188)
 
-        builddir = args[0]
-        files = client.service.get_files(builddir)
+            bin_data = f.read(size)
+            data = binascii.b2a_base64(bin_data)
 
-        nfiles = 0
+            if not isinstance(data, str):
+                data = data.decode('ascii')
 
-        for f in files[0]:
-            if (opt.pbuilder_only and not f.name.startswith('pbuilder_cross')
-                    and not f.name.startswith('pbuilder')):
-                continue
-
-            if opt.matches and not fnmatch.fnmatch(f.name, opt.matches):
-                continue
-
-            nfiles += 1
-            try:
-                print(f'{f.name} \t({f.description})')
-            except AttributeError:
-                print(f'{f.name}')
-
-            if opt.output:
-                dst = os.path.abspath(opt.output)
-                os.makedirs(dst, exist_ok=True)
-                dst_fname = str(os.path.join(dst, os.path.basename(f.name)))
-                client.download_file(builddir, f.name, dst_fname)
-
-        if nfiles == 0:
-            sys.exit(189)
+            append(build_dir, data)
 
+            if len(bin_data) != size:
+                break
 
-ClientAction.register(GetFilesAction)
 
+ at _add_project_dir_argument
+def _remove_log(client, args):
+    client.service.rm_log(args.project_dir)
 
-class WaitProjectBusyAction(ClientAction):
 
-    tag = 'wait_busy'
+def _list_projects(client, args):
+    projects = client.service.list_projects()
 
-    def execute(self, client, _opt, args):
-        if len(args) != 1:
+    try:
+        for p in projects.SoapProject:
             print(
-                'usage: elbe control wait_busy <project_dir>',
-                file=sys.stderr)
-            sys.exit(190)
-
-        builddir = args[0]
-
-        while True:
-            try:
-                msg = client.service.get_project_busy(builddir)
-            # TODO the root cause of this problem is unclear. To enable a
-            # get more information print the exception and retry to see if
-            # the connection problem is just a temporary problem. This
-            # code should be reworked as soon as it's clear what is going on
-            # here
-            except socket.error as e:
-                print(str(e), file=sys.stderr)
-                print('socket error during wait busy occured, retry..',
-                      file=sys.stderr)
-                continue
+                f'{p.builddir}\t{p.name}\t{p.version}\t{p.status}\t'
+                f'{p.edit}')
+    except AttributeError:
+        print('No projects configured in initvm')
 
-            if not msg:
-                time.sleep(0.1)
-                continue
 
-            if msg == 'ELBE-FINISH':
-                break
+def _list_users(client, args):
+    users = client.service.list_users()
 
-            print(msg)
+    for u in users.string:
+        print(u)
 
-        # exited the while loop -> the project is not busy anymore,
-        # check, whether everything is ok.
 
-        prj = client.service.get_project(builddir)
-        if prj.status != 'build_done':
-            print(
-                'Project build was not successful, current status: '
-                f'{prj.status}',
-                file=sys.stderr)
-            sys.exit(191)
+ at add_argument('name')
+ at add_argument('fullname')
+ at add_argument('password')
+ at add_argument('email')
+def _add_user(client, args):
+    try:
+        client.service.add_user(args.name, args.fullname, args.password, args.email, False)
+    except WebFault as e:
+        if not hasattr(e.fault, 'faultstring'):
+            raise
 
+        if not e.fault.faultstring.endswith('already exists in the database'):
+            raise
 
-ClientAction.register(WaitProjectBusyAction)
+        # when we get here, the user we wanted to create already exists.
+        # that is fine, and we dont need to do anything now.
 
 
-class SetCdromAction(ClientAction):
+def _create_project(client, args):
+    uuid = client.service.new_project()
+    print(uuid)
 
-    tag = 'set_cdrom'
-
-    def execute(self, client, _opt, args):
-
-        if len(args) != 2:
-            print(
-                'usage: elbe control set_cdrom <project_dir> <cdrom file>',
-                file=sys.stderr)
-            sys.exit(192)
 
-        builddir = args[0]
-        filename = args[1]
+ at _add_project_dir_argument
+def _reset_project(client, args):
+    client.service.reset_project(args.project_dir)
 
-        client.service.start_cdrom(builddir)
-        self.upload_file(client.service.append_cdrom, builddir, filename)
-        client.service.finish_cdrom(builddir)
 
+ at _add_project_dir_argument
+def _delete_project(client, args):
+    client.service.del_project(args.project_dir)
 
-ClientAction.register(SetCdromAction)
 
+ at _add_project_dir_argument
+ at add_argument('xml')
+def _set_xml(client, args):
+    builddir = args.project_dir
+    filename = args.xml
 
-class SetOrigAction(ClientAction):
+    try:
+        x = ElbeXML(
+            filename,
+            skip_validate=True,
+            url_validation=ValidationMode.NO_CHECK)
+    except IOError:
+        print(f'{filename} is not a valid elbe xml file')
+        sys.exit(177)
 
-    tag = 'set_orig'
+    if not x.has('target'):
+        print("<target> is missing, this file can't be built in an initvm",
+              file=sys.stderr)
+        sys.exit(178)
 
-    def execute(self, client, _opt, args):
+    size = 1024 * 1024
+    part = 0
+    with open(filename, 'rb') as fp:
+        while True:
 
-        if len(args) != 2:
-            print(
-                'usage: elbe control set_orig <project_dir> <orig file>',
-                file=sys.stderr)
-            sys.exit(193)
+            xml_base64 = binascii.b2a_base64(fp.read(size))
 
-        builddir = args[0]
-        filename = args[1]
+            if not isinstance(xml_base64, str):
+                xml_base64 = xml_base64.decode('ascii')
 
-        client.service.start_upload_orig(builddir, os.path.basename(filename))
-        self.upload_file(client.service.append_upload_orig, builddir, filename)
-        client.service.finish_upload_orig(builddir)
+            # finish upload
+            if len(xml_base64) == 1:
+                part = client.service.upload_file(builddir,
+                                                  'source.xml',
+                                                  xml_base64,
+                                                  -1)
+            else:
+                part = client.service.upload_file(builddir,
+                                                  'source.xml',
+                                                  xml_base64,
+                                                  part)
+            if part == -1:
+                print('project busy, upload not allowed')
+                return part
+            if part == -2:
+                print('upload of xml finished')
+                return 0
 
 
-ClientAction.register(SetOrigAction)
+ at _add_project_dir_argument
+def _build(client, args):
+    client.service.build(args.project_dir, args.build_bin, args.build_sources, args.skip_pbuilder)
 
 
-class SetPdebuilderAction(ClientAction):
+ at _add_project_dir_argument
+def _build_sysroot(client, args):
+    client.service.build_sysroot(args.project_dir)
 
-    tag = 'set_pdebuild'
 
-    def execute(self, client, opt, args):
+ at _add_project_dir_argument
+def _build_sdk(client, args):
+    client.service.build_sdk(args.project_dir)
 
-        if len(args) != 2 and len(args) != 3:
-            print('usage: elbe control set_pdebuild '
-                  '<project_dir> <pdebuild file>', file=sys.stderr)
-            sys.exit(195)
 
-        builddir = args[0]
-        filename = args[1]
+ at _add_project_dir_argument
+def _build_cdroms(client, args):
+    if not args.build_bin and not args.build_sources:
+        args.parser.error('One of --build-bin or --build-sources needs to be specified')
 
-        client.service.start_pdebuild(builddir)
-        self.upload_file(client.service.append_pdebuild, builddir, filename)
-        client.service.finish_pdebuild(builddir, opt.cpuset,
-                                       opt.profile, opt.cross)
+    client.service.build_cdroms(args.project_dir, args.build_bin, args.build_sources)
 
 
-ClientAction.register(SetPdebuilderAction)
+ at _add_project_dir_argument
+ at add_argument('file')
+def _get_file(client, args):
+    if args.output:
+        dst = os.path.abspath(args.output)
+        os.makedirs(dst, exist_ok=True)
+        dst_fname = str(os.path.join(dst, args.file)).encode()
 
+    client.download_file(args.project_dir, args.file, dst_fname)
+    print(f'{args.file} saved')
 
-class BuildPbuilderAction(ClientAction):
 
-    tag = 'build_pbuilder'
+ at _add_project_dir_argument
+def _build_chroot(client, args):
+    client.service.build_chroot_tarball(args.project_dir)
 
-    def execute(self, client, opt, args):
-        if len(args) != 1:
-            print(
-                'usage: elbe control build_pbuilder <project_dir>',
-                file=sys.stderr)
-            sys.exit(196)
 
-        builddir = args[0]
-        client.service.build_pbuilder(builddir, opt.cross, opt.noccache,
-                                      opt.ccachesize)
+ at _add_project_dir_argument
+ at add_argument('file')
+def _dump_file(client, args):
+    part = 0
+    while True:
+        ret = client.service.get_file(args.project_dir, args.file, part)
+        if ret == 'FileNotFound':
+            print(ret, file=sys.stderr)
+            sys.exit(187)
+        if ret == 'EndOfFile':
+            return
 
+        os.write(sys.stdout.fileno(), binascii.a2b_base64(ret))
+        part = part + 1
 
-ClientAction.register(BuildPbuilderAction)
 
+ at _add_project_dir_argument
+def _get_files(client, args):
+    files = client.service.get_files(args.project_dir)
 
-class UpdatePbuilderAction(ClientAction):
+    nfiles = 0
 
-    tag = 'update_pbuilder'
+    for f in files[0]:
+        if (args.pbuilder_only and not f.name.startswith('pbuilder_cross')
+                and not f.name.startswith('pbuilder')):
+            continue
 
-    def execute(self, client, _opt, args):
-        if len(args) != 1:
-            print(
-                'usage: elbe control update_pbuilder <project_dir>',
-                file=sys.stderr)
-            sys.exit(197)
+        if args.matches and not fnmatch.fnmatch(f.name, args.matches):
+            continue
 
-        builddir = args[0]
-        client.service.update_pbuilder(builddir)
+        nfiles += 1
+        try:
+            print(f'{f.name} \t({f.description})')
+        except AttributeError:
+            print(f'{f.name}')
 
+        if args.output:
+            dst = os.path.abspath(args.output)
+            os.makedirs(dst, exist_ok=True)
+            dst_fname = str(os.path.join(dst, os.path.basename(f.name)))
+            client.download_file(args.project_dir, f.name, dst_fname)
 
-ClientAction.register(UpdatePbuilderAction)
+    if nfiles == 0:
+        sys.exit(189)
 
 
-class RepoAction(ClientAction):
+ at _add_project_dir_argument
+def _wait_busy(client, args):
+    while True:
+        try:
+            msg = client.service.get_project_busy(args.project_dir)
+        # TODO the root cause of this problem is unclear. To enable a
+        # get more information print the exception and retry to see if
+        # the connection problem is just a temporary problem. This
+        # code should be reworked as soon as it's clear what is going on
+        # here
+        except socket.error as e:
+            print(str(e), file=sys.stderr)
+            print('socket error during wait busy occured, retry..',
+                  file=sys.stderr)
+            continue
+
+        if not msg:
+            time.sleep(0.1)
+            continue
+
+        if msg == 'ELBE-FINISH':
+            break
+
+        print(msg)
+
+    # exited the while loop -> the project is not busy anymore,
+    # check, whether everything is ok.
+
+    prj = client.service.get_project(args.project_dir)
+    if prj.status != 'build_done':
+        print(
+            'Project build was not successful, current status: '
+            f'{prj.status}',
+            file=sys.stderr)
+        sys.exit(191)
+
+
+ at _add_project_dir_argument
+ at add_argument('cdrom_file')
+def _set_cdrom(client, args):
+    client.service.start_cdrom(args.project_dir)
+    _client_action_upload_file(client.service.append_cdrom, args.project_dir, args.cdrom_file)
+    client.service.finish_cdrom(args.project_dir)
+
+
+ at _add_project_dir_argument
+ at add_argument('orig_file')
+def _set_orig(client, args):
+    client.service.start_upload_orig(args.project_dir, os.path.basename(args.orig_file))
+    _client_action_upload_file(client.service.append_upload_orig, args.project_dir, args.orig_file)
+    client.service.finish_upload_orig(args.project_dir)
+
+
+ at _add_project_dir_argument
+ at add_argument('pdebuild_file')
+def _set_pdebuild(client, args):
+    client.service.start_pdebuild(args.project_dir)
+    _client_action_upload_file(client.service.append_pdebuild, args.project_dir, args.pdebuild_file)
+    client.service.finish_pdebuild(args.project_dir, args.cpuset, args.profile, args.cross)
+
+
+ at _add_project_dir_argument
+def _build_pbuilder(client, args):
+    client.service.build_pbuilder(args.project_dir, args.cross, args.noccache, args.ccachesize)
+
+
+ at _add_project_dir_argument
+def _update_pbuilder(client, args):
+    client.service.update_pbuilder(args.project_dir)
+
+
+client_actions = {
+    'rm_log':               _remove_log,
+    'list_projects':        _list_projects,
+    'list_users':           _list_users,
+    'add_user':             _add_user,
+    'create_project':       _create_project,
+    'reset_project':        _reset_project,
+    'del_project':          _delete_project,
+    'set_xml':              _set_xml,
+    'build':                _build,
+    'build_sysroot':        _build_sysroot,
+    'build_sdk':            _build_sdk,
+    'build_cdroms':         _build_cdroms,
+    'get_file':             _get_file,
+    'build_chroot_tarball': _build_chroot,
+    'dump_file':            _dump_file,
+    'get_files':            _get_files,
+    'wait_busy':            _wait_busy,
+    'set_cdrom':            _set_cdrom,
+    'set_orig':             _set_orig,
+    'set_pdebuild':         _set_pdebuild,
+    'build_pbuilder':       _build_pbuilder,
+    'update_pbuilder':      _update_pbuilder,
+}
+
+
+class RepoAction:
     repoactiondict = {}
 
     @classmethod

-- 
2.45.2



More information about the elbe-devel mailing list