Allow matching on hostname and username
This commit is contained in:
parent
fe27ee2d79
commit
e797ba4800
@ -46,7 +46,7 @@ quick example:
|
|||||||
copy env-files
|
copy env-files
|
||||||
env SOMETHING=else
|
env SOMETHING=else
|
||||||
|
|
||||||
hostname somehost
|
hostname someuser@somehost
|
||||||
copy --dest=foo/bar some-file
|
copy --dest=foo/bar some-file
|
||||||
copy --glob some/files.*
|
copy --glob some/files.*
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
|
||||||
|
import fnmatch
|
||||||
import os
|
import os
|
||||||
from typing import Any, Dict, Iterable, Optional
|
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')
|
defconf = os.path.join(config_dir, 'ssh.conf')
|
||||||
|
|
||||||
|
|
||||||
def options_for_host(hostname: str, per_host_opts: Dict[str, SSHOptions]) -> SSHOptions:
|
def host_matches(pat: str, hostname: str, username: str) -> bool:
|
||||||
import fnmatch
|
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 = []
|
matches = []
|
||||||
for spat, opts in per_host_opts.items():
|
for spat, opts in per_host_opts.items():
|
||||||
for pat in spat.split():
|
for pat in spat.split():
|
||||||
if fnmatch.fnmatchcase(hostname, pat):
|
if host_matches(pat, hostname, username):
|
||||||
matches.append(opts)
|
matches.append(opts)
|
||||||
if not matches:
|
if not matches:
|
||||||
return SSHOptions({})
|
return SSHOptions({})
|
||||||
@ -45,7 +52,9 @@ def load_config(*paths: str, overrides: Optional[Iterable[str]] = None) -> Dict[
|
|||||||
from .options.parse import (
|
from .options.parse import (
|
||||||
create_result_dict, merge_result_dicts, parse_conf_item
|
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]:
|
def merge_dicts(base: Dict[str, Any], vals: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
base_phd = get_per_hosts_dict(base)
|
base_phd = get_per_hosts_dict(base)
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import time
|
|||||||
import traceback
|
import traceback
|
||||||
from base64 import standard_b64decode
|
from base64 import standard_b64decode
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
from getpass import getuser
|
||||||
from typing import (
|
from typing import (
|
||||||
Any, Callable, Dict, Iterator, List, NoReturn, Optional, Set, Tuple, Union
|
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']
|
hostname = md['hostname']
|
||||||
pw = md['pw']
|
pw = md['pw']
|
||||||
pwfilename = md['pwfile']
|
pwfilename = md['pwfile']
|
||||||
|
username = md['user']
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
yield fmt_prefix('!invalid ssh data request message')
|
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()
|
traceback.print_exc()
|
||||||
yield fmt_prefix('!incorrect ssh data password')
|
yield fmt_prefix('!incorrect ssh data password')
|
||||||
else:
|
else:
|
||||||
resolved_ssh_opts = options_for_host(hostname, ssh_opts)
|
resolved_ssh_opts = options_for_host(hostname, username, ssh_opts)
|
||||||
try:
|
try:
|
||||||
data = make_tarfile(resolved_ssh_opts, env_data['env'])
|
data = make_tarfile(resolved_ssh_opts, env_data['env'])
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -348,8 +350,18 @@ def main(args: List[str]) -> NoReturn:
|
|||||||
cmd.append('-t')
|
cmd.append('-t')
|
||||||
cmd.append('--')
|
cmd.append('--')
|
||||||
cmd.append(hostname)
|
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]
|
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)
|
os.execvp('ssh', cmd)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -27,6 +27,7 @@ opt('hostname', '*', option_type='hostname',
|
|||||||
long_text='''
|
long_text='''
|
||||||
The hostname the following options apply to. A glob pattern to match multiple
|
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.
|
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
|
When not specified options apply to all hosts, until the
|
||||||
first hostname specification is found. Note that the hostname this matches
|
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
|
against is the hostname used by the remote computer, not the name you pass
|
||||||
|
|||||||
@ -57,10 +57,10 @@ print(' '.join(map(str, buf)))'''), lines=13, cols=77)
|
|||||||
def parse(conf):
|
def parse(conf):
|
||||||
return load_config(overrides=conf.splitlines())
|
return load_config(overrides=conf.splitlines())
|
||||||
|
|
||||||
def for_host(hostname, conf):
|
def for_host(hostname, conf, username='kitty'):
|
||||||
if isinstance(conf, str):
|
if isinstance(conf, str):
|
||||||
conf = parse(conf)
|
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, {})
|
||||||
self.ae(for_host('x', 'env a=b').env, {'a': 'b'})
|
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('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': ''})
|
||||||
self.ae(for_host('x', 'env a').env, {'a': '_delete_this_env_var_'})
|
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
|
@property
|
||||||
@lru_cache()
|
@lru_cache()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user