Blame tests/unittests/test_cli.py

Packit Service a04d08
# This file is part of cloud-init. See LICENSE file for license information.
Packit Service a04d08
Packit Service 11b429
import os
Packit Service 751c4a
import io
Packit Service 751c4a
from collections import namedtuple
Packit Service a04d08
Packit Service a04d08
from cloudinit.cmd import main as cli
Packit Service a04d08
from cloudinit.tests import helpers as test_helpers
Packit Service a04d08
from cloudinit.util import load_file, load_json
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
mock = test_helpers.mock
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class TestCLI(test_helpers.FilesystemMockingTestCase):
Packit Service a04d08
Packit Service a04d08
    with_logs = True
Packit Service a04d08
Packit Service a04d08
    def setUp(self):
Packit Service a04d08
        super(TestCLI, self).setUp()
Packit Service 751c4a
        self.stderr = io.StringIO()
Packit Service a04d08
        self.patchStdoutAndStderr(stderr=self.stderr)
Packit Service a04d08
Packit Service a04d08
    def _call_main(self, sysv_args=None):
Packit Service a04d08
        if not sysv_args:
Packit Service a04d08
            sysv_args = ['cloud-init']
Packit Service a04d08
        try:
Packit Service a04d08
            return cli.main(sysv_args=sysv_args)
Packit Service a04d08
        except SystemExit as e:
Packit Service a04d08
            return e.code
Packit Service a04d08
Packit Service a04d08
    def test_status_wrapper_errors_on_invalid_name(self):
Packit Service a04d08
        """status_wrapper will error when the name parameter is not valid.
Packit Service a04d08
Packit Service a04d08
        Valid name values are only init and modules.
Packit Service a04d08
        """
Packit Service a04d08
        tmpd = self.tmp_dir()
Packit Service a04d08
        data_d = self.tmp_path('data', tmpd)
Packit Service a04d08
        link_d = self.tmp_path('link', tmpd)
Packit Service a04d08
        FakeArgs = namedtuple('FakeArgs', ['action', 'local', 'mode'])
Packit Service a04d08
Packit Service a04d08
        def myaction():
Packit Service a04d08
            raise Exception('Should not call myaction')
Packit Service a04d08
Packit Service a04d08
        myargs = FakeArgs(('doesnotmatter', myaction), False, 'bogusmode')
Packit Service a04d08
        with self.assertRaises(ValueError) as cm:
Packit Service a04d08
            cli.status_wrapper('init1', myargs, data_d, link_d)
Packit Service a04d08
        self.assertEqual('unknown name: init1', str(cm.exception))
Packit Service a04d08
        self.assertNotIn('Should not call myaction', self.logs.getvalue())
Packit Service a04d08
Packit Service a04d08
    def test_status_wrapper_errors_on_invalid_modes(self):
Packit Service a04d08
        """status_wrapper will error if a parameter combination is invalid."""
Packit Service a04d08
        tmpd = self.tmp_dir()
Packit Service a04d08
        data_d = self.tmp_path('data', tmpd)
Packit Service a04d08
        link_d = self.tmp_path('link', tmpd)
Packit Service a04d08
        FakeArgs = namedtuple('FakeArgs', ['action', 'local', 'mode'])
Packit Service a04d08
Packit Service a04d08
        def myaction():
Packit Service a04d08
            raise Exception('Should not call myaction')
Packit Service a04d08
Packit Service a04d08
        myargs = FakeArgs(('modules_name', myaction), False, 'bogusmode')
Packit Service a04d08
        with self.assertRaises(ValueError) as cm:
Packit Service a04d08
            cli.status_wrapper('modules', myargs, data_d, link_d)
Packit Service a04d08
        self.assertEqual(
Packit Service a04d08
            "Invalid cloud init mode specified 'modules-bogusmode'",
Packit Service a04d08
            str(cm.exception))
Packit Service a04d08
        self.assertNotIn('Should not call myaction', self.logs.getvalue())
Packit Service a04d08
Packit Service a04d08
    def test_status_wrapper_init_local_writes_fresh_status_info(self):
Packit Service a04d08
        """When running in init-local mode, status_wrapper writes status.json.
Packit Service a04d08
Packit Service a04d08
        Old status and results artifacts are also removed.
Packit Service a04d08
        """
Packit Service a04d08
        tmpd = self.tmp_dir()
Packit Service a04d08
        data_d = self.tmp_path('data', tmpd)
Packit Service a04d08
        link_d = self.tmp_path('link', tmpd)
Packit Service a04d08
        status_link = self.tmp_path('status.json', link_d)
Packit Service a04d08
        # Write old artifacts which will be removed or updated.
Packit Service a04d08
        for _dir in data_d, link_d:
Packit Service a04d08
            test_helpers.populate_dir(
Packit Service a04d08
                _dir, {'status.json': 'old', 'result.json': 'old'})
Packit Service a04d08
Packit Service a04d08
        FakeArgs = namedtuple('FakeArgs', ['action', 'local', 'mode'])
Packit Service a04d08
Packit Service a04d08
        def myaction(name, args):
Packit Service a04d08
            # Return an error to watch status capture them
Packit Service a04d08
            return 'SomeDatasource', ['an error']
Packit Service a04d08
Packit Service a04d08
        myargs = FakeArgs(('ignored_name', myaction), True, 'bogusmode')
Packit Service a04d08
        cli.status_wrapper('init', myargs, data_d, link_d)
Packit Service a04d08
        # No errors reported in status
Packit Service a04d08
        status_v1 = load_json(load_file(status_link))['v1']
Packit Service a04d08
        self.assertEqual(['an error'], status_v1['init-local']['errors'])
Packit Service a04d08
        self.assertEqual('SomeDatasource', status_v1['datasource'])
Packit Service a04d08
        self.assertFalse(
Packit Service a04d08
            os.path.exists(self.tmp_path('result.json', data_d)),
Packit Service a04d08
            'unexpected result.json found')
Packit Service a04d08
        self.assertFalse(
Packit Service a04d08
            os.path.exists(self.tmp_path('result.json', link_d)),
Packit Service a04d08
            'unexpected result.json link found')
Packit Service a04d08
Packit Service a04d08
    def test_no_arguments_shows_usage(self):
Packit Service a04d08
        exit_code = self._call_main()
Packit Service a04d08
        self.assertIn('usage: cloud-init', self.stderr.getvalue())
Packit Service a04d08
        self.assertEqual(2, exit_code)
Packit Service a04d08
Packit Service a04d08
    def test_no_arguments_shows_error_message(self):
Packit Service a04d08
        exit_code = self._call_main()
Packit Service a04d08
        missing_subcommand_message = [
Packit Service a04d08
            'too few arguments',  # python2.7 msg
Packit Service a04d08
            'the following arguments are required: subcommand'  # python3 msg
Packit Service a04d08
        ]
Packit Service a04d08
        error = self.stderr.getvalue()
Packit Service a04d08
        matches = ([msg in error for msg in missing_subcommand_message])
Packit Service a04d08
        self.assertTrue(
Packit Service a04d08
            any(matches), 'Did not find error message for missing subcommand')
Packit Service a04d08
        self.assertEqual(2, exit_code)
Packit Service a04d08
Packit Service a04d08
    def test_all_subcommands_represented_in_help(self):
Packit Service a04d08
        """All known subparsers are represented in the cloud-int help doc."""
Packit Service a04d08
        self._call_main()
Packit Service a04d08
        error = self.stderr.getvalue()
Packit Service a04d08
        expected_subcommands = ['analyze', 'clean', 'devel', 'dhclient-hook',
Packit Service a04d08
                                'features', 'init', 'modules', 'single']
Packit Service a04d08
        for subcommand in expected_subcommands:
Packit Service a04d08
            self.assertIn(subcommand, error)
Packit Service a04d08
Packit Service a04d08
    @mock.patch('cloudinit.cmd.main.status_wrapper')
Packit Service a04d08
    def test_init_subcommand_parser(self, m_status_wrapper):
Packit Service a04d08
        """The subcommand 'init' calls status_wrapper passing init."""
Packit Service a04d08
        self._call_main(['cloud-init', 'init'])
Packit Service a04d08
        (name, parseargs) = m_status_wrapper.call_args_list[0][0]
Packit Service a04d08
        self.assertEqual('init', name)
Packit Service a04d08
        self.assertEqual('init', parseargs.subcommand)
Packit Service a04d08
        self.assertEqual('init', parseargs.action[0])
Packit Service a04d08
        self.assertEqual('main_init', parseargs.action[1].__name__)
Packit Service a04d08
Packit Service a04d08
    @mock.patch('cloudinit.cmd.main.status_wrapper')
Packit Service a04d08
    def test_modules_subcommand_parser(self, m_status_wrapper):
Packit Service a04d08
        """The subcommand 'modules' calls status_wrapper passing modules."""
Packit Service a04d08
        self._call_main(['cloud-init', 'modules'])
Packit Service a04d08
        (name, parseargs) = m_status_wrapper.call_args_list[0][0]
Packit Service a04d08
        self.assertEqual('modules', name)
Packit Service a04d08
        self.assertEqual('modules', parseargs.subcommand)
Packit Service a04d08
        self.assertEqual('modules', parseargs.action[0])
Packit Service a04d08
        self.assertEqual('main_modules', parseargs.action[1].__name__)
Packit Service a04d08
Packit Service a04d08
    def test_conditional_subcommands_from_entry_point_sys_argv(self):
Packit Service a04d08
        """Subcommands from entry-point are properly parsed from sys.argv."""
Packit Service 751c4a
        stdout = io.StringIO()
Packit Service a04d08
        self.patchStdoutAndStderr(stdout=stdout)
Packit Service a04d08
Packit Service a04d08
        expected_errors = [
Packit Service a04d08
            'usage: cloud-init analyze', 'usage: cloud-init clean',
Packit Service a04d08
            'usage: cloud-init collect-logs', 'usage: cloud-init devel',
Packit Service a04d08
            'usage: cloud-init status']
Packit Service a04d08
        conditional_subcommands = [
Packit Service a04d08
            'analyze', 'clean', 'collect-logs', 'devel', 'status']
Packit Service a04d08
        # The cloud-init entrypoint calls main without passing sys_argv
Packit Service a04d08
        for subcommand in conditional_subcommands:
Packit Service a04d08
            with mock.patch('sys.argv', ['cloud-init', subcommand, '-h']):
Packit Service a04d08
                try:
Packit Service a04d08
                    cli.main()
Packit Service a04d08
                except SystemExit as e:
Packit Service a04d08
                    self.assertEqual(0, e.code)  # exit 2 on proper -h usage
Packit Service a04d08
        for error_message in expected_errors:
Packit Service a04d08
            self.assertIn(error_message, stdout.getvalue())
Packit Service a04d08
Packit Service a04d08
    def test_analyze_subcommand_parser(self):
Packit Service a04d08
        """The subcommand cloud-init analyze calls the correct subparser."""
Packit Service a04d08
        self._call_main(['cloud-init', 'analyze'])
Packit Service a04d08
        # These subcommands only valid for cloud-init analyze script
Packit Service a04d08
        expected_subcommands = ['blame', 'show', 'dump']
Packit Service a04d08
        error = self.stderr.getvalue()
Packit Service a04d08
        for subcommand in expected_subcommands:
Packit Service a04d08
            self.assertIn(subcommand, error)
Packit Service a04d08
Packit Service a04d08
    def test_collect_logs_subcommand_parser(self):
Packit Service a04d08
        """The subcommand cloud-init collect-logs calls the subparser."""
Packit Service a04d08
        # Provide -h param to collect-logs to avoid having to mock behavior.
Packit Service 751c4a
        stdout = io.StringIO()
Packit Service a04d08
        self.patchStdoutAndStderr(stdout=stdout)
Packit Service a04d08
        self._call_main(['cloud-init', 'collect-logs', '-h'])
Packit Service a04d08
        self.assertIn('usage: cloud-init collect-log', stdout.getvalue())
Packit Service a04d08
Packit Service a04d08
    def test_clean_subcommand_parser(self):
Packit Service a04d08
        """The subcommand cloud-init clean calls the subparser."""
Packit Service a04d08
        # Provide -h param to clean to avoid having to mock behavior.
Packit Service 751c4a
        stdout = io.StringIO()
Packit Service a04d08
        self.patchStdoutAndStderr(stdout=stdout)
Packit Service a04d08
        self._call_main(['cloud-init', 'clean', '-h'])
Packit Service a04d08
        self.assertIn('usage: cloud-init clean', stdout.getvalue())
Packit Service a04d08
Packit Service a04d08
    def test_status_subcommand_parser(self):
Packit Service a04d08
        """The subcommand cloud-init status calls the subparser."""
Packit Service a04d08
        # Provide -h param to clean to avoid having to mock behavior.
Packit Service 751c4a
        stdout = io.StringIO()
Packit Service a04d08
        self.patchStdoutAndStderr(stdout=stdout)
Packit Service a04d08
        self._call_main(['cloud-init', 'status', '-h'])
Packit Service a04d08
        self.assertIn('usage: cloud-init status', stdout.getvalue())
Packit Service a04d08
Packit Service a04d08
    def test_devel_subcommand_parser(self):
Packit Service a04d08
        """The subcommand cloud-init devel calls the correct subparser."""
Packit Service a04d08
        self._call_main(['cloud-init', 'devel'])
Packit Service a04d08
        # These subcommands only valid for cloud-init schema script
Packit Service a04d08
        expected_subcommands = ['schema']
Packit Service a04d08
        error = self.stderr.getvalue()
Packit Service a04d08
        for subcommand in expected_subcommands:
Packit Service a04d08
            self.assertIn(subcommand, error)
Packit Service a04d08
Packit Service a04d08
    def test_wb_devel_schema_subcommand_parser(self):
Packit Service a04d08
        """The subcommand cloud-init schema calls the correct subparser."""
Packit Service a04d08
        exit_code = self._call_main(['cloud-init', 'devel', 'schema'])
Packit Service a04d08
        self.assertEqual(1, exit_code)
Packit Service a04d08
        # Known whitebox output from schema subcommand
Packit Service a04d08
        self.assertEqual(
Packit Service 751c4a
            'Expected either --config-file argument or --docs\n',
Packit Service a04d08
            self.stderr.getvalue())
Packit Service a04d08
Packit Service a04d08
    def test_wb_devel_schema_subcommand_doc_content(self):
Packit Service a04d08
        """Validate that doc content is sane from known examples."""
Packit Service 751c4a
        stdout = io.StringIO()
Packit Service a04d08
        self.patchStdoutAndStderr(stdout=stdout)
Packit Service 751c4a
        self._call_main(['cloud-init', 'devel', 'schema', '--docs', 'all'])
Packit Service a04d08
        expected_doc_sections = [
Packit Service a04d08
            '**Supported distros:** all',
Packit Service 751c4a
            '**Supported distros:** alpine, centos, debian, fedora',
Packit Service a04d08
            '**Config schema**:\n    **resize_rootfs:** (true/false/noblock)',
Packit Service a04d08
            '**Examples**::\n\n    runcmd:\n        - [ ls, -l, / ]\n'
Packit Service a04d08
        ]
Packit Service a04d08
        stdout = stdout.getvalue()
Packit Service a04d08
        for expected in expected_doc_sections:
Packit Service a04d08
            self.assertIn(expected, stdout)
Packit Service a04d08
Packit Service a04d08
    @mock.patch('cloudinit.cmd.main.main_single')
Packit Service a04d08
    def test_single_subcommand(self, m_main_single):
Packit Service a04d08
        """The subcommand 'single' calls main_single with valid args."""
Packit Service a04d08
        self._call_main(['cloud-init', 'single', '--name', 'cc_ntp'])
Packit Service a04d08
        (name, parseargs) = m_main_single.call_args_list[0][0]
Packit Service a04d08
        self.assertEqual('single', name)
Packit Service a04d08
        self.assertEqual('single', parseargs.subcommand)
Packit Service a04d08
        self.assertEqual('single', parseargs.action[0])
Packit Service a04d08
        self.assertFalse(parseargs.debug)
Packit Service a04d08
        self.assertFalse(parseargs.force)
Packit Service a04d08
        self.assertIsNone(parseargs.frequency)
Packit Service a04d08
        self.assertEqual('cc_ntp', parseargs.name)
Packit Service a04d08
        self.assertFalse(parseargs.report)
Packit Service a04d08
Packit Service a04d08
    @mock.patch('cloudinit.cmd.main.dhclient_hook.handle_args')
Packit Service a04d08
    def test_dhclient_hook_subcommand(self, m_handle_args):
Packit Service a04d08
        """The subcommand 'dhclient-hook' calls dhclient_hook with args."""
Packit Service a04d08
        self._call_main(['cloud-init', 'dhclient-hook', 'up', 'eth0'])
Packit Service a04d08
        (name, parseargs) = m_handle_args.call_args_list[0][0]
Packit Service a04d08
        self.assertEqual('dhclient-hook', name)
Packit Service a04d08
        self.assertEqual('dhclient-hook', parseargs.subcommand)
Packit Service a04d08
        self.assertEqual('dhclient-hook', parseargs.action[0])
Packit Service a04d08
        self.assertFalse(parseargs.debug)
Packit Service a04d08
        self.assertFalse(parseargs.force)
Packit Service a04d08
        self.assertEqual('up', parseargs.event)
Packit Service a04d08
        self.assertEqual('eth0', parseargs.interface)
Packit Service a04d08
Packit Service a04d08
    @mock.patch('cloudinit.cmd.main.main_features')
Packit Service a04d08
    def test_features_hook_subcommand(self, m_features):
Packit Service a04d08
        """The subcommand 'features' calls main_features with args."""
Packit Service a04d08
        self._call_main(['cloud-init', 'features'])
Packit Service a04d08
        (name, parseargs) = m_features.call_args_list[0][0]
Packit Service a04d08
        self.assertEqual('features', name)
Packit Service a04d08
        self.assertEqual('features', parseargs.subcommand)
Packit Service a04d08
        self.assertEqual('features', parseargs.action[0])
Packit Service a04d08
        self.assertFalse(parseargs.debug)
Packit Service a04d08
        self.assertFalse(parseargs.force)
Packit Service a04d08
Packit Service a04d08
# : ts=4 expandtab