diff --git a/satori/errors.py b/satori/errors.py index 822f129..314ca3e 100644 --- a/satori/errors.py +++ b/satori/errors.py @@ -61,6 +61,11 @@ class SystemInfoNotJson(DiscoveryException): """Command did not produce valid JSON.""" +class SystemInfoMissingJson(DiscoveryException): + + """Command did not produce stdout containing JSON.""" + + class SystemInfoCommandInstallFailed(DiscoveryException): """Failed to install package that provides system information.""" diff --git a/satori/sysinfo/ohai_solo.py b/satori/sysinfo/ohai_solo.py index a912245..3fcd4e5 100644 --- a/satori/sysinfo/ohai_solo.py +++ b/satori/sysinfo/ohai_solo.py @@ -47,10 +47,12 @@ def system_info(ssh_client): :param ssh_client: :class:`ssh.SSH` instance :returns: dict -- system information from ohai-solo :raises: SystemInfoCommandMissing, SystemInfoCommandOld, SystemInfoNotJson + SystemInfoMissingJson SystemInfoCommandMissing if `ohai` is not installed. SystemInfoCommandOld if `ohai` is not the latest. - SystemInfoNotJson if `ohai` does not return valid json. + SystemInfoNotJson if `ohai` does not return valid JSON. + SystemInfoMissingJson if `ohai` does not return any JSON. """ output = ssh_client.remote_execute("sudo -i ohai-solo") not_found_msgs = ["command not found", "Could not find ohai"] @@ -59,10 +61,15 @@ def system_info(ssh_client): LOG.warning("SystemInfoCommandMissing on host: [%s]", ssh_client.host) raise errors.SystemInfoCommandMissing("ohai-solo missing on %s", ssh_client.host) + unicode_output = unicode(output['stdout'], errors='replace') try: - results = json.loads(unicode(output['stdout'], errors='replace')) + results = json.loads(unicode_output) except ValueError as exc: - raise errors.SystemInfoNotJson(exc) + try: + clean_output = get_json(unicode_output) + results = json.loads(clean_output) + except ValueError as exc: + raise errors.SystemInfoNotJson(exc) return results @@ -119,3 +126,21 @@ def remove_remote(ssh_client): command = "cd /tmp && %s" % remove output = ssh_client.remote_execute(command) return output + + +def get_json(data): + """Find the JSON string in data and return a string. + + :param data: :string: + :returns: string -- JSON string striped of non-JSON data + :raises: SystemInfoMissingJson + + SystemInfoMissingJson if `ohai` does not return any JSON. + """ + try: + first = data.index('{') + last = data.rindex('}') + return data[first:last + 1] + except ValueError as e: + context = {"ValueError": "%s" % e} + raise errors.SystemInfoMissingJson(context) diff --git a/satori/tests/test_sysinfo_ohai_solo.py b/satori/tests/test_sysinfo_ohai_solo.py index 664d6a2..d12dcdc 100644 --- a/satori/tests/test_sysinfo_ohai_solo.py +++ b/satori/tests/test_sysinfo_ohai_solo.py @@ -111,19 +111,39 @@ class TestSystemInfo(utils.TestCase): 'stdout': "{}", 'stderr': "" } - result = ohai_solo.system_info(mock_ssh) - mock_ssh.remote_execute("sudo -i ohai-solo") + ohai_solo.system_info(mock_ssh) + mock_ssh.remote_execute.assert_called_with("sudo -i ohai-solo") + + def test_system_info_with_motd(self): + mock_ssh = mock.MagicMock() + mock_ssh.remote_execute.return_value = { + 'exit_code': 0, + 'stdout': "Hello world\n {}", + 'stderr': "" + } + ohai_solo.system_info(mock_ssh) + mock_ssh.remote_execute.assert_called_with("sudo -i ohai-solo") def test_system_info_bad_json(self): mock_ssh = mock.MagicMock() mock_ssh.remote_execute.return_value = { 'exit_code': 0, - 'stdout': "", + 'stdout': "{Not JSON!}", 'stderr': "" } self.assertRaises(errors.SystemInfoNotJson, ohai_solo.system_info, mock_ssh) + def test_system_info_missing_json(self): + mock_ssh = mock.MagicMock() + mock_ssh.remote_execute.return_value = { + 'exit_code': 0, + 'stdout': "No JSON!", + 'stderr': "" + } + self.assertRaises(errors.SystemInfoMissingJson, ohai_solo.system_info, + mock_ssh) + def test_system_info_command_not_found(self): mock_ssh = mock.MagicMock() mock_ssh.remote_execute.return_value = {