[elbe-devel] [PATCH 3/3] elbepack: initvmaction: use elbepack.cli error handling

Thomas Weißschuh thomas.weissschuh at linutronix.de
Thu Jul 18 14:47:26 CEST 2024


With the new elbepack.cli-based error handling the ad-hoc calls to
sys.exit() can be replaced by exceptions.

Signed-off-by: Thomas Weißschuh <thomas.weissschuh at linutronix.de>
---
 elbepack/initvmaction.py | 308 ++++++++++++++++-------------------------------
 1 file changed, 102 insertions(+), 206 deletions(-)

diff --git a/elbepack/initvmaction.py b/elbepack/initvmaction.py
index 51a618cee431..f3274989a9db 100644
--- a/elbepack/initvmaction.py
+++ b/elbepack/initvmaction.py
@@ -10,9 +10,11 @@ import shutil
 import socket
 import subprocess
 import sys
+import textwrap
 import time
 
 import elbepack
+from elbepack.cli import CliError, with_cli_details
 from elbepack.config import cfg
 from elbepack.directories import run_elbe
 from elbepack.elbexml import ElbeXML, ValidationError, ValidationMode
@@ -112,30 +114,26 @@ class InitVMAction:
                         break
 
                 if not self.conn:
-                    print('', file=sys.stderr)
-                    print('Accessing libvirt provider system not possible.', file=sys.stderr)
-                    print('Even after waiting 180 seconds.', file=sys.stderr)
-                    print("Make sure that package 'libvirt-daemon-system' is", file=sys.stderr)
-                    print('installed, and the service is running properly', file=sys.stderr)
-                    sys.exit(118)
+                    raise CliError(118, textwrap.dedent("""
+                        Accessing libvirt provider system not possible.
+                        Even after waiting 180 seconds.
+                        Make sure that package 'libvirt-daemon-system' is
+                        installed, and the service is running properly."""))
 
             elif verr.args[0].startswith('authentication unavailable'):
-                print('', file=sys.stderr)
-                print('Accessing libvirt provider system not allowed.', file=sys.stderr)
-                print('Users which want to use elbe'
-                      "need to be members of the 'libvirt' group.", file=sys.stderr)
-                print("'gpasswd -a <user> libvirt' and logging in again,", file=sys.stderr)
-                print('should fix the problem.', file=sys.stderr)
-                sys.exit(119)
+                raise CliError(119, textwrap.dedent("""
+                    Accessing libvirt provider system not allowed.
+                    Users which want to use elbe'
+                    need to be members of the 'libvirt' group.
+                    'gpasswd -a <user> libvirt' and logging in again,
+                    should fix the problem."""))
 
             elif verr.args[0].startswith('error from service: CheckAuthorization'):
-                print('', file=sys.stderr)
-                print('Accessing libvirt failed.', file=sys.stderr)
-                print('Probably entering the password for accssing libvirt', file=sys.stderr)
-                print("timed out. If this occured after 'elbe initvm create'", file=sys.stderr)
-                print("it should be safe to use 'elbe initvm start' to", file=sys.stderr)
-                print('continue.', file=sys.stderr)
-                sys.exit(120)
+                raise CliError(120, textwrap.dedent("""
+                    Accessing libvirt failed.
+                    Probably entering the password for accssing libvirt
+                    timed out. If this occured after 'elbe initvm create'
+                    it should be safe to use 'elbe initvm start' to continue."""))
 
             else:
                 # In case we get here, the exception is unknown, and we want to see it
@@ -148,7 +146,7 @@ class InitVMAction:
                 self.initvm = d
 
         if not self.initvm and initvmNeeded:
-            sys.exit(121)
+            raise CliError(121, 'No initvm available')
 
     def execute(self, _initvmdir, _opt, _args):
         raise NotImplementedError('execute() not implemented')
@@ -186,9 +184,8 @@ def test_soap_communication(sleep=10, wait=120):
             if ps.returncode == 0:
                 break
         if time.time() > stop:
-            print(f'Waited for {wait/60} minutes and the daemon is still not active.',
-                  file=sys.stderr)
-            sys.exit(123)
+            raise CliError(123,
+                           f'Waited for {wait/60} minutes and the daemon is still not active.')
         print('*', end='', flush=True)
         time.sleep(sleep)
 
@@ -200,8 +197,7 @@ def check_initvm_dir(initvmdir):
             print('Using default initvm directory "./initvm".')
             initvmdir = './initvm'
         else:
-            print('No initvm found!')
-            sys.exit(207)
+            raise CliError(207, 'No initvm found!')
     return initvmdir
 
 
@@ -243,15 +239,14 @@ class StartAction(InitVMAction):
                 print('This initvm is already running.')
             else:
                 # If no unix socket file is found, assume another VM is bound to the soap port.
-                print('There is already another running initvm.\nPlease stop this VM first.')
-                sys.exit(211)
+                raise CliError(211, 'There is already another running initvm.\n'
+                                    'Please stop this VM first.')
         else:
             # Try to start the QEMU VM for the given directory.
             try:
                 subprocess.Popen(['make', 'run_qemu'], cwd=initvmdir)
             except Exception as e:
-                print(f'Running QEMU failed: {e}')
-                sys.exit(211)
+                raise with_cli_details(e, 211, f'Running QEMU failed: {e}')
 
             # This will sys.exit on error.
             test_soap_communication(sleep=1, wait=60)
@@ -261,8 +256,7 @@ class StartAction(InitVMAction):
         import libvirt
 
         if self.initvm_state() == libvirt.VIR_DOMAIN_RUNNING:
-            print('Initvm already running.')
-            sys.exit(122)
+            raise CliError(122, 'Initvm already running.')
         elif self.initvm_state() == libvirt.VIR_DOMAIN_SHUTOFF:
             self._attach_disk_fds()
 
@@ -298,8 +292,7 @@ class EnsureAction(InitVMAction):
         # use port bind test in case of if QEMU mode
         if opt.qemu_mode:
             if not is_soap_port_reachable():
-                print('Elbe initvm in bad state.\nNo process found on soap port.')
-                sys.exit(206)
+                raise CliError(206, 'Elbe initvm in bad state.\nNo process found on soap port.')
             return
 
         import libvirt
@@ -309,8 +302,7 @@ class EnsureAction(InitVMAction):
         elif self.initvm_state() == libvirt.VIR_DOMAIN_RUNNING:
             test_soap_communication()
         else:
-            print('Elbe initvm in bad state.')
-            sys.exit(124)
+            raise CliError(124, 'Elbe initvm in bad state.')
 
 
 @InitVMAction.register('stop')
@@ -329,8 +321,7 @@ class StopAction(InitVMAction):
 
         # Test if QEMU monitor unix-socket file exists, and error exit if not.
         if not os.path.exists(socket_path):
-            print('No unix socket found for this vm!\nunable to shutdown this vm.')
-            sys.exit(212)
+            raise CliError(212, 'No unix socket found for this vm!\nunable to shutdown this vm.')
 
         try:
             with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client:
@@ -355,8 +346,7 @@ class StopAction(InitVMAction):
         import libvirt
 
         if self.initvm_state() != libvirt.VIR_DOMAIN_RUNNING:
-            print('Initvm is not running.')
-            sys.exit(125)
+            raise CliError(125, 'Initvm is not running.')
 
         while True:
             sys.stdout.write('*')
@@ -404,8 +394,8 @@ class AttachAction(InitVMAction):
 
         # Test if socat command is available.
         if shutil.which('socat') is None:
-            print('The command "socat" is required.\nPlease install socat: sudo apt install socat')
-            sys.exit(208)
+            raise CliError(208, 'The command "socat" is required.\n'
+                                'Please install socat: sudo apt install socat')
 
         # Connect to socket file, if it exists.
         if os.path.exists(os.path.join(initvmdir, 'vm-serial-socket')):
@@ -413,17 +403,16 @@ class AttachAction(InitVMAction):
                             'unix-connect:vm-serial-socket'],
                            cwd=initvmdir, check=False)
         else:
-            print('No unix socket found for the console of this vm!\nUnable to attach.')
+            msg = 'No unix socket found for the console of this vm!\nUnable to attach.'
             if is_soap_port_reachable():
-                print('There seems to be another initvm running. The soap port is in use.')
-            sys.exit(212)
+                msg += '\nThere seems to be another initvm running. The soap port is in use.'
+            raise CliError(212, msg)
 
     def _attach_libvirt_vm(self):
         import libvirt
 
         if self.initvm_state() != libvirt.VIR_DOMAIN_RUNNING:
-            print('Error: Initvm not running properly.')
-            sys.exit(126)
+            raise CliError(126, 'Error: Initvm not running properly.')
 
         print('Attaching to initvm console.')
         subprocess.run(['virsh', '--connect', 'qemu:///system', 'console', cfg['initvm_domain']],
@@ -443,9 +432,7 @@ def submit_with_repodir_and_dl_result(xmlfile, cdrom, opt):
         with Repodir(xmlfile, preprocess_xmlfile):
             submit_and_dl_result(preprocess_xmlfile, cdrom, opt)
     except RepodirError as err:
-        print('elbe repodir failed', file=sys.stderr)
-        print(err, file=sys.stderr)
-        sys.exit(127)
+        raise with_cli_details(err, 127, 'elbe repodir failed')
     finally:
         os.remove(preprocess_xmlfile)
 
@@ -454,22 +441,13 @@ def submit_and_dl_result(xmlfile, cdrom, opt):
 
     with preprocess_file(xmlfile, opt.variants) as xmlfile:
 
-        ps = run_elbe(['control', 'create_project'], capture_output=True, encoding='utf-8')
-        if ps.returncode != 0:
-            print('elbe control create_project failed.', file=sys.stderr)
-            print(ps.stderr, file=sys.stderr)
-            print('Giving up', file=sys.stderr)
-            sys.exit(128)
+        ps = run_elbe(['control', 'create_project'],
+                      capture_output=True, encoding='utf-8', check=True)
 
         prjdir = ps.stdout.strip()
 
         ps = run_elbe(['control', 'set_xml', prjdir, xmlfile],
-                      capture_output=True, encoding='utf-8')
-        if ps.returncode != 0:
-            print('elbe control set_xml failed2', file=sys.stderr)
-            print(ps.stderr, file=sys.stderr)
-            print('Giving up', file=sys.stderr)
-            sys.exit(129)
+                      capture_output=True, encoding='utf-8', check=True)
 
     if opt.writeproject:
         with open(opt.writeproject, 'w') as wpf:
@@ -477,12 +455,7 @@ def submit_and_dl_result(xmlfile, cdrom, opt):
 
     if cdrom is not None:
         print('Uploading CDROM. This might take a while')
-        try:
-            run_elbe(['control', 'set_cdrom', prjdir, cdrom], check=True)
-        except subprocess.CalledProcessError:
-            print('elbe control set_cdrom Failed', file=sys.stderr)
-            print('Giving up', file=sys.stderr)
-            sys.exit(131)
+        run_elbe(['control', 'set_cdrom', prjdir, cdrom], check=True)
 
         print('Upload finished')
 
@@ -494,46 +467,29 @@ def submit_and_dl_result(xmlfile, cdrom, opt):
     if cdrom:
         build_opts.append('--skip-pbuilder')
 
-    try:
-        run_elbe(['control', 'build', prjdir, *build_opts], check=True)
-    except subprocess.CalledProcessError:
-        print('elbe control build Failed', file=sys.stderr)
-        print('Giving up', file=sys.stderr)
-        sys.exit(132)
+    run_elbe(['control', 'build', prjdir, *build_opts], check=True)
 
     print('Build started, waiting till it finishes')
 
     try:
         run_elbe(['control', 'wait_busy', prjdir], check=True)
-    except subprocess.CalledProcessError:
-        print('elbe control wait_busy Failed', file=sys.stderr)
-        print('', file=sys.stderr)
-        print('The project will not be deleted from the initvm.',
-              file=sys.stderr)
-        print('The files, that have been built, can be downloaded using:',
-              file=sys.stderr)
-        print(
-            f'{prog} control get_files --output "{opt.outdir}" "{prjdir}"',
-            file=sys.stderr)
-        print('', file=sys.stderr)
-        print('The project can then be removed using:',
-              file=sys.stderr)
-        print(f'{prog} control del_project "{prjdir}"',
-              file=sys.stderr)
-        print('', file=sys.stderr)
-        sys.exit(133)
+    except subprocess.CalledProcessError as e:
+        raise with_cli_details(e, 133, textwrap.dedent(f"""
+            elbe control wait_busy Failed
+
+            The project will not be deleted from the initvm.
+            The files, that have been built, can be downloaded using:
+            {prog} control get_files --output "{opt.outdir}" "{prjdir}"
+
+            The project can then be removed using:
+            {prog} control del_project "{prjdir}" """))
 
     print('')
     print('Build finished !')
     print('')
 
     if opt.build_sdk:
-        try:
-            run_elbe(['control', 'build_sdk', prjdir], check=True)
-        except subprocess.CalledProcessError:
-            print('elbe control build_sdk Failed', file=sys.stderr)
-            print('Giving up', file=sys.stderr)
-            sys.exit(134)
+        run_elbe(['control', 'build_sdk', prjdir], check=True)
 
         print('SDK Build started, waiting till it finishes')
 
@@ -582,12 +538,7 @@ def submit_and_dl_result(xmlfile, cdrom, opt):
         print('')
         print('Listing available files:')
         print('')
-        try:
-            run_elbe(['control', 'get_files', prjdir], check=True)
-        except subprocess.CalledProcessError:
-            print('elbe control get_files Failed', file=sys.stderr)
-            print('Giving up', file=sys.stderr)
-            sys.exit(137)
+        run_elbe(['control', 'get_files', prjdir], check=True)
 
         print('')
         print(f'Get Files with: elbe control get_file "{prjdir}" <filename>')
@@ -598,20 +549,10 @@ def submit_and_dl_result(xmlfile, cdrom, opt):
 
         ensure_outdir(opt)
 
-        try:
-            run_elbe(['control', 'get_files', '--output', opt.outdir, prjdir], check=True)
-        except subprocess.CalledProcessError:
-            print('elbe control get_files Failed', file=sys.stderr)
-            print('Giving up', file=sys.stderr)
-            sys.exit(138)
+        run_elbe(['control', 'get_files', '--output', opt.outdir, prjdir], check=True)
 
         if not opt.keep_files:
-            try:
-                run_elbe(['control', 'del_project', prjdir], check=True)
-            except subprocess.CalledProcessError:
-                print('remove project from initvm failed',
-                      file=sys.stderr)
-                sys.exit(139)
+            run_elbe(['control', 'del_project', prjdir], check=True)
 
 
 def extract_cdrom(cdrom):
@@ -635,31 +576,18 @@ def extract_cdrom(cdrom):
     print('', file=sys.stderr)
 
     if not tmp.isfile('source.xml'):
-        print(
-            'Iso image does not contain a source.xml file',
-            file=sys.stderr)
-        print(
-            "This is not supported by 'elbe initvm'",
-            file=sys.stderr)
-        print('', file=sys.stderr)
-        print('Exiting !!!', file=sys.stderr)
-        sys.exit(140)
+        raise CliError(140, textwrap.dedent("""
+            Iso image does not contain a source.xml file.
+            This is not supported by 'elbe initvm'."""))
 
     try:
         exml = ElbeXML(
             tmp.fname('source.xml'),
             url_validation=ValidationMode.NO_CHECK)
     except ValidationError as e:
-        print(
-            'Iso image does contain a source.xml file.',
-            file=sys.stderr)
-        print(
-            'But that xml does not validate correctly',
-            file=sys.stderr)
-        print('', file=sys.stderr)
-        print('Exiting !!!', file=sys.stderr)
-        print(e)
-        sys.exit(141)
+        raise with_cli_details(e, 141, textwrap.dedent("""
+            Iso image does contain a source.xml file.
+            But that xml does not validate correctly."""))
 
     print('Iso Image with valid source.xml detected !')
     print(f'Image was generated using Elbe Version {exml.get_elbe_version()}')
@@ -676,27 +604,27 @@ class CreateAction(InitVMAction):
     def execute(self, initvmdir, opt, args):
 
         if self.initvm is not None and not opt.qemu_mode:
-            print(f"Initvm is already defined for the libvirt domain '{cfg['initvm_domain']}'.\n")
-            print('If you want to build in your old initvm, use `elbe initvm submit <xml>`.')
-            print('If you want to remove your old initvm from libvirt run `elbe initvm destroy`.\n')
-            print('You can specify another libvirt domain by setting the '
-                  'ELBE_INITVM_DOMAIN environment variable to an unused domain name.\n')
-            print('Note:')
-            print('\t1) You can reimport your old initvm via '
-                  '`virsh --connect qemu:///system define <file>`')
-            print('\t   where <file> is the corresponding libvirt.xml')
-            print('\t2) virsh --connect qemu:///system undefine does not delete the image '
-                  'of your old initvm.')
-            sys.exit(142)
+            raise CliError(142, textwrap.dedent(f"""
+                Initvm is already defined for the libvirt domain '{cfg['initvm_domain']}'.
+                If you want to build in your old initvm, use `elbe initvm submit <xml>`.')
+                If you want to remove your old initvm from libvirt run `elbe initvm destroy`.
+                You can specify another libvirt domain by setting the
+                ELBE_INITVM_DOMAIN environment variable to an unused domain name.
+                Note:
+                \t1) You can reimport your old initvm via
+                `virsh --connect qemu:///system define <file>`
+                \t   where <file> is the corresponding libvirt.xml
+                \t2) virsh --connect qemu:///system undefine does not delete the image
+                of your old initvm."""))
 
         # Upgrade from older versions which used tmux
         try:
             subprocess.run(['tmux', 'has-session', '-t', 'ElbeInitVMSession'],
                            stderr=subprocess.DEVNULL, check=True)
-            print('ElbeInitVMSession exists in tmux. '
-                  'It may belong to an old elbe version. '
-                  'Please stop it to prevent interfering with this version.', file=sys.stderr)
-            sys.exit(143)
+            raise CliError(143, textwrap.dedent("""
+                ElbeInitVMSession exists in tmux.
+                It may belong to an old elbe version.
+                Please stop it to prevent interfering with this version."""))
         except (subprocess.CalledProcessError, FileNotFoundError):
             pass
 
@@ -723,41 +651,32 @@ class CreateAction(InitVMAction):
                 xmlfile = tmp.fname('source.xml')
                 cdrom = args[0]
             else:
-                print(
-                    'Unknown file ending (use either xml or iso)',
-                    file=sys.stderr)
-                sys.exit(144)
+                raise CliError(144, 'Unknown file ending (use either xml or iso)')
         else:
             # No xml File was specified, build the default elbe-init-with-ssh
             xmlfile = os.path.join(
                 elbepack.__path__[0],
                 'init/default-init.xml')
 
-        try:
-            init_opts = []
+        init_opts = []
 
-            if not opt.build_bin:
-                init_opts.append('--skip-build-bin')
+        if not opt.build_bin:
+            init_opts.append('--skip-build-bin')
 
-            if not opt.build_sources:
-                init_opts.append('--skip-build-source')
+        if not opt.build_sources:
+            init_opts.append('--skip-build-source')
 
-            if opt.fail_on_warning:
-                init_opts.append('--fail-on-warning')
-
-            if cdrom:
-                cdrom_opts = ['--cdrom', cdrom]
-            else:
-                cdrom_opts = []
+        if opt.fail_on_warning:
+            init_opts.append('--fail-on-warning')
 
-            with preprocess_file(xmlfile, opt.variants) as preproc:
-                run_elbe(['init', *init_opts, '--directory', initvmdir, *cdrom_opts, preproc],
-                         check=True)
+        if cdrom:
+            cdrom_opts = ['--cdrom', cdrom]
+        else:
+            cdrom_opts = []
 
-        except subprocess.CalledProcessError:
-            print("'elbe init' Failed", file=sys.stderr)
-            print('Giving up', file=sys.stderr)
-            sys.exit(145)
+        with preprocess_file(xmlfile, opt.variants) as preproc:
+            run_elbe(['init', *init_opts, '--directory', initvmdir, *cdrom_opts, preproc],
+                     check=True)
 
         # Skip libvirt VM creation in QEMU mode.
         if not opt.qemu_mode:
@@ -770,42 +689,27 @@ class CreateAction(InitVMAction):
             # Register initvm in libvirt.
             try:
                 self.conn.defineXML(xml)
-            except subprocess.CalledProcessError:
-                print('Registering initvm in libvirt failed', file=sys.stderr)
-                print('Try `elbe initvm destroy` to delete existing initvm',
-                      file=sys.stderr)
-                sys.exit(146)
+            except subprocess.CalledProcessError as e:
+                raise with_cli_details(e, 146, textwrap.dedent("""
+                    Registering initvm in libvirt failed.
+                    Try `elbe initvm destroy` to delete existing initvm."""))
 
         # Build initvm
-        try:
-            subprocess.run(['make'], cwd=initvmdir, check=True)
-        except subprocess.CalledProcessError:
-            print('Building the initvm Failed', file=sys.stderr)
-            print('Giving up', file=sys.stderr)
-            sys.exit(147)
+        subprocess.run(['make'], cwd=initvmdir, check=True)
 
         # In case of QEMU mode, we need to forward the additional parameters.
         additional_params = []
         if opt.qemu_mode:
             additional_params = ['--qemu', f'--directory={initvmdir}']
 
-        ps = run_elbe(['initvm', 'start', *additional_params], capture_output=False,
-                      encoding='utf-8')
-        if ps.returncode != 0:
-            print('Starting the initvm Failed', file=sys.stderr)
-            print('Giving up', file=sys.stderr)
-            sys.exit(148)
+        run_elbe(['initvm', 'start', *additional_params], check=True)
 
         if len(args) == 1:
             # If provided xml file has no initvm section xmlfile is set to a
             # default initvm XML file. But we need the original file here.
             if args[0].endswith('.xml'):
                 # Stop here if no project node was specified.
-                try:
-                    x = etree(args[0])
-                except ValidationError as e:
-                    print(f'XML file is invalid: {e}')
-                    sys.exit(149)
+                x = etree(args[0])
                 if not x.has('project'):
                     print("elbe initvm ready: use 'elbe initvm submit "
                           "myproject.xml' to build a project")
@@ -839,12 +743,7 @@ class SubmitAction(InitVMAction):
         if opt.qemu_mode:
             additional_params = ['--qemu', f'--directory={initvmdir}']
 
-        ps = run_elbe(['initvm', 'ensure', *additional_params], capture_output=True,
-                      encoding='utf-8')
-        if ps.returncode != 0:
-            print('Starting the initvm Failed', file=sys.stderr)
-            print('Giving up', file=sys.stderr)
-            sys.exit(150)
+        run_elbe(['initvm', 'ensure', *additional_params], check=True)
 
         # Init cdrom to None, if we detect it, we set it
         cdrom = None
@@ -860,10 +759,7 @@ class SubmitAction(InitVMAction):
                 xmlfile = tmp.fname('source.xml')
                 cdrom = args[0]
             else:
-                print(
-                    'Unknown file ending (use either xml or iso)',
-                    file=sys.stderr)
-                sys.exit(151)
+                raise CliError(151, 'Unknown file ending (use either xml or iso)')
 
             submit_with_repodir_and_dl_result(xmlfile, cdrom, opt)
 

-- 
2.45.2



More information about the elbe-devel mailing list