exosphere-cli 2.4.1__tar.gz → 2.4.2__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.
Files changed (53) hide show
  1. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/PKG-INFO +1 -1
  2. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/pyproject.toml +4 -4
  3. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/providers/debian.py +21 -0
  4. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/providers/redhat.py +8 -2
  5. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/setup/detect.py +26 -29
  6. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/LICENSE +0 -0
  7. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/README.md +0 -0
  8. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/__init__.py +0 -0
  9. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/cli.py +0 -0
  10. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/commands/__init__.py +0 -0
  11. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/commands/config.py +0 -0
  12. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/commands/connections.py +0 -0
  13. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/commands/host.py +0 -0
  14. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/commands/inventory.py +0 -0
  15. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/commands/report.py +0 -0
  16. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/commands/sudo.py +0 -0
  17. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/commands/ui.py +0 -0
  18. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/commands/utils.py +0 -0
  19. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/commands/version.py +0 -0
  20. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/config.py +0 -0
  21. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/context.py +0 -0
  22. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/data.py +0 -0
  23. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/database.py +0 -0
  24. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/errors.py +0 -0
  25. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/fspaths.py +0 -0
  26. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/inventory.py +0 -0
  27. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/main.py +0 -0
  28. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/migrations.py +0 -0
  29. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/objects.py +0 -0
  30. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/pipelining.py +0 -0
  31. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/providers/__init__.py +0 -0
  32. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/providers/api.py +0 -0
  33. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/providers/factory.py +0 -0
  34. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/providers/freebsd.py +0 -0
  35. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/providers/openbsd.py +0 -0
  36. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/repl.py +0 -0
  37. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/reporting.py +0 -0
  38. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/schema/__init__.py +0 -0
  39. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/schema/host-report.schema.json +0 -0
  40. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/security.py +0 -0
  41. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/setup/__init__.py +0 -0
  42. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/templates/report.html.j2 +0 -0
  43. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/templates/report.md.j2 +0 -0
  44. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/templates/report.txt.j2 +0 -0
  45. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/ui/__init__.py +0 -0
  46. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/ui/app.py +0 -0
  47. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/ui/context.py +0 -0
  48. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/ui/dashboard.py +0 -0
  49. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/ui/elements.py +0 -0
  50. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/ui/inventory.py +0 -0
  51. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/ui/logs.py +0 -0
  52. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/ui/messages.py +0 -0
  53. {exosphere_cli-2.4.1 → exosphere_cli-2.4.2}/src/exosphere/ui/style.tcss +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: exosphere-cli
3
- Version: 2.4.1
3
+ Version: 2.4.2
4
4
  Summary: CLI/TUI driven patch reporting for remote Unix-like systems.
5
5
  Author: Alexandre Gauthier
6
6
  Author-email: Alexandre Gauthier <alex@underwares.org>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "exosphere-cli"
3
- version = "2.4.1"
3
+ version = "2.4.2"
4
4
  description = "CLI/TUI driven patch reporting for remote Unix-like systems."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -48,14 +48,14 @@ dev = [
48
48
  "pytest-cov>=6.1.1",
49
49
  "pytest-json-ctrf>=0.3.5",
50
50
  "pytest-mock>=3.14.1",
51
- "renku-sphinx-theme>=0.5.0",
51
+ "sphinx-rtd-theme>=3.0.2",
52
52
  "ruff>=0.15.0",
53
- "sphinx>=8.2.3,<9.0",
53
+ "sphinx>=8.2.3",
54
54
  "sphinx-autobuild>=2024.10.3",
55
55
  "sphinx-lint>=1.0.0",
56
56
  "sphinx-tabs>=3.4.7",
57
57
  "sphinxcontrib-spelling>=8.0.1",
58
- "sphinxcontrib-typer>=0.5.1",
58
+ "sphinxcontrib-typer>=0.7.2",
59
59
  "textual-dev>=1.7.0",
60
60
  ]
61
61
 
@@ -50,6 +50,10 @@ class Apt(PkgManager):
50
50
  )
51
51
  return False
52
52
 
53
+ # Log warnings from apt-get if there was any stderr output.
54
+ if update.stderr:
55
+ self._log_apt_warn(update.stderr)
56
+
53
57
  self.logger.debug("Apt repositories synchronized successfully")
54
58
 
55
59
  return True
@@ -79,6 +83,10 @@ class Apt(PkgManager):
79
83
  self.logger.debug("No updates available or no matches in output.")
80
84
  return updates
81
85
 
86
+ # Log warnings from apt-get if there was any stderr output.
87
+ if raw_query.stderr:
88
+ self._log_apt_warn(raw_query.stderr)
89
+
82
90
  for line in raw_query.stdout.splitlines():
83
91
  line = line.strip()
84
92
 
@@ -151,3 +159,16 @@ class Apt(PkgManager):
151
159
  source=repo_source,
152
160
  security=is_security,
153
161
  )
162
+
163
+ def _log_apt_warn(self, lines: str) -> None:
164
+ """
165
+ Log warnings from APT stderr output.
166
+ We ignore empty lines and lines that do not begin with "W:"
167
+
168
+ :param lines: Stderr output to log.
169
+ """
170
+ for line in lines.splitlines():
171
+ line = line.strip()
172
+ if not line or not line.startswith("W: "):
173
+ continue
174
+ self.logger.warning("APT: %s", line)
@@ -216,11 +216,14 @@ class Dnf(PkgManager):
216
216
  """
217
217
 
218
218
  # Lazy compile the line pattern on first use
219
+ # Repository source component is usually in the form of "reponame"
220
+ # but can be prefixed with "@" or in the case of missing metadata,
221
+ # be the string "<unknown>" or similar. We account for these here.
219
222
  if self.line_pattern is None:
220
223
  self.line_pattern = re.compile(
221
224
  r"^(?P<name>[a-z0-9][\w+.-]*\.\w+)\s+" # Package (name.arch)
222
225
  r"(?P<version>[\w.+~:-]+-[\w.+~]+)\s+" # RPM version-release
223
- r"(?P<source>@?[a-z0-9][\w.:+/-]*)$", # Repo source (optional @)
226
+ r"(?P<source>@?[a-z0-9][\w.:+/-]*|<[^>\s]+>)$", # Repo source (optional @ or <>
224
227
  re.ASCII | re.IGNORECASE,
225
228
  )
226
229
 
@@ -230,7 +233,10 @@ class Dnf(PkgManager):
230
233
  self.logger.debug("Skipping garbage line: %s", line)
231
234
  return None
232
235
 
233
- return (match["name"], match["version"], match["source"].removeprefix("@"))
236
+ # Cleanup source string before sending it up
237
+ source = match["source"].removeprefix("@").strip("<>")
238
+
239
+ return (match["name"], match["version"], source)
234
240
 
235
241
  def _get_current_version(
236
242
  self, cx: Connection, package_names: list[str]
@@ -30,6 +30,10 @@ def platform_detect(cx: Connection) -> HostInfo:
30
30
  Detect the platform of the remote system.
31
31
  Entry point for refreshing all platform details.
32
32
 
33
+ Linux detection in general depends on the presence of a well-formed
34
+ /etc/os-release file, which is a safe assumption for everything we
35
+ explicitly support that's not EOL'd.
36
+
33
37
  :param cx: Fabric Connection object
34
38
  :return: HostInfo object with platform details
35
39
  """
@@ -114,8 +118,6 @@ def flavor_detect(cx: Connection, platform_name: str) -> str:
114
118
 
115
119
  # Linux
116
120
  if platform_name == "linux":
117
- # We're just going to query /etc/os-release directly.
118
- # Using lsb_release would be better, but it's less available
119
121
  result_id = cx.run("grep ^ID= /etc/os-release", hide=True, warn=True)
120
122
  result_like_id = cx.run(
121
123
  "grep ^ID_LIKE= /etc/os-release",
@@ -125,7 +127,7 @@ def flavor_detect(cx: Connection, platform_name: str) -> str:
125
127
 
126
128
  if result_id.failed:
127
129
  raise DataRefreshError(
128
- "Failed to detect OS flavor via lsb identifier.",
130
+ "Failed to detect OS flavor via os-release identifier.",
129
131
  stderr=result_id.stderr,
130
132
  stdout=result_id.stdout,
131
133
  )
@@ -187,36 +189,31 @@ def version_detect(cx: Connection, flavor_name: str) -> str:
187
189
  if flavor_name.lower() not in SUPPORTED_FLAVORS:
188
190
  raise UnsupportedOSError(f"Unsupported OS flavor: {flavor_name}")
189
191
 
190
- # Debian/Ubuntu
191
- if flavor_name in ["ubuntu", "debian"]:
192
- result_version = cx.run("lsb_release -s -r", hide=True, warn=True)
193
-
194
- if result_version.failed:
195
- raise DataRefreshError(
196
- "Failed to detect OS version via lsb_release.",
197
- stderr=result_version.stderr,
198
- stdout=result_version.stdout,
192
+ # Linux flavors that rely on os-release metadata
193
+ # (which is all of them, at the time of writing)
194
+ if flavor_name in ["ubuntu", "debian", "rhel", "fedora"]:
195
+ result_version = None
196
+
197
+ # Some systems (debian sid, for instance) don't provide VERSION_ID,
198
+ # So we fall back to VERSION_CODENAME in these cases.
199
+ # If neither work, we just err on the side of failure.
200
+ for version_key in ["VERSION_ID", "VERSION_CODENAME"]:
201
+ result_version = cx.run(
202
+ f"grep ^{version_key}= /etc/os-release", hide=True, warn=True
199
203
  )
200
204
 
201
- return result_version.stdout.strip()
202
-
203
- # Redhat-likes
204
- if flavor_name in ["rhel", "fedora"]:
205
- result_version = cx.run(
206
- "grep ^VERSION_ID= /etc/os-release", hide=True, warn=True
207
- )
208
-
209
- if result_version.failed:
210
- raise DataRefreshError(
211
- "Failed to detect OS version via os-release VERSION_ID.",
212
- stderr=result_version.stderr,
213
- stdout=result_version.stdout,
214
- )
205
+ if not result_version.failed:
206
+ logger.debug("Found version using %s", version_key)
207
+ version_line = result_version.stdout.strip()
208
+ version_value = version_line.partition("=")[2].strip().strip("\"'")
215
209
 
216
- version_line = result_version.stdout.strip()
217
- version_value = version_line.partition("=")[2].strip().strip("\"'")
210
+ return version_value
218
211
 
219
- return version_value.lower()
212
+ raise DataRefreshError(
213
+ "Failed to detect OS version via os-release VERSION_ID or VERSION_CODENAME.",
214
+ stderr=result_version.stderr if result_version else "",
215
+ stdout=result_version.stdout if result_version else "",
216
+ )
220
217
 
221
218
  # FreeBSD
222
219
  if flavor_name == "freebsd":
File without changes
File without changes