pass_typer: Try more candidate globs for user and host variants

This commit is contained in:
Adam Goldsmith 2023-04-19 12:10:20 -04:00
parent 7c9f062061
commit f0c26851e5

View File

@ -1,11 +1,14 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import dataclasses
import getpass
import os import os
import re import re
import subprocess import subprocess
from collections.abc import Iterator
from functools import partial from functools import partial
from itertools import chain
from pathlib import Path from pathlib import Path
from typing import Optional
PASSWORD_STORE = Path( PASSWORD_STORE = Path(
os.environ.get("PASSWORD_STORE_DIR", Path("~/.password-store").expanduser()) os.environ.get("PASSWORD_STORE_DIR", Path("~/.password-store").expanduser())
@ -14,18 +17,79 @@ PASSWORD_STORE = Path(
cmd = partial(subprocess.run, capture_output=True, encoding="ascii") cmd = partial(subprocess.run, capture_output=True, encoding="ascii")
def alias(server_name): @dataclasses.dataclass
match server_name.rpartition("@"): class ServerMatch:
case [user, at, ("salt" | "salt.sawtooth.claremontmakerspace.org")]: folder: Optional[str]
return f"cms/{user}{at}cms-net-svcs" user: Optional[str]
case [user, at, host] if host.lower().startswith("cms-wall-display") or host.startswith("iPad"): host: str
return f"cms/{user}{at}ipads"
case [user, at, ("octopi-taz-6" | "octopi-lulzbot-mini")]: @classmethod
return f"cms/{user}{at}octopi" def from_destination(cls, server_name: str) -> 'ServerMatch':
case [user, at, server] if re.match(".*-pidgey(.vpn)?", server): user, _, host = server_name.rpartition("@")
return "tsrc/pidgey" return cls(None, user if user != "" else None, host).alias()
case _:
return f"**/{server_name}" def alias(self) -> 'ServerMatch':
# TODO: this still feels a bit hacky
host = self.host
if host.endswith(".sawtooth.claremontmakerspace.org"):
self.folder = "cms"
host = self.host.removesuffix(".sawtooth.claremontmakerspace.org")
if host == "salt":
self.folder = "cms"
self.host = "cms-net-svcs"
elif host.lower().startswith("cms-wall-display") or host.startswith("iPad"):
self.folder = "cms"
self.host = "ipads"
elif host in ("octopi-taz-6", "octopi-lulzbot-mini"):
self.folder = "cms"
self.host = "octopi"
elif re.match(".*-pidgey(.vpn)?", host):
self.folder = "tsrc"
self.host = "pidgey"
return self
def to_globs(self) -> Iterator[Path]:
path = Path("servers")
if self.folder is not None:
path = path / Path(self.folder)
else:
path = path / "**"
user_candidates = [None, getpass.getuser()]
if self.user is None:
user_candidates.append("*")
else:
user_candidates.insert(0, self.user)
# try host without each subdomain level
host_parts = self.host.split(".")
host_candidates = [
".".join(host_parts[:i]) for i in range(len(host_parts), 0, -1)
]
host_candidates.append(self.host + ".*")
# generate all combinations of user and host
for user in user_candidates:
for host in host_candidates:
if user is None:
yield path / f"{host}.gpg"
else:
yield path / f"{user}@{host}.gpg"
def to_paths(self) -> Iterator[Path]:
print("Candidate globs:")
for glob in self.to_globs():
print(glob)
yield from PASSWORD_STORE.glob(str(glob))
def __str__(self) -> str:
folder = self.folder if self.folder is not None else '**'
if self.user is not None and self.user != "":
return f"{folder}/{self.user}@{self.host}"
else:
return f"{folder}/{self.host}"
def notify(summary: str, body: str) -> None: def notify(summary: str, body: str) -> None:
@ -44,15 +108,15 @@ def get_password(password_name: str) -> str:
return password return password
def select_and_type(server_name: Path) -> None: def select_and_type(server_match: ServerMatch) -> None:
path = ("servers" / server_name).with_name(server_name.name + ".gpg") paths = server_match.to_paths()
files = chain(
PASSWORD_STORE.glob(str(path)),
PASSWORD_STORE.glob(str(path.with_name("*@" + path.name))),
)
file_list = list( file_list = list(
dict.fromkeys(str(f.relative_to(PASSWORD_STORE).with_suffix("")) for f in files) dict.fromkeys(str(f.relative_to(PASSWORD_STORE).with_suffix("")) for f in paths)
) )
if len(file_list) == 0:
notify(f"No matches found for '{server_match}'", "")
return
selected = rofi_select(file_list) selected = rofi_select(file_list)
if selected: if selected:
@ -63,13 +127,13 @@ def select_and_type(server_name: Path) -> None:
window_name = cmd(["xdotool", "getactivewindow", "getwindowname"]).stdout.strip() window_name = cmd(["xdotool", "getactivewindow", "getwindowname"]).stdout.strip()
ssh_match = re.search(":([^ ]+=[^ ]* )*(mosh|ssh) (?P<server>.*)", window_name) ssh_match = re.search(":([^ ]+=[^ ]* )*(mosh|ssh) (?P<destination>.*)", window_name)
if ssh_match: if ssh_match:
server_name = alias(ssh_match.group("server")) server_match = ServerMatch.from_destination(ssh_match.group("destination"))
notify(f"Matched server '{server_name}'", f"Window name: {window_name}") notify(f"Matched server '{server_match}'", f"Window name: {window_name}")
select_and_type(Path(server_name)) select_and_type(server_match)
else: else:
notify("Window name did not match any rules", f"Window name: {window_name}") notify("Window name did not match any rules", f"Window name: {window_name}")