hopgo 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
hopgo-0.1.0/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -0,0 +1 @@
1
+ 3.14
hopgo-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: hopgo
3
+ Version: 0.1.0
4
+ Summary: A Pythonic multi-hop SSH/ADB/Docker automation framework
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: fabric>=3.2
7
+ Requires-Dist: rich>=13.0
hopgo-0.1.0/README.md ADDED
File without changes
@@ -0,0 +1,40 @@
1
+ from .session import connect
2
+ from .shell import Shell, Result
3
+ from .hops import Hop, SSHHop, ADBHop, DockerHop
4
+ from .files import FileInfo
5
+ from .exceptions import (
6
+ HopError,
7
+ HopConnectionError,
8
+ HopSudoError,
9
+ HopSecurityError,
10
+ HopFileNotFoundError,
11
+ HopFileExistsError,
12
+ HopIsDirectoryError,
13
+ HopPermissionError,
14
+ HopTimeoutError,
15
+ HopFileSizeError,
16
+ )
17
+
18
+ MAX_READ_SIZE = 10 * 1024 * 1024
19
+
20
+ __all__ = [
21
+ "connect",
22
+ "Shell",
23
+ "Result",
24
+ "Hop",
25
+ "SSHHop",
26
+ "ADBHop",
27
+ "DockerHop",
28
+ "FileInfo",
29
+ "HopError",
30
+ "HopConnectionError",
31
+ "HopSudoError",
32
+ "HopSecurityError",
33
+ "HopFileNotFoundError",
34
+ "HopFileExistsError",
35
+ "HopIsDirectoryError",
36
+ "HopPermissionError",
37
+ "HopTimeoutError",
38
+ "HopFileSizeError",
39
+ "MAX_READ_SIZE",
40
+ ]
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env python3
2
+ """HopGo CLI - Multi-hop SSH/ADB/Docker automation framework."""
3
+
4
+
5
+ def main() -> None:
6
+ print("HopGo - A Pythonic multi-hop automation framework")
7
+ print("Use 'from hopgo import connect' in Python to get started.")
8
+
9
+
10
+ if __name__ == "__main__":
11
+ main()
@@ -0,0 +1,38 @@
1
+ class HopError(Exception):
2
+ pass
3
+
4
+
5
+ class HopConnectionError(HopError):
6
+ pass
7
+
8
+
9
+ class HopSudoError(HopError):
10
+ pass
11
+
12
+
13
+ class HopSecurityError(HopError):
14
+ pass
15
+
16
+
17
+ class HopFileNotFoundError(HopError):
18
+ pass
19
+
20
+
21
+ class HopFileExistsError(HopError):
22
+ pass
23
+
24
+
25
+ class HopIsDirectoryError(HopError):
26
+ pass
27
+
28
+
29
+ class HopPermissionError(HopError):
30
+ pass
31
+
32
+
33
+ class HopTimeoutError(HopError):
34
+ pass
35
+
36
+
37
+ class HopFileSizeError(HopError):
38
+ pass
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from datetime import datetime
5
+ from typing import Optional
6
+
7
+
8
+ @dataclass
9
+ class FileInfo:
10
+ name: str
11
+ path: str
12
+ size: int
13
+ is_dir: bool
14
+ is_symlink: bool
15
+ modified: Optional[datetime] = None
16
+ permissions: str = ""
@@ -0,0 +1,91 @@
1
+ from __future__ import annotations
2
+
3
+ import shlex
4
+ from abc import ABC, abstractmethod
5
+ from typing import Optional
6
+
7
+
8
+ class Hop(ABC):
9
+ target_os: str = "linux"
10
+
11
+ def __init__(self, sudo_password: Optional[str] = None) -> None:
12
+ self.sudo_password = sudo_password
13
+
14
+ @abstractmethod
15
+ def wrap(self, cmd: str) -> str:
16
+ ...
17
+
18
+
19
+ class SSHHop(Hop):
20
+ def __init__(
21
+ self,
22
+ host: str,
23
+ user: Optional[str] = None,
24
+ password: Optional[str] = None,
25
+ key_filename: Optional[str] = None,
26
+ port: int = 22,
27
+ sudo_password: Optional[str] = None,
28
+ ) -> None:
29
+ super().__init__(sudo_password=sudo_password)
30
+ self.host = host
31
+ self.user = user
32
+ self.password = password
33
+ self.key_filename = key_filename
34
+ self.port = port
35
+ self.target_os = "linux"
36
+
37
+ def wrap(self, cmd: str) -> str:
38
+ parts = ["ssh", "-o", "StrictHostKeyChecking=no"]
39
+ if self.port != 22:
40
+ parts += ["-p", str(self.port)]
41
+ if self.key_filename:
42
+ parts += ["-i", shlex.quote(self.key_filename)]
43
+ user_host = f"{self.user}@{self.host}" if self.user else self.host
44
+ parts.append(user_host)
45
+ parts.append(shlex.quote(cmd))
46
+ return " ".join(parts)
47
+
48
+
49
+ class ADBHop(Hop):
50
+ def __init__(
51
+ self,
52
+ serial: Optional[str] = None,
53
+ user: Optional[str] = None,
54
+ sudo_password: Optional[str] = None,
55
+ ) -> None:
56
+ super().__init__(sudo_password=sudo_password)
57
+ self.serial = serial
58
+ self.user = user
59
+ self.target_os = "linux"
60
+
61
+ def wrap(self, cmd: str) -> str:
62
+ parts = ["adb"]
63
+ if self.serial:
64
+ parts += ["-s", shlex.quote(self.serial)]
65
+ parts.append("shell")
66
+ if self.user:
67
+ parts += ["run-as", shlex.quote(self.user)]
68
+ parts.append(shlex.quote(cmd))
69
+ return " ".join(parts)
70
+
71
+
72
+ class DockerHop(Hop):
73
+ def __init__(
74
+ self,
75
+ container: str,
76
+ user: Optional[str] = None,
77
+ host_os: str = "linux",
78
+ sudo_password: Optional[str] = None,
79
+ ) -> None:
80
+ super().__init__(sudo_password=sudo_password)
81
+ self.container = container
82
+ self.user = user
83
+ self.target_os = host_os
84
+
85
+ def wrap(self, cmd: str) -> str:
86
+ parts = ["docker", "exec", "-i"]
87
+ if self.user:
88
+ parts += ["-u", shlex.quote(self.user)]
89
+ parts.append(shlex.quote(self.container))
90
+ parts += ["sh", "-c", shlex.quote(cmd)]
91
+ return " ".join(parts)
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from typing import Optional, List
5
+
6
+ from .shell import Shell
7
+
8
+
9
+ class Session:
10
+ def __init__(self, _ssh_conn=None) -> None:
11
+ self._ssh_conn = _ssh_conn
12
+
13
+
14
+ def connect(
15
+ host: Optional[str] = None,
16
+ hops: Optional[List] = None,
17
+ **ssh_kwargs,
18
+ ) -> Shell:
19
+ ssh_conn = None
20
+ if host is not None:
21
+ from fabric import Connection
22
+ ssh_conn = Connection(host, **ssh_kwargs)
23
+ try:
24
+ ssh_conn.open()
25
+ except Exception as e:
26
+ from .exceptions import HopConnectionError
27
+ raise HopConnectionError(f"SSH connection to {host} failed: {e}")
28
+
29
+ session = Session(_ssh_conn=ssh_conn)
30
+ target_os = "windows" if sys.platform == "win32" else "linux"
31
+
32
+ return Shell(
33
+ _session=session,
34
+ _cwd="/",
35
+ _target_os=target_os,
36
+ )