Allow matching on hostname and username

This commit is contained in:
Kovid Goyal 2022-03-04 12:42:31 +05:30
parent fe27ee2d79
commit e797ba4800
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 40 additions and 9 deletions

View File

@ -46,7 +46,7 @@ quick example:
copy env-files
env SOMETHING=else
hostname somehost
hostname someuser@somehost
copy --dest=foo/bar some-file
copy --glob some/files.*

View File

@ -2,6 +2,7 @@
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
import fnmatch
import os
from typing import Any, Dict, Iterable, Optional
@ -16,12 +17,18 @@ SYSTEM_CONF = '/etc/xdg/kitty/ssh.conf'
defconf = os.path.join(config_dir, 'ssh.conf')
def options_for_host(hostname: str, per_host_opts: Dict[str, SSHOptions]) -> SSHOptions:
import fnmatch
def host_matches(pat: str, hostname: str, username: str) -> bool:
upat = '*'
if '@' in pat:
upat, pat = pat.split('@', 1)
return fnmatch.fnmatchcase(hostname, pat) and fnmatch.fnmatchcase(username, upat)
def options_for_host(hostname: str, username: str, per_host_opts: Dict[str, SSHOptions]) -> SSHOptions:
matches = []
for spat, opts in per_host_opts.items():
for pat in spat.split():
if fnmatch.fnmatchcase(hostname, pat):
if host_matches(pat, hostname, username):
matches.append(opts)
if not matches:
return SSHOptions({})
@ -45,7 +52,9 @@ def load_config(*paths: str, overrides: Optional[Iterable[str]] = None) -> Dict[
from .options.parse import (
create_result_dict, merge_result_dicts, parse_conf_item
)
from .options.utils import get_per_hosts_dict, init_results_dict, first_seen_positions
from .options.utils import (
first_seen_positions, get_per_hosts_dict, init_results_dict
)
def merge_dicts(base: Dict[str, Any], vals: Dict[str, Any]) -> Dict[str, Any]:
base_phd = get_per_hosts_dict(base)

View File

@ -15,6 +15,7 @@ import time
import traceback
from base64 import standard_b64decode
from contextlib import suppress
from getpass import getuser
from typing import (
Any, Callable, Dict, Iterator, List, NoReturn, Optional, Set, Tuple, Union
)
@ -131,6 +132,7 @@ def get_ssh_data(msg: str, ssh_opts: Optional[Dict[str, SSHOptions]] = None) ->
hostname = md['hostname']
pw = md['pw']
pwfilename = md['pwfile']
username = md['user']
except Exception:
traceback.print_exc()
yield fmt_prefix('!invalid ssh data request message')
@ -145,7 +147,7 @@ def get_ssh_data(msg: str, ssh_opts: Optional[Dict[str, SSHOptions]] = None) ->
traceback.print_exc()
yield fmt_prefix('!incorrect ssh data password')
else:
resolved_ssh_opts = options_for_host(hostname, ssh_opts)
resolved_ssh_opts = options_for_host(hostname, username, ssh_opts)
try:
data = make_tarfile(resolved_ssh_opts, env_data['env'])
except Exception:
@ -348,8 +350,18 @@ def main(args: List[str]) -> NoReturn:
cmd.append('-t')
cmd.append('--')
cmd.append(hostname)
uname = getuser()
if hostname.startswith('ssh://'):
from urllib.parse import urlparse
purl = urlparse(hostname)
hostname_for_match = purl.hostname or hostname
uname = purl.username or uname
elif '@' in hostname and hostname[0] != '@':
uname, hostname_for_match = hostname.split('@', 1)
else:
hostname_for_match = hostname
hostname_for_match = hostname.split('@', 1)[-1].split(':', 1)[0]
cmd += get_remote_command(remote_args, hostname, options_for_host(hostname_for_match, load_ssh_options()).interpreter)
cmd += get_remote_command(remote_args, hostname, options_for_host(hostname_for_match, uname, load_ssh_options()).interpreter)
os.execvp('ssh', cmd)

View File

@ -27,6 +27,7 @@ opt('hostname', '*', option_type='hostname',
long_text='''
The hostname the following options apply to. A glob pattern to match multiple
hosts can be used. Multiple hostnames can also be specified separated by spaces.
The hostname can include an optional username in the form :code:`user@host`.
When not specified options apply to all hosts, until the
first hostname specification is found. Note that the hostname this matches
against is the hostname used by the remote computer, not the name you pass

View File

@ -57,10 +57,10 @@ print(' '.join(map(str, buf)))'''), lines=13, cols=77)
def parse(conf):
return load_config(overrides=conf.splitlines())
def for_host(hostname, conf):
def for_host(hostname, conf, username='kitty'):
if isinstance(conf, str):
conf = parse(conf)
return options_for_host(hostname, conf)
return options_for_host(hostname, username, conf)
self.ae(for_host('x', '').env, {})
self.ae(for_host('x', 'env a=b').env, {'a': 'b'})
@ -70,6 +70,15 @@ print(' '.join(map(str, buf)))'''), lines=13, cols=77)
self.ae(for_host('2', pc).env, {'a': 'c', 'b': 'b'})
self.ae(for_host('x', 'env a=').env, {'a': ''})
self.ae(for_host('x', 'env a').env, {'a': '_delete_this_env_var_'})
pc = parse('env a=b\nhostname test@2\nenv a=c\nenv b=b')
self.ae(set(pc.keys()), {'*', 'test@2'})
self.ae(for_host('x', pc).env, {'a': 'b'})
self.ae(for_host('2', pc).env, {'a': 'b'})
self.ae(for_host('2', pc, 'test').env, {'a': 'c', 'b': 'b'})
pc = parse('env a=b\nhostname 1 2\nenv a=c\nenv b=b')
self.ae(for_host('x', pc).env, {'a': 'b'})
self.ae(for_host('1', pc).env, {'a': 'c', 'b': 'b'})
self.ae(for_host('2', pc).env, {'a': 'c', 'b': 'b'})
@property
@lru_cache()