github2gerrit 0.1.5__py3-none-any.whl → 0.1.7__py3-none-any.whl

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.
@@ -16,6 +16,8 @@ import os
16
16
  import socket
17
17
  from pathlib import Path
18
18
 
19
+ from .external_api import ApiType
20
+ from .external_api import external_api_call
19
21
  from .gitutils import CommandError
20
22
  from .gitutils import run_cmd
21
23
 
@@ -28,31 +30,17 @@ class SSHDiscoveryError(Exception):
28
30
 
29
31
 
30
32
  # Error message constants to comply with TRY003
31
- _MSG_HOST_UNREACHABLE = (
32
- "Host {hostname}:{port} is not reachable. "
33
- "Check network connectivity and server availability."
34
- )
33
+ _MSG_HOST_UNREACHABLE = "Host {hostname}:{port} is not reachable. Check network connectivity and server availability."
35
34
  _MSG_NO_KEYS_FOUND = (
36
- "No SSH host keys found for {hostname}:{port}. "
37
- "The server may not be running SSH or may be blocking connections."
35
+ "No SSH host keys found for {hostname}:{port}. The server may not be running SSH or may be blocking connections."
38
36
  )
39
37
  _MSG_NO_VALID_KEYS = (
40
- "No valid SSH host keys found for {hostname}:{port}. "
41
- "The ssh-keyscan output was empty or malformed."
42
- )
43
- _MSG_CONNECTION_FAILED = (
44
- "Failed to connect to {hostname}:{port} for SSH key discovery. "
45
- "Error: {error}"
46
- )
47
- _MSG_KEYSCAN_FAILED = (
48
- "ssh-keyscan failed with return code {returncode}: {error}"
49
- )
50
- _MSG_UNEXPECTED_ERROR = (
51
- "Unexpected error during SSH key discovery for {hostname}:{port}: {error}"
52
- )
53
- _MSG_SAVE_FAILED = (
54
- "Failed to save host keys to configuration file {config_file}: {error}"
38
+ "No valid SSH host keys found for {hostname}:{port}. The ssh-keyscan output was empty or malformed."
55
39
  )
40
+ _MSG_CONNECTION_FAILED = "Failed to connect to {hostname}:{port} for SSH key discovery. Error: {error}"
41
+ _MSG_KEYSCAN_FAILED = "ssh-keyscan failed with return code {returncode}: {error}"
42
+ _MSG_UNEXPECTED_ERROR = "Unexpected error during SSH key discovery for {hostname}:{port}: {error}"
43
+ _MSG_SAVE_FAILED = "Failed to save host keys to configuration file {config_file}: {error}"
56
44
 
57
45
 
58
46
  def is_host_reachable(hostname: str, port: int, timeout: int = 5) -> bool:
@@ -64,9 +52,8 @@ def is_host_reachable(hostname: str, port: int, timeout: int = 5) -> bool:
64
52
  return False
65
53
 
66
54
 
67
- def fetch_ssh_host_keys(
68
- hostname: str, port: int = 22, timeout: int = 10
69
- ) -> str:
55
+ @external_api_call(ApiType.SSH, "fetch_ssh_host_keys")
56
+ def fetch_ssh_host_keys(hostname: str, port: int = 22, timeout: int = 10) -> str:
70
57
  """
71
58
  Fetch SSH host keys for a given hostname and port using ssh-keyscan.
72
59
 
@@ -85,9 +72,7 @@ def fetch_ssh_host_keys(
85
72
 
86
73
  # First check if the host is reachable
87
74
  if not is_host_reachable(hostname, port, timeout=5):
88
- raise SSHDiscoveryError(
89
- _MSG_HOST_UNREACHABLE.format(hostname=hostname, port=port)
90
- )
75
+ raise SSHDiscoveryError(_MSG_HOST_UNREACHABLE.format(hostname=hostname, port=port))
91
76
 
92
77
  try:
93
78
  # Use ssh-keyscan to fetch all available key types
@@ -142,23 +127,13 @@ def fetch_ssh_host_keys(
142
127
  # ssh-keyscan returns 1 when it can't connect
143
128
  error_msg = exc.stderr or exc.stdout or "Connection failed"
144
129
  raise SSHDiscoveryError(
145
- _MSG_CONNECTION_FAILED.format(
146
- hostname=hostname, port=port, error=error_msg
147
- )
130
+ _MSG_CONNECTION_FAILED.format(hostname=hostname, port=port, error=error_msg)
148
131
  ) from exc
149
132
  else:
150
133
  error_msg = exc.stderr or exc.stdout or "Unknown error"
151
- raise SSHDiscoveryError(
152
- _MSG_KEYSCAN_FAILED.format(
153
- returncode=exc.returncode, error=error_msg
154
- )
155
- ) from exc
134
+ raise SSHDiscoveryError(_MSG_KEYSCAN_FAILED.format(returncode=exc.returncode, error=error_msg)) from exc
156
135
  except Exception as exc:
157
- raise SSHDiscoveryError(
158
- _MSG_UNEXPECTED_ERROR.format(
159
- hostname=hostname, port=port, error=exc
160
- )
161
- ) from exc
136
+ raise SSHDiscoveryError(_MSG_UNEXPECTED_ERROR.format(hostname=hostname, port=port, error=exc)) from exc
162
137
  else:
163
138
  return discovered_keys
164
139
 
@@ -196,9 +171,8 @@ def extract_gerrit_info_from_gitreview(content: str) -> tuple[str, int] | None:
196
171
  return (hostname, port) if hostname else None
197
172
 
198
173
 
199
- def discover_and_save_host_keys(
200
- hostname: str, port: int, organization: str, config_path: str | None = None
201
- ) -> str:
174
+ @external_api_call(ApiType.SSH, "discover_and_save_host_keys")
175
+ def discover_and_save_host_keys(hostname: str, port: int, organization: str, config_path: str | None = None) -> str:
202
176
  """
203
177
  Discover SSH host keys and save them to the organization's configuration.
204
178
 
@@ -224,9 +198,7 @@ def discover_and_save_host_keys(
224
198
  return host_keys
225
199
 
226
200
 
227
- def save_host_keys_to_config(
228
- host_keys: str, organization: str, config_path: str | None = None
229
- ) -> None:
201
+ def save_host_keys_to_config(host_keys: str, organization: str, config_path: str | None = None) -> None:
230
202
  """
231
203
  Save SSH host keys to the organization's configuration file.
232
204
 
@@ -242,9 +214,7 @@ def save_host_keys_to_config(
242
214
  from .config import DEFAULT_CONFIG_PATH
243
215
 
244
216
  if config_path is None:
245
- config_path = (
246
- os.getenv("G2G_CONFIG_PATH", "").strip() or DEFAULT_CONFIG_PATH
247
- )
217
+ config_path = os.getenv("G2G_CONFIG_PATH", "").strip() or DEFAULT_CONFIG_PATH
248
218
 
249
219
  config_file = Path(config_path).expanduser()
250
220
 
@@ -313,9 +283,7 @@ def save_host_keys_to_config(
313
283
 
314
284
  # Insert the GERRIT_KNOWN_HOSTS entry
315
285
  escaped_keys = host_keys.replace("\n", "\\n")
316
- new_lines.insert(
317
- section_end, f'GERRIT_KNOWN_HOSTS = "{escaped_keys}"'
318
- )
286
+ new_lines.insert(section_end, f'GERRIT_KNOWN_HOSTS = "{escaped_keys}"')
319
287
 
320
288
  # Write the updated configuration
321
289
  config_file.write_text("\n".join(new_lines), encoding="utf-8")
@@ -327,9 +295,7 @@ def save_host_keys_to_config(
327
295
  )
328
296
 
329
297
  except Exception as exc:
330
- raise SSHDiscoveryError(
331
- _MSG_SAVE_FAILED.format(config_file=config_file, error=exc)
332
- ) from exc
298
+ raise SSHDiscoveryError(_MSG_SAVE_FAILED.format(config_file=config_file, error=exc)) from exc
333
299
 
334
300
 
335
301
  def auto_discover_gerrit_host_keys(
@@ -360,21 +326,14 @@ def auto_discover_gerrit_host_keys(
360
326
  gerrit_port = 29418
361
327
 
362
328
  if organization is None:
363
- organization = (
364
- os.getenv("ORGANIZATION")
365
- or os.getenv("GITHUB_REPOSITORY_OWNER")
366
- or ""
367
- ).strip()
329
+ organization = (os.getenv("ORGANIZATION") or os.getenv("GITHUB_REPOSITORY_OWNER") or "").strip()
368
330
 
369
331
  if not gerrit_hostname:
370
332
  log.debug("No Gerrit hostname provided for auto-discovery")
371
333
  return None
372
334
 
373
335
  if not organization:
374
- log.warning(
375
- "No organization specified for SSH host key auto-discovery. "
376
- "Cannot save to configuration file."
377
- )
336
+ log.warning("No organization specified for SSH host key auto-discovery. Cannot save to configuration file.")
378
337
  save_to_config = False
379
338
 
380
339
  log.info(
@@ -404,9 +363,7 @@ def auto_discover_gerrit_host_keys(
404
363
  log.warning("SSH host key auto-discovery failed: %s", exc)
405
364
  return None
406
365
  except Exception as exc:
407
- log.warning(
408
- "Unexpected error during SSH host key auto-discovery: %s", exc
409
- )
366
+ log.warning("Unexpected error during SSH host key auto-discovery: %s", exc)
410
367
  return None
411
368
  else:
412
369
  return host_keys
github2gerrit/utils.py ADDED
@@ -0,0 +1,113 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # SPDX-FileCopyrightText: 2025 The Linux Foundation
3
+
4
+ """Common utilities used across multiple modules.
5
+
6
+ This module consolidates helper functions that were previously duplicated
7
+ across cli.py, core.py, and gitutils.py to reduce maintenance overhead
8
+ and ensure consistent behavior.
9
+ """
10
+
11
+ import logging
12
+ import os
13
+ from typing import Any
14
+
15
+
16
+ def env_bool(name: str, default: bool = False) -> bool:
17
+ """Parse boolean environment variable correctly handling string values.
18
+
19
+ Args:
20
+ name: Environment variable name
21
+ default: Default value if variable is not set
22
+
23
+ Returns:
24
+ Boolean value parsed from environment variable
25
+ """
26
+ val = os.getenv(name)
27
+ if val is None:
28
+ return default
29
+ s = val.strip().lower()
30
+ return s in ("1", "true", "yes", "on")
31
+
32
+
33
+ def env_str(name: str, default: str = "") -> str:
34
+ """Get string environment variable with default fallback.
35
+
36
+ Args:
37
+ name: Environment variable name
38
+ default: Default value if variable is not set
39
+
40
+ Returns:
41
+ String value from environment variable or default
42
+ """
43
+ val = os.getenv(name)
44
+ return val if val is not None else default
45
+
46
+
47
+ def parse_bool_env(value: str | None) -> bool:
48
+ """Parse boolean environment variable correctly handling string values.
49
+
50
+ Args:
51
+ value: String value to parse as boolean
52
+
53
+ Returns:
54
+ Boolean value parsed from string
55
+ """
56
+ if value is None:
57
+ return False
58
+ s = value.strip().lower()
59
+ return s in ("1", "true", "yes", "on")
60
+
61
+
62
+ def is_verbose_mode() -> bool:
63
+ """Check if verbose mode is enabled via environment variable.
64
+
65
+ Returns:
66
+ True if G2G_VERBOSE environment variable is set to a truthy value
67
+ """
68
+ return os.getenv("G2G_VERBOSE", "").lower() in ("true", "1", "yes")
69
+
70
+
71
+ def log_exception_conditionally(logger: logging.Logger, message: str, *args: Any) -> None:
72
+ """Log exception with traceback only if verbose mode is enabled.
73
+
74
+ Args:
75
+ logger: Logger instance to use
76
+ message: Log message format string
77
+ *args: Arguments for message formatting
78
+ """
79
+ if is_verbose_mode():
80
+ logger.exception(message, *args)
81
+ else:
82
+ logger.error(message, *args)
83
+
84
+
85
+ def append_github_output(outputs: dict[str, str]) -> None:
86
+ """Append key-value pairs to GitHub Actions output file.
87
+
88
+ This function writes outputs to the GITHUB_OUTPUT file for use by
89
+ subsequent steps in a GitHub Actions workflow. It handles multiline
90
+ values using heredoc syntax when running in GitHub Actions.
91
+
92
+ Args:
93
+ outputs: Dictionary of key-value pairs to write to output
94
+ """
95
+ gh_out = os.getenv("GITHUB_OUTPUT")
96
+ if not gh_out:
97
+ return
98
+
99
+ try:
100
+ with open(gh_out, "a", encoding="utf-8") as fh:
101
+ for key, val in outputs.items():
102
+ if not val:
103
+ continue
104
+ if "\n" in val:
105
+ fh.write(f"{key}<<G2G\n")
106
+ fh.write(f"{val}\n")
107
+ fh.write("G2G\n")
108
+ else:
109
+ fh.write(f"{key}={val}\n")
110
+ except Exception as exc:
111
+ # Use a basic logger since we can't import from other modules
112
+ # without creating circular dependencies
113
+ logging.getLogger(__name__).debug("Failed to write GITHUB_OUTPUT: %s", exc)