[cubicweb-ctl] move to accepting only once instance name per command
authorLaurent Peuch <cortex@worlddomination.be>
Tue, 21 May 2019 16:36:12 +0200
changeset 12685 84a8a8915512
parent 12684 d464495452aa
child 12686 74b8bceddee7
[cubicweb-ctl] move to accepting only once instance name per command The rationals behind this decision are: - while in the past managing all instances sytem wide made a lot of sens, pratices have evolved today and we've moved to managing one instance by one - this makes things easier to debug since commands since using them on several instances were making this harder (errors hidden in the middle) - also solve the problem of the return code to have, before it was always 0 which prevented to do things like: ipython --pdb $(which cubicweb-ctl) $command $instance or shell scripts that used it - this simplify the code and is always good to take
cubicweb/cwctl.py
cubicweb/test/unittest_cwctl.py
doc/changes/3.27.rst
--- a/cubicweb/cwctl.py	Tue Jul 09 11:13:08 2019 +0200
+++ b/cubicweb/cwctl.py	Tue May 21 16:36:12 2019 +0200
@@ -23,7 +23,7 @@
 # completion). So import locally in command helpers.
 import sys
 from warnings import filterwarnings
-from os import listdir, system
+from os import listdir
 from os.path import exists, join, isdir
 
 try:
@@ -98,10 +98,12 @@
 
 
 class InstanceCommand(Command):
-    """base class for command taking 0 to n instance id as arguments
-    (0 meaning all registered instances)
-    """
-    arguments = '[<instance>...]'
+    """base class for command taking one instance id as arguments"""
+    arguments = '<instance>'
+
+    # enforce having one instance
+    min_args = max_args = 1
+
     options = (
         ("force",
          {'short': 'f', 'action': 'store_true',
@@ -116,33 +118,9 @@
         """run the <command>_method on each argument (a list of instance
         identifiers)
         """
-        if not args:
-            args = list_instances(cwcfg.instances_dir())
-            try:
-                askconfirm = not self.config.force
-            except AttributeError:
-                # no force option
-                askconfirm = False
-        else:
-            askconfirm = False
-        self.run_args(args, askconfirm)
+        appid = args[0]
+        cmdmeth = getattr(self, '%s_instance' % self.name)
 
-    def run_args(self, args, askconfirm):
-        status = 0
-        for appid in args:
-            if askconfirm:
-                print('*' * 72)
-                if not ASK.confirm('%s instance %r ?' % (self.name, appid)):
-                    continue
-            try:
-                status = max(status, self.run_arg(appid))
-            except (KeyboardInterrupt, SystemExit):
-                sys.stderr.write('%s aborted\n' % self.name)
-                return 2  # specific error code
-        sys.exit(status)
-
-    def run_arg(self, appid):
-        cmdmeth = getattr(self, '%s_instance' % self.name)
         try:
             status = cmdmeth(appid) or 0
         except (ExecutionError, ConfigurationError) as ex:
@@ -155,30 +133,12 @@
             sys.stderr.write('instance %s not %s: %s\n' % (
                 appid, self.actionverb, ex))
             status = 8
-        return status
-
-
-class InstanceCommandFork(InstanceCommand):
-    """Same as `InstanceCommand`, but command is forked in a new environment
-    for each argument
-    """
 
-    def run_args(self, args, askconfirm):
-        if len(args) > 1:
-            forkcmd = ' '.join(w for w in sys.argv if w not in args)
-        else:
-            forkcmd = None
-        for appid in args:
-            if askconfirm:
-                print('*' * 72)
-                if not ASK.confirm('%s instance %r ?' % (self.name, appid)):
-                    continue
-            if forkcmd:
-                status = system('%s %s' % (forkcmd, appid))
-                if status:
-                    print('%s exited with status %s' % (forkcmd, status))
-            else:
-                self.run_arg(appid)
+        except (KeyboardInterrupt, SystemExit):
+            sys.stderr.write('%s aborted\n' % self.name)
+            status = 2  # specific error code
+
+        sys.exit(status)
 
 
 # base commands ###############################################################
@@ -465,16 +425,15 @@
         config.init_log(config['log-threshold'], force=True)
 
 
-class UpgradeInstanceCommand(InstanceCommandFork):
+class UpgradeInstanceCommand(InstanceCommand):
     """Upgrade an instance after cubicweb and/or component(s) upgrade.
 
     For repository update, you will be prompted for a login / password to use
     to connect to the system database.  For some upgrades, the given user
     should have create or alter table permissions.
 
-    <instance>...
-      identifiers of the instances to upgrade. If no instance is
-      given, upgrade them all.
+    <instance>
+      identifier of the instance to upgrade.
     """
     name = 'upgrade'
     actionverb = 'upgraded'
@@ -638,6 +597,7 @@
     name = 'shell'
     arguments = '<instance> [batch command file(s)] [-- <script arguments>]'
     min_args = 1
+    max_args = None
     options = (
         ('system-only',
          {'short': 'S', 'action': 'store_true',
@@ -701,9 +661,8 @@
 class RecompileInstanceCatalogsCommand(InstanceCommand):
     """Recompile i18n catalogs for instances.
 
-    <instance>...
-      identifiers of the instances to consider. If no instance is
-      given, recompile for all registered instances.
+    <instance>
+      identifier of the instance to consider.
     """
     name = 'i18ninstance'
 
@@ -747,7 +706,7 @@
 class ConfigureInstanceCommand(InstanceCommand):
     """Configure instance.
 
-    <instance>...
+    <instance>
       identifier of the instance to configure.
     """
     name = 'configure'
--- a/cubicweb/test/unittest_cwctl.py	Tue Jul 09 11:13:08 2019 +0200
+++ b/cubicweb/test/unittest_cwctl.py	Tue May 21 16:36:12 2019 +0200
@@ -20,17 +20,20 @@
 from os.path import join
 from io import StringIO
 import unittest
-from unittest.mock import patch
+from unittest.mock import patch, MagicMock
 
-from cubicweb.cwctl import ListCommand
+from logilab.common.clcommands import CommandLine
+
+from cubicweb.cwctl import ListCommand, InstanceCommand
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.server.migractions import ServerMigrationHelper
+from cubicweb.cwconfig import CubicWebConfiguration as cwcfg
+from cubicweb.__pkginfo__ import version as cw_version
 
 import unittest_cwconfig
 
 
 class CubicWebCtlTC(unittest.TestCase):
-
     setUpClass = unittest_cwconfig.CubicWebConfigurationTC.setUpClass
     tearDownClass = unittest_cwconfig.CubicWebConfigurationTC.tearDownClass
 
@@ -78,5 +81,32 @@
                 mih.cmd_process_script(scriptname, None, scriptargs=args)
 
 
+class TestCommand(InstanceCommand):
+    "I need some doc"
+    name = "test"
+
+    def test_instance(self, appid):
+        pass
+
+
+class InstanceCommandTest(unittest.TestCase):
+    def test_getting_called(self):
+        CWCTL = CommandLine('cubicweb-ctl', 'The CubicWeb swiss-knife.',
+                            version=cw_version, check_duplicated_command=False)
+        cwcfg.load_cwctl_plugins()
+        CWCTL.register(TestCommand)
+
+        TestCommand.test_instance = MagicMock(return_value=0)
+
+        # pretend that this instance exists
+        cwcfg.config_for = MagicMock(return_value=object())
+
+        try:
+            CWCTL.run(["test", "some_instance"])
+        except SystemExit as ex:  # CWCTL will finish the program after that
+            self.assertEqual(ex.code, 0)
+        TestCommand.test_instance.assert_called_with("some_instance")
+
+
 if __name__ == '__main__':
     unittest.main()
--- a/doc/changes/3.27.rst	Tue Jul 09 11:13:08 2019 +0200
+++ b/doc/changes/3.27.rst	Tue May 21 16:36:12 2019 +0200
@@ -40,6 +40,9 @@
   one can expect tests that seem to pass (but were actually silently failing)
   to fail now.
 
+* All "cubicweb-ctl" command only accept one instance argument from now one
+  (instead of 0 to n)
+
 Deprecated code drops
 ---------------------