Skip to content

Commit 68c05ea

Browse files
committed
Add functions with arguments for constructing SSH command and config strings
1 parent 50aa421 commit 68c05ea

File tree

5 files changed

+143
-56
lines changed

5 files changed

+143
-56
lines changed

hostedpi/cli/options.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
Option("--launchpad", "--lp", help="Launchpad usernames to source SSH keys from"),
2424
]
2525
ipv6 = Annotated[bool, Option(help="Use the IPv6 connection method")]
26-
numeric = Annotated[bool, Option("-n", help="Use numeric IPv6 address in SSH command")]
26+
numeric = Annotated[bool, Option("--numeric", "-n", help="Use numeric IPv6 address in SSH command")]
27+
user = Annotated[str, Option("--username", "-u", help="Username for SSH connection")]
2728
yes = Annotated[bool, Option("--yes", "-y", help="Proceed without confirmation")]
2829
number = Annotated[Union[int, None], Option(help="Number of Raspberry Pi servers to create", min=1)]
2930
full_table = Annotated[bool, Option(help="Show full table of Raspberry Pi server info")]

hostedpi/cli/ssh.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212

1313
@ssh_app.command("command")
1414
def do_command(
15-
name: arguments.server_name, ipv6: options.ipv6 = False, numeric: options.numeric = False
15+
name: arguments.server_name,
16+
ipv6: options.ipv6 = False,
17+
numeric: options.numeric = False,
18+
user: options.user = "root",
1619
):
1720
"""
1821
Get the SSH command to connect to a Raspberry Pi server
@@ -23,14 +26,11 @@ def do_command(
2326
raise Exit(1)
2427
try:
2528
if ipv6:
26-
if numeric:
27-
print(pi.ipv6_ssh_command_numeric)
28-
else:
29-
print(pi.ipv6_ssh_command)
29+
print(pi.get_ipv6_ssh_command(numeric=numeric, user=user))
3030
else:
31-
print(pi.ipv4_ssh_command)
31+
print(pi.get_ipv4_ssh_command(user=user))
3232
except HostedPiException as exc:
33-
print(f"hostedpi error: {exc}")
33+
print_error(f"hostedpi error: {exc}")
3434
raise Exit(1)
3535

3636

@@ -40,20 +40,21 @@ def do_config(
4040
filter: options.filter_pattern_pi = None,
4141
ipv6: options.ipv6 = False,
4242
numeric: options.numeric = False,
43+
user: options.user = "root",
4344
):
4445
"""
4546
Get the SSH config to connect to one or more Raspberry Pi servers
4647
"""
48+
if numeric and not ipv6:
49+
print_error("--numeric is only supported with --ipv6")
50+
4751
pis = get_pis(names, filter)
4852
for pi in pis:
4953
try:
5054
if ipv6:
51-
if numeric:
52-
print(pi.ipv6_ssh_config_numeric)
53-
else:
54-
print(pi.ipv6_ssh_config)
55+
print(pi.get_ipv6_ssh_config(numeric=numeric, user=user))
5556
else:
56-
print(pi.ipv4_ssh_config)
57+
print(pi.get_ipv4_ssh_config(user=user))
5758
except HostedPiException as exc:
58-
print(f"hostedpi error: {exc}")
59+
print_error(f"hostedpi error: {exc}")
5960
raise Exit(1)

hostedpi/pi.py

Lines changed: 52 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -274,58 +274,32 @@ def ipv6_ssh_hostname(self) -> str:
274274
@property
275275
def ipv4_ssh_command(self) -> str:
276276
"""
277-
The SSH command required to connect to the Pi using SSH over IPv4
277+
The default SSH command required to connect to the Pi using SSH over IPv4
278278
"""
279-
return f"ssh -p {self.ipv4_ssh_port} root@{self.ipv4_ssh_hostname}"
279+
return self.get_ipv4_ssh_command()
280280

281281
@property
282282
def ipv6_ssh_command(self) -> str:
283283
"""
284-
The SSH command required to connect to the Pi using SSH over IPv6
284+
The default SSH command required to connect to the Pi using SSH over IPv6
285285
"""
286-
return f"ssh root@{self.ipv6_ssh_hostname}"
287-
288-
@property
289-
def ipv6_ssh_command_numeric(self) -> str:
290-
"""
291-
The SSH command required to connect to the Pi using SSH over IPv6 (using the IPv6 address
292-
rather than hostname)
293-
"""
294-
return f"ssh root@[{self.ipv6_address.compressed}]"
286+
return self.get_ipv6_ssh_command()
295287

296288
@property
297289
def ipv4_ssh_config(self) -> str:
298290
"""
299-
A string containing the IPv4 SSH config for the Pi. The contents could be added to an SSH
300-
config file for easy access to the Pi.
291+
A string containing the default IPv4 SSH config for the Pi. The contents could be added to
292+
an SSH config file for easy access to the Pi.
301293
"""
302-
return f"""Host {self.name}
303-
user root
304-
port {self.ipv4_ssh_port}
305-
hostname {self.ipv4_ssh_hostname}
306-
""".strip()
294+
return self.get_ipv4_ssh_config()
307295

308296
@property
309297
def ipv6_ssh_config(self) -> str:
310298
"""
311-
A string containing the IPv6 SSH config for the Pi. The contents could be added to an SSH
312-
config file for easy access to the Pi.
299+
A string containing the default IPv6 SSH config for the Pi. The contents could be added to
300+
an SSH config file for easy access to the Pi.
313301
"""
314-
return f"""Host {self.name}
315-
user root
316-
hostname {self.ipv6_ssh_hostname}
317-
""".strip()
318-
319-
@property
320-
def ipv6_ssh_config_numeric(self) -> str:
321-
"""
322-
A string containing the IPv6 SSH config for the Pi. The contents could be added to an SSH
323-
config file for easy access to the Pi.
324-
"""
325-
return f"""Host {self.name}
326-
user root
327-
hostname {self.ipv6_address.compressed}
328-
""".strip()
302+
return self.get_ipv6_ssh_config()
329303

330304
@property
331305
def url(self) -> str:
@@ -523,6 +497,48 @@ def cancel(self):
523497

524498
self._cancelled = True
525499

500+
def get_ipv4_ssh_command(self, user: str = "root") -> str:
501+
"""
502+
Construct an SSH command required to connect to the Pi using SSH over IPv4
503+
"""
504+
return f"ssh -p {self.ipv4_ssh_port} {user}@{self.ipv4_ssh_hostname}"
505+
506+
def get_ipv6_ssh_command(self, *, user: str = "root", numeric: bool = False) -> str:
507+
"""
508+
Construct an SSH command required to connect to the Pi using SSH over IPv6
509+
"""
510+
if numeric:
511+
return f"ssh {user}@[{self.ipv6_address.compressed}]"
512+
else:
513+
return f"ssh {user}@{self.ipv6_ssh_hostname}"
514+
515+
def get_ipv4_ssh_config(self, user: str = "root") -> str:
516+
"""
517+
Construct a string containing the IPv4 SSH config for the Pi. The contents could be added to
518+
an SSH config file for easy access to the Pi.
519+
"""
520+
return f"""Host {self.name}
521+
user {user}
522+
port {self.ipv4_ssh_port}
523+
hostname {self.ipv4_ssh_hostname}
524+
""".strip()
525+
526+
def get_ipv6_ssh_config(
527+
self,
528+
*,
529+
user: str = "root",
530+
numeric: bool = False,
531+
) -> str:
532+
"""
533+
Construct a string containing the SSH config for the Pi. The contents could be added to an
534+
SSH config file for easy access to the Pi.
535+
"""
536+
hostname = self.ipv6_address.compressed if numeric else self.ipv6_ssh_hostname
537+
return f"""Host {self.name}
538+
user {user}
539+
hostname {hostname}
540+
""".strip()
541+
526542
def add_ssh_keys(self, ssh_keys: SSHKeySources) -> set[str]:
527543
"""
528544
Add SSH keys to the Pi from the specified sources.

hostedpi/picloud.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,18 +96,34 @@ def pis(self) -> dict[str, Pi]:
9696
@property
9797
def ipv4_ssh_config(self) -> str:
9898
"""
99-
A string containing the IPv4 SSH config for all Pis within the account. The contents could
100-
be added to an SSH config file for easy access to the Pis in the account.
99+
A string containing the default IPv4 SSH config for all Pis within the account. The contents
100+
could be added to an SSH config file for easy access to the Pis in the account.
101101
"""
102-
return "\n".join(pi.ipv4_ssh_config for pi in self.pis.values())
102+
return self.get_ipv4_ssh_config()
103103

104104
@property
105105
def ipv6_ssh_config(self) -> str:
106106
"""
107-
A string containing the IPv6 SSH config for all Pis within the account. The contents could
108-
be added to an SSH config file for easy access to the Pis in the account.
107+
A string containing the default IPv6 SSH config for all Pis within the account. The contents
108+
could be added to an SSH config file for easy access to the Pis in the account.
109109
"""
110-
return "\n".join(pi.ipv6_ssh_config for pi in self.pis.values())
110+
return self.get_ipv6_ssh_config()
111+
112+
def get_ipv4_ssh_config(self, user: str = "root") -> str:
113+
"""
114+
Construct a string containing the IPv4 SSH config for all Pis within the account. The
115+
contents could be added to an SSH config file for easy access to the Pis in the account.
116+
"""
117+
return "\n".join(pi.get_ipv4_ssh_config(user=user) for pi in self.pis.values())
118+
119+
def get_ipv6_ssh_config(self, user: str = "root", numeric: bool = False) -> str:
120+
"""
121+
Construct a string containing the IPv6 SSH config for all Pis within the account. The
122+
contents could be added to an SSH config file for easy access to the Pis in the account.
123+
"""
124+
return "\n".join(
125+
pi.get_ipv6_ssh_config(user=user, numeric=numeric) for pi in self.pis.values()
126+
)
111127

112128
def create_pi(
113129
self,

tests/test_pi.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,3 +707,56 @@ def test_get_pi_info_error_500(pi_name, pi_info_basic, auth, error_500):
707707
with pytest.raises(HostedPiServerError):
708708
pi.info
709709
assert auth._api_session.get.call_count == 1
710+
711+
712+
def test_pi_get_ipv4_ssh_command(pi_name, pi_info_basic, auth, pi_info_response):
713+
pi = Pi(name=pi_name, info=pi_info_basic, auth=auth)
714+
auth._api_session.get.return_value = pi_info_response
715+
assert pi.get_ipv4_ssh_command() == "ssh -p 5100 [email protected]"
716+
assert pi.get_ipv4_ssh_command(user="pi") == "ssh -p 5100 [email protected]"
717+
718+
719+
def test_pi_get_ipv6_ssh_command(pi_name, pi_info_basic, auth, pi_info_response):
720+
pi = Pi(name=pi_name, info=pi_info_basic, auth=auth)
721+
auth._api_session.get.return_value = pi_info_response
722+
assert pi.get_ipv6_ssh_command() == "ssh [email protected]"
723+
assert pi.get_ipv6_ssh_command(user="pi") == "ssh [email protected]"
724+
assert pi.get_ipv6_ssh_command(numeric=True) == "ssh root@[2a00:1098:8:64::1]"
725+
assert pi.get_ipv6_ssh_command(user="pi", numeric=True) == "ssh pi@[2a00:1098:8:64::1]"
726+
727+
728+
def test_pi_get_ipv4_ssh_config(pi_name, pi_info_basic, auth, pi_info_response):
729+
pi = Pi(name=pi_name, info=pi_info_basic, auth=auth)
730+
auth._api_session.get.return_value = pi_info_response
731+
732+
config = pi.get_ipv4_ssh_config()
733+
assert "Host test-pi" in config
734+
assert "user root" in config
735+
assert "port 5100" in config
736+
assert "hostname ssh.test-pi.hostedpi.com" in config
737+
738+
config = pi.get_ipv4_ssh_config(user="pi")
739+
assert "Host test-pi" in config
740+
assert "user pi" in config
741+
assert "port 5100" in config
742+
assert "hostname ssh.test-pi.hostedpi.com" in config
743+
744+
745+
def test_pi_get_ipv6_ssh_config(pi_name, pi_info_basic, auth, pi_info_response):
746+
pi = Pi(name=pi_name, info=pi_info_basic, auth=auth)
747+
auth._api_session.get.return_value = pi_info_response
748+
749+
config = pi.get_ipv6_ssh_config()
750+
assert "Host test-pi" in config
751+
assert "user root" in config
752+
assert "hostname test-pi.hostedpi.com" in config
753+
754+
config = pi.get_ipv6_ssh_config(user="pi")
755+
assert "Host test-pi" in config
756+
assert "user pi" in config
757+
assert "hostname test-pi.hostedpi.com" in config
758+
759+
config = pi.get_ipv6_ssh_config(numeric=True)
760+
assert "Host test-pi" in config
761+
assert "user root" in config
762+
assert "hostname 2a00:1098:8:64::1" in config

0 commit comments

Comments
 (0)