machine: validation fixes

Protect the sudo request with a lock.  Add retries to the sudo
commands.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Eric Callahan 2022-10-15 08:37:45 -04:00
parent f0e4678fab
commit 00c4846d11
No known key found for this signature in database
GPG Key ID: 5A1EB336DFB4C71B
1 changed files with 61 additions and 50 deletions

View File

@ -86,6 +86,7 @@ class Machine:
dist_info['release_info'] = distro.distro_release_info()
self.inside_container = False
self.moonraker_service_info: Dict[str, Any] = {}
self.sudo_req_lock = asyncio.Lock()
self._sudo_password: Optional[str] = None
sudo_template = config.gettemplate("sudo_password", None)
if sudo_template is not None:
@ -282,50 +283,53 @@ class Machine:
async def _set_sudo_password(
self, web_request: WebRequest
) -> Dict[str, Any]:
self._sudo_password = web_request.get_str("password")
if not await self.check_sudo_access():
self._sudo_password = None
raise self.server.error("Invalid password, sudo access was denied")
sudo_responses = ["Sudo password successfully set."]
restart: bool = False
failed: List[Tuple[SudoCallback, str]] = []
failed_msgs: List[str] = []
if self.sudo_requests:
while self.sudo_requests:
cb, msg = self.sudo_requests.pop(0)
try:
ret = cb()
if isinstance(ret, Awaitable):
ret = await ret
msg, need_restart = ret
sudo_responses.append(msg)
restart |= need_restart
except asyncio.CancelledError:
raise
except Exception as e:
failed.append((cb, msg))
failed_msgs.append(str(e))
restart = False if len(failed) > 0 else restart
self.sudo_requests = failed
if not restart and len(sudo_responses) > 1:
# at least one successful response and not restarting
eventloop = self.server.get_event_loop()
eventloop.delay_callback(
.05, self.server.send_event,
"machine:sudo_alert",
{
"sudo_requested": self.sudo_requested,
"request_messages": self.sudo_request_messages
}
)
if failed_msgs:
err_msg = "\n".join(failed_msgs)
raise self.server.error(err_msg, 500)
if restart:
self.restart_moonraker_service()
sudo_responses.append(
"Moonraker is currently in the process of restarting."
async with self.sudo_req_lock:
self._sudo_password = web_request.get_str("password")
if not await self.check_sudo_access():
self._sudo_password = None
raise self.server.error(
"Invalid password, sudo access was denied"
)
sudo_responses = ["Sudo password successfully set."]
restart: bool = False
failed: List[Tuple[SudoCallback, str]] = []
failed_msgs: List[str] = []
if self.sudo_requests:
while self.sudo_requests:
cb, msg = self.sudo_requests.pop(0)
try:
ret = cb()
if isinstance(ret, Awaitable):
ret = await ret
msg, need_restart = ret
sudo_responses.append(msg)
restart |= need_restart
except asyncio.CancelledError:
raise
except Exception as e:
failed.append((cb, msg))
failed_msgs.append(str(e))
restart = False if len(failed) > 0 else restart
self.sudo_requests = failed
if not restart and len(sudo_responses) > 1:
# at least one successful response and not restarting
eventloop = self.server.get_event_loop()
eventloop.delay_callback(
.05, self.server.send_event,
"machine:sudo_alert",
{
"sudo_requested": self.sudo_requested,
"request_messages": self.sudo_request_messages
}
)
if failed_msgs:
err_msg = "\n".join(failed_msgs)
raise self.server.error(err_msg, 500)
if restart:
self.restart_moonraker_service()
sudo_responses.append(
"Moonraker is currently in the process of restarting."
)
return {
"sudo_responses": sudo_responses,
"is_restarting": restart
@ -391,7 +395,7 @@ class Machine:
return False
return True
async def exec_sudo_command(self, command: str) -> str:
async def exec_sudo_command(self, command: str, tries: int = 1) -> str:
proc_input = None
full_cmd = f"sudo {command}"
if self._sudo_password is not None:
@ -399,7 +403,7 @@ class Machine:
full_cmd = f"sudo -S {command}"
shell_cmd: SCMDComp = self.server.lookup_component("shell_command")
return await shell_cmd.exec_cmd(
full_cmd, proc_input=proc_input, log_complete=False
full_cmd, proc_input=proc_input, log_complete=False, retries=tries
)
def _get_sdcard_info(self) -> Dict[str, Any]:
@ -861,7 +865,7 @@ class SystemdCliProvider(BaseProvider):
)
prop_args = ",".join(properties)
props: str = await self.shell_cmd.exec_cmd(
f"systemctl show -p {prop_args} {unit_name}"
f"systemctl show -p {prop_args} {unit_name}", retries=5
)
raw_props: Dict[str, Any] = {}
lines = [p.strip() for p in props.split("\n") if p.strip]
@ -1276,6 +1280,7 @@ class InstallValidator:
"must be updated manually."
)
if unit != "moonraker":
logging.info(f"Custom service file detected: {unit}")
# Not using he default unit name
if app_args["is_default_data_path"]:
# No datapath set, create a new, unique data path
@ -1290,11 +1295,13 @@ class InstallValidator:
f"data path '{new_dp}' already exists. Service file "
"must be updated manually."
)
# If the current path is bare we can remove it
if self._check_path_bare(self.data_path):
shutil.rmtree(self.data_path)
self.data_path = new_dp
if not self.data_path.exists():
logging.info(f"New data path created at {self.data_path}")
self.data_path.mkdir()
# A non-default datapath requires successful update of the
# service
@ -1362,8 +1369,13 @@ class InstallValidator:
% (SERVICE_VERSION, user, src_path, env_file, sys.executable)
)
try:
await machine.exec_sudo_command(f"cp -f {tmp_svc} {svc_dest}")
await machine.exec_sudo_command("systemctl daemon-reload")
# write new environment
env_file.write_text(ENVIRONMENT % (src_path, cmd_args))
await machine.exec_sudo_command(
f"cp -f {tmp_svc} {svc_dest}", tries=5)
await machine.exec_sudo_command(
"systemctl daemon-reload", tries=5
)
except asyncio.CancelledError:
raise
except Exception:
@ -1374,9 +1386,8 @@ class InstallValidator:
) from None
finally:
tmp_svc.unlink()
# write new environment
env_file.write_text(ENVIRONMENT % (src_path, cmd_args))
self.data_path_valid = True
self.sc_enabled = False
return True
def _check_path_bare(self, path: pathlib.Path) -> bool: