diff --git a/AUTHORS.rst b/AUTHORS.rst index 67ce357..8228781 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -3,3 +3,4 @@ Contributors ============ * Josiah Baldwin +* Daan Selen \ No newline at end of file diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 844025c..9f5673a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,17 @@ Changelog ========= +version 1.3.0 +============= + +Improvements: + * Improved how run_commands was handled (#51) + * Added remove device functionality (#52) + * Added run_console_commands functionality (#55) + +Bugs: + * Silly documentation being wrong (#53) + version 1.2.2 ============= diff --git a/src/meshctrl/device.py b/src/meshctrl/device.py index 3efefe8..ee9a651 100644 --- a/src/meshctrl/device.py +++ b/src/meshctrl/device.py @@ -295,6 +295,23 @@ class Device(object): ''' return await self._session.reset_devices(self.nodeid, timeout=timeout) + async def remove(self, timeout=None): + ''' + Remove device from MeshCentral + + Args: + nodeids (str|list[str]): nodeid(s) of the device(s) that have to be removed + timeout (int): duration in seconds to wait for a response before throwing an error + + Returns: + bool: True on success, raise otherwise + + Raises: + :py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure + asyncio.TimeoutError: Command timed out + ''' + return self._session.remove_devices(self.nodeid, timeout) + async def sleep(self, timeout=None): ''' Sleep device diff --git a/src/meshctrl/files.py b/src/meshctrl/files.py index b8958ac..3a74bb5 100644 --- a/src/meshctrl/files.py +++ b/src/meshctrl/files.py @@ -157,7 +157,7 @@ class Files(tunnel.Tunnel): async def rm(self, path, files, recursive=False, timeout=None): """ - Create a directory on the device. This API doesn't error if the file doesn't exist. + Remove a set of files or directories from the device. This API doesn't error if the file doesn't exist. Args: path (str): Directory from which to delete files diff --git a/src/meshctrl/session.py b/src/meshctrl/session.py index e6d1897..1c2554f 100644 --- a/src/meshctrl/session.py +++ b/src/meshctrl/session.py @@ -571,7 +571,7 @@ class Session(object): while True: data = await event_queue.get() if filter and not util.compare_dict(filter, data): - continue + continue yield data finally: self._eventer.off("server_event", _) @@ -1062,6 +1062,30 @@ class Session(object): raise exceptions.ServerError(data["result"]) return True + async def remove_devices(self, nodeids, timeout=None): + ''' + Remove device(s) from MeshCentral + + Args: + nodeids (str|list[str]): nodeid(s) of the device(s) that have to be removed + timeout (int): duration in seconds to wait for a response before throwing an error + + Returns: + bool: True on success, raise otherwise + + Raises: + :py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure + :py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure + asyncio.TimeoutError: Command timed out + ''' + if isinstance(nodeids, str): + nodeids = [nodeids] + + data = await self._send_command({ "action": 'removedevices', "nodeids": nodeids}, "remove_devices", timeout=timeout) + + if data.get("result", "ok").lower() != "ok": + raise exceptions.ServerError(data["result"]) + return True async def add_device_group(self, name, description="", amtonly=False, features=0, consent=0, timeout=None): ''' @@ -1488,25 +1512,35 @@ class Session(object): async def __(command): data = await self._send_command(command, "run_command", timeout=timeout) - if data.get("result", "ok").lower() != "ok": + if data.get("type", None) != "runcommands" and data.get("result", "ok").lower() != "ok": raise exceptions.ServerError(data["result"]) - - expect_response = False - if not ignore_output: - userid = (await self.user_info())["_id"] - for n in nodeids: - device_info = await self.device_info(n, timeout=timeout) - try: - permissions = device_info.mesh.links.get(userid, {}).get("rights",constants.DeviceRights.norights)\ - # This should work for device rights, but it only seems to work for mesh rights. Not sure why, but I can't get the events to show up when the user only has individual device rights - # |device_info.get("links", {}).get(userid, {}).get("rights", constants.DeviceRights.norights) - # If we don't have agentconsole rights, we won't be able te read the output, so fill in blanks on this node - if not permissions&constants.DeviceRights.agentconsole: - result[n]["complete"] = True - else: - expect_response = True - except AttributeError: - result[n]["complete"] = True + elif data.get("type", None) != "runcommands" and data.get("result", "ok").lower() == "ok": + expect_response = False + console_task = tg.create_task(asyncio.wait_for(_console(), timeout=timeout)) + if not ignore_output: + userid = (await self.user_info())["_id"] + for n in nodeids: + device_info = await self.device_info(n, timeout=timeout) + try: + permissions = device_info.mesh.links.get(userid, {}).get("rights",constants.DeviceRights.norights)\ + # This should work for device rights, but it only seems to work for mesh rights. Not sure why, but I can't get the events to show up when the user only has individual device rights + # |device_info.get("links", {}).get(userid, {}).get("rights", constants.DeviceRights.norights) + # If we don't have agentconsole rights, we won't be able te read the output, so fill in blanks on this node + if not permissions&constants.DeviceRights.agentconsole: + result[n]["complete"] = True + else: + expect_response = True + except AttributeError: + result[n]["complete"] = True + if expect_response: + tasks.append(console_task) + else: + console_task.cancel() + elif data.get("type", None) == "runcommands" and not ignore_output: + tasks.append(tg.create_task(asyncio.wait_for(_reply(data["responseid"], start_data=data), timeout=timeout))) + # Force this to run immediately? This might be odd; but we want to make sure we get don't lose the race condition with the srever. + # Not sure if this actually works but I haven't yet seen it fail. *shrug* + await asyncio.sleep(0) tasks = [] async with asyncio.TaskGroup() as tg: diff --git a/tests/test_session.py b/tests/test_session.py index ca2d163..797fb7d 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -313,6 +313,15 @@ async def test_mesh_device(env): r = await admin_session.remove_users_from_device_group((await privileged_session.user_info())["_id"], mesh.meshid, timeout=10) print("\ninfo remove_users_from_device_group: {}\n".format(r)) assert (r[(await privileged_session.user_info())["_id"]]["success"]), "Failed to remove user from device group" + + await admin_session.remove_devices(agent2.nodeid, timeout=10) + try: + await admin_session.device_info(agent2.nodeid, timeout=10) + except ValueError: + pass + else: + raise Exception("Device not deleted") + assert (await admin_session.remove_users_from_device(agent.nodeid, (await unprivileged_session.user_info())["_id"], timeout=10)), "Failed to remove user from device"