← Back to team overview

cloud-init-dev team mailing list archive

[Merge] ~chad.smith/cloud-init:fix-cloudinit-subcommands into cloud-init:master

 

Chad Smith has proposed merging ~chad.smith/cloud-init:fix-cloudinit-subcommands into cloud-init:master.

Requested reviews:
  cloud-init commiters (cloud-init-dev)
Related bugs:
  Bug #1712676 in cloud-init: "Cloud-init analyzeand devel  commandline traceback"
  https://bugs.launchpad.net/cloud-init/+bug/1712676

For more details, see:
https://code.launchpad.net/~chad.smith/cloud-init/+git/cloud-init/+merge/329493

cloud-init cli: Fix command line parsing of coniditionally loaded subcommands
   
In an effort to save file load cost during system boot, certain
subcommands, analyze and devel, do not get loaded unless the subcommand is
specified on the commandline. Because setup.py entrypoint for cloud-init
script doesn't specify sysv_args parameter when calling the CLI's main()
we need main to read sys.argv into sysv_args so our subparser loading
continues to work.
    
LP: #1712676

-- 
Your team cloud-init commiters is requested to review the proposed merge of ~chad.smith/cloud-init:fix-cloudinit-subcommands into cloud-init:master.
diff --git a/cloudinit/cmd/main.py b/cloudinit/cmd/main.py
index 5b46797..68563e0 100644
--- a/cloudinit/cmd/main.py
+++ b/cloudinit/cmd/main.py
@@ -676,11 +676,10 @@ def main_features(name, args):
 
 
 def main(sysv_args=None):
-    if sysv_args is not None:
-        parser = argparse.ArgumentParser(prog=sysv_args[0])
-        sysv_args = sysv_args[1:]
-    else:
-        parser = argparse.ArgumentParser()
+    if not sysv_args:
+        sysv_args = sys.argv
+    parser = argparse.ArgumentParser(prog=sysv_args[0])
+    sysv_args = sysv_args[1:]
 
     # Top level args
     parser.add_argument('--version', '-v', action='version',
diff --git a/tests/unittests/test_cli.py b/tests/unittests/test_cli.py
index 2449880..12f0185 100644
--- a/tests/unittests/test_cli.py
+++ b/tests/unittests/test_cli.py
@@ -70,6 +70,21 @@ class TestCLI(test_helpers.FilesystemMockingTestCase):
         self.assertEqual('modules', parseargs.action[0])
         self.assertEqual('main_modules', parseargs.action[1].__name__)
 
+    def test_conditional_subcommands_from_entry_point_sys_argv(self):
+        """Subcommands from entry-point are properly parsed from sys.argv."""
+        expected_errors = [
+            'usage: cloud-init analyze', 'usage: cloud-init devel']
+        conditional_subcommands = ['analyze', 'devel']
+        # The cloud-init entrypoint calls main without passing sys_argv
+        for subcommand in conditional_subcommands:
+            with mock.patch('sys.argv', ['cloud-init', subcommand]):
+                try:
+                    cli.main()
+                except SystemExit as e:
+                    self.assertEqual(2, e.code)  # exit 2 on proper usage docs
+        for error_message in expected_errors:
+            self.assertIn(error_message, self.stderr.getvalue())
+
     def test_analyze_subcommand_parser(self):
         """The subcommand cloud-init analyze calls the correct subparser."""
         self._call_main(['cloud-init', 'analyze'])

Follow ups