gnetclisdk 1.0.100__tar.gz → 1.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.

Potentially problematic release.


This version of gnetclisdk might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gnetclisdk
3
- Version: 1.0.100
3
+ Version: 1.1.0
4
4
  Summary: Client for Gnetcli GRPC-server
5
5
  Home-page: https://github.com/annetutil/gnetcli
6
6
  Author: Alexander Balezin
@@ -16,6 +16,7 @@ Description-Content-Type: text/markdown
16
16
  Requires-Dist: protobuf>=4.24.4
17
17
  Requires-Dist: grpcio>=1.59.2
18
18
  Requires-Dist: googleapis-common-protos>=1.61.0
19
+ Requires-Dist: pyyaml
19
20
  Dynamic: author
20
21
  Dynamic: author-email
21
22
  Dynamic: classifier
@@ -14,7 +14,7 @@ from google.protobuf.message import Message
14
14
 
15
15
  from .proto import server_pb2, server_pb2_grpc
16
16
  from .auth import BasicClientAuthentication, ClientAuthentication, OAuthClientAuthentication
17
- from .exceptions import parse_grpc_error
17
+ from .exceptions import parse_grpc_error, DeviceConnectError
18
18
  from .interceptors import get_auth_client_interceptors
19
19
 
20
20
  _logger = logging.getLogger(__name__)
@@ -470,6 +470,8 @@ async def grpc_call_wrapper(stub: grpc.UnaryUnaryMultiCallable, request: Any) ->
470
470
  verbose=verbose,
471
471
  )
472
472
  last_exc.__cause__ = e
473
+ if gn_exc == DeviceConnectError:
474
+ continue
473
475
  raise last_exc from None
474
476
  else:
475
477
  last_exc = None
@@ -0,0 +1,54 @@
1
+ from dataclasses import dataclass, asdict, field
2
+ from datetime import timedelta
3
+
4
+ import yaml
5
+
6
+
7
+ @dataclass
8
+ class LogConfig:
9
+ level: str = "info"
10
+ json: bool = False
11
+
12
+
13
+ @dataclass
14
+ class AuthAppConfig:
15
+ login: str = ""
16
+ password: str = ""
17
+ private_key: str = "" # path to private key file
18
+ proxy_jump: str = ""
19
+ use_agent: bool = False
20
+ ssh_config: bool = False
21
+
22
+
23
+ @dataclass
24
+ class Config:
25
+ logging: LogConfig = field(default_factory=LogConfig)
26
+ port: str = "" # Listen address
27
+ http_port: str = "" # Http listen address
28
+ dev_auth: AuthAppConfig = field(default_factory=AuthAppConfig)
29
+ dev_conf: str = "" # Path to yaml with device types
30
+ tls: bool = False
31
+ cert_file: str = ""
32
+ key_file: str = ""
33
+ basic_auth: str = ""
34
+ disable_tcp: bool = False
35
+ unix_socket: str = "" # Unix socket pat
36
+ default_read_timeout: timedelta = timedelta(seconds=0)
37
+ default_cmd_timeout: timedelta = timedelta(seconds=0)
38
+
39
+
40
+ def dict_factory(data):
41
+ return {
42
+ key: (
43
+ f"{value.total_seconds()}s"
44
+ if isinstance(value, timedelta)
45
+ else value
46
+ )
47
+ for key, value in data
48
+ if value != "" # skip empty strings
49
+ }
50
+
51
+
52
+ def config_to_yaml(cfg: Config) -> str:
53
+ cfg_dict = asdict(cfg, dict_factory=dict_factory)
54
+ return yaml.safe_dump(cfg_dict, sort_keys=False)
@@ -33,7 +33,7 @@ class GnetcliException(Exception):
33
33
  super().__init__(self.message)
34
34
 
35
35
 
36
- class DeviceConnectError(GnetcliException):
36
+ class DeviceConnectionError(GnetcliException):
37
37
  """
38
38
  Problem with connection to a device.
39
39
  """
@@ -41,6 +41,14 @@ class DeviceConnectError(GnetcliException):
41
41
  pass
42
42
 
43
43
 
44
+ class DeviceConnectError(GnetcliException):
45
+ """
46
+ Problem with connecting to a device.
47
+ """
48
+
49
+ pass
50
+
51
+
44
52
  class UnknownDevice(GnetcliException):
45
53
  """
46
54
  Host is not found in inventory
@@ -49,7 +57,7 @@ class UnknownDevice(GnetcliException):
49
57
  pass
50
58
 
51
59
 
52
- class DeviceAuthError(DeviceConnectError):
60
+ class DeviceAuthError(DeviceConnectionError):
53
61
  """
54
62
  Unable to authenticate on a device.
55
63
  """
@@ -140,9 +148,12 @@ def parse_grpc_error(grpc_error: grpc.aio.AioRpcError) -> Tuple[Type[GnetcliExce
140
148
  return DeviceAuthError, verbose
141
149
  elif detail in {"connection_error", "busy_error"}:
142
150
  verbose = ""
143
- return DeviceConnectError, verbose
151
+ return DeviceConnectionError, verbose
144
152
  elif detail in {"exec_error", "generic_error"}:
145
153
  verbose = ""
146
154
  return ExecError, verbose
155
+ elif detail == "connect_error":
156
+ verbose = str(error_info)
157
+ return DeviceConnectError, verbose
147
158
 
148
159
  return GnetcliException, ""
@@ -0,0 +1,102 @@
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ from asyncio.subprocess import Process
5
+ from json import JSONDecodeError
6
+ from subprocess import DEVNULL, PIPE, TimeoutExpired
7
+
8
+ from gnetclisdk.config import Config, LogConfig, AuthAppConfig, config_to_yaml
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ DEFAULT_GNETCLI_SERVER_CONF = Config(
13
+ logging=LogConfig(level="debug", json=True),
14
+ dev_auth=AuthAppConfig(use_agent=True, ssh_config=True),
15
+ )
16
+
17
+
18
+ class GnetcliStarter:
19
+ def __init__(
20
+ self,
21
+ server_path: str,
22
+ server_conf: Config = DEFAULT_GNETCLI_SERVER_CONF,
23
+ start_timeout: int = 5,
24
+ stop_timeout: int = 10,
25
+ ):
26
+ self._server_path = server_path
27
+ self._server_conf = server_conf
28
+ self._running = False
29
+ self._proc: Process | None = None
30
+ self._start_timeout = start_timeout
31
+ self._stop_timeout = stop_timeout
32
+
33
+ async def _start(self) -> Process:
34
+ logger.debug("Starting Gnetcli server: %s", self._server_path)
35
+ proc = await asyncio.create_subprocess_exec(
36
+ self._server_path,
37
+ "--conf-file",
38
+ "-",
39
+ stdout=DEVNULL, # we do not read stdout
40
+ stderr=PIPE,
41
+ stdin=PIPE,
42
+ )
43
+ self._running = True
44
+ conf_yaml = config_to_yaml(self._server_conf)
45
+ proc.stdin.write(conf_yaml.encode())
46
+ await proc.stdin.drain()
47
+ proc.stdin.close()
48
+ return proc
49
+
50
+ async def _wait_url(self) -> str:
51
+ while proc := self._proc:
52
+ output = await proc.stderr.readline()
53
+ if output == "" and proc.returncode is not None:
54
+ break
55
+ if not output:
56
+ continue
57
+ logger.debug("gnetcli output: %s", output.strip())
58
+ try:
59
+ data = json.loads(output)
60
+ except JSONDecodeError:
61
+ logger.error("cannot decode data")
62
+ continue
63
+ if data.get("msg") == "init tcp socket":
64
+ logger.debug("Tcp socket found")
65
+ return data.get("address")
66
+ if data.get("msg") == "init unix socket":
67
+ logger.debug("Unix socket found")
68
+ return "unix:" + data.get("path")
69
+ if data.get("level") == "panic":
70
+ logger.error("gnetcli error %s", data)
71
+ return "" # stopped
72
+
73
+ async def __aenter__(self) -> str:
74
+ self._proc = await self._start()
75
+ try:
76
+ return await asyncio.wait_for(
77
+ self._wait_url(), timeout=self._start_timeout
78
+ )
79
+ except asyncio.TimeoutError:
80
+ logger.error("gnetcli _wait_url timeout, terminating")
81
+ await self._terminate()
82
+ raise RuntimeError("gnetcli start failed")
83
+
84
+ async def _terminate(self) -> None:
85
+ if (proc := self._proc) is None:
86
+ return
87
+ if proc.returncode is not None:
88
+ logger.error(
89
+ "gnetcli already terminated with code: %s", proc.returncode
90
+ )
91
+ return
92
+ proc.terminate()
93
+ try:
94
+ await asyncio.wait_for(proc.wait(), timeout=self._stop_timeout)
95
+ except TimeoutExpired:
96
+ logger.debug("gnetcli terminate failed, killing")
97
+ self._proc.kill()
98
+ self._proc = None
99
+
100
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
101
+ self.running = False
102
+ await self._terminate()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gnetclisdk
3
- Version: 1.0.100
3
+ Version: 1.1.0
4
4
  Summary: Client for Gnetcli GRPC-server
5
5
  Home-page: https://github.com/annetutil/gnetcli
6
6
  Author: Alexander Balezin
@@ -16,6 +16,7 @@ Description-Content-Type: text/markdown
16
16
  Requires-Dist: protobuf>=4.24.4
17
17
  Requires-Dist: grpcio>=1.59.2
18
18
  Requires-Dist: googleapis-common-protos>=1.61.0
19
+ Requires-Dist: pyyaml
19
20
  Dynamic: author
20
21
  Dynamic: author-email
21
22
  Dynamic: classifier
@@ -5,8 +5,10 @@ requirements.txt
5
5
  setup.py
6
6
  gnetclisdk/auth.py
7
7
  gnetclisdk/client.py
8
+ gnetclisdk/config.py
8
9
  gnetclisdk/exceptions.py
9
10
  gnetclisdk/interceptors.py
11
+ gnetclisdk/starter.py
10
12
  gnetclisdk.egg-info/PKG-INFO
11
13
  gnetclisdk.egg-info/SOURCES.txt
12
14
  gnetclisdk.egg-info/dependency_links.txt
@@ -1,3 +1,4 @@
1
1
  protobuf>=4.24.4
2
2
  grpcio>=1.59.2
3
3
  googleapis-common-protos>=1.61.0
4
+ pyyaml
@@ -1,3 +1,4 @@
1
1
  protobuf>=4.24.4
2
2
  grpcio>=1.59.2
3
3
  googleapis-common-protos>=1.61.0
4
+ pyyaml
File without changes
File without changes
File without changes
File without changes
File without changes