exosphere-cli 0.9.9__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.
- exosphere_cli-0.9.9/LICENSE +21 -0
- exosphere_cli-0.9.9/PKG-INFO +104 -0
- exosphere_cli-0.9.9/README.md +71 -0
- exosphere_cli-0.9.9/pyproject.toml +87 -0
- exosphere_cli-0.9.9/src/exosphere/__init__.py +14 -0
- exosphere_cli-0.9.9/src/exosphere/cli.py +129 -0
- exosphere_cli-0.9.9/src/exosphere/commands/__init__.py +0 -0
- exosphere_cli-0.9.9/src/exosphere/commands/config.py +163 -0
- exosphere_cli-0.9.9/src/exosphere/commands/host.py +317 -0
- exosphere_cli-0.9.9/src/exosphere/commands/inventory.py +518 -0
- exosphere_cli-0.9.9/src/exosphere/commands/ui.py +31 -0
- exosphere_cli-0.9.9/src/exosphere/compat/__init__.py +0 -0
- exosphere_cli-0.9.9/src/exosphere/compat/win32readline.py +134 -0
- exosphere_cli-0.9.9/src/exosphere/config.py +281 -0
- exosphere_cli-0.9.9/src/exosphere/context.py +4 -0
- exosphere_cli-0.9.9/src/exosphere/data.py +31 -0
- exosphere_cli-0.9.9/src/exosphere/database.py +33 -0
- exosphere_cli-0.9.9/src/exosphere/errors.py +27 -0
- exosphere_cli-0.9.9/src/exosphere/inventory.py +363 -0
- exosphere_cli-0.9.9/src/exosphere/main.py +172 -0
- exosphere_cli-0.9.9/src/exosphere/objects.py +361 -0
- exosphere_cli-0.9.9/src/exosphere/providers/__init__.py +11 -0
- exosphere_cli-0.9.9/src/exosphere/providers/api.py +64 -0
- exosphere_cli-0.9.9/src/exosphere/providers/debian.py +144 -0
- exosphere_cli-0.9.9/src/exosphere/providers/factory.py +29 -0
- exosphere_cli-0.9.9/src/exosphere/providers/freebsd.py +183 -0
- exosphere_cli-0.9.9/src/exosphere/providers/redhat.py +226 -0
- exosphere_cli-0.9.9/src/exosphere/setup/__init__.py +0 -0
- exosphere_cli-0.9.9/src/exosphere/setup/detect.py +236 -0
- exosphere_cli-0.9.9/src/exosphere/ui/__init__.py +0 -0
- exosphere_cli-0.9.9/src/exosphere/ui/app.py +55 -0
- exosphere_cli-0.9.9/src/exosphere/ui/dashboard.py +150 -0
- exosphere_cli-0.9.9/src/exosphere/ui/elements.py +183 -0
- exosphere_cli-0.9.9/src/exosphere/ui/inventory.py +391 -0
- exosphere_cli-0.9.9/src/exosphere/ui/logs.py +110 -0
- exosphere_cli-0.9.9/src/exosphere/ui/style.tcss +160 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Alexandre Gauthier
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: exosphere-cli
|
|
3
|
+
Version: 0.9.9
|
|
4
|
+
Summary: A simple centralized patch reporting tool for unix systems
|
|
5
|
+
Author: Alexandre Gauthier
|
|
6
|
+
Author-email: Alexandre Gauthier <alex@underwares.org>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: System Administrators
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Natural Language :: English
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: System :: Monitoring
|
|
17
|
+
Classifier: Topic :: System :: Systems Administration
|
|
18
|
+
Classifier: Topic :: Utilities
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Dist: click-shell[readline]>=2.1 ; sys_platform != 'win32'
|
|
21
|
+
Requires-Dist: click-shell>=2.1 ; sys_platform == 'win32'
|
|
22
|
+
Requires-Dist: fabric>=3.2.2
|
|
23
|
+
Requires-Dist: typer>=0.15.2
|
|
24
|
+
Requires-Dist: textual>=3.2.0
|
|
25
|
+
Requires-Dist: pyyaml>=6.0.2
|
|
26
|
+
Requires-Dist: textual-serve>=1.1.2
|
|
27
|
+
Requires-Dist: pyreadline3>=3.5.4 ; sys_platform == 'win32'
|
|
28
|
+
Requires-Python: >=3.13
|
|
29
|
+
Project-URL: homepage, https://github.com/mrdaemon/exosphere
|
|
30
|
+
Project-URL: issues, https://github.com/mrdaemon/exosphere/issues
|
|
31
|
+
Project-URL: repository, https://github.com/mrdaemon/exosphere
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# Exosphere
|
|
35
|
+
|
|
36
|
+
[](https://github.com/mrdaemon/exosphere/actions/workflows/exosphere-test.yml)
|
|
37
|
+
|
|
38
|
+
Exosphere is a command line and Text User Interface driven utility to query and
|
|
39
|
+
get ad-hoc reports from server infrastructure.
|
|
40
|
+
|
|
41
|
+
It can, so far, be used to:
|
|
42
|
+
|
|
43
|
+
- Get an overview of pending package and security updates
|
|
44
|
+
- Aggregate host online status and basic information
|
|
45
|
+
|
|
46
|
+
It is meant to be simple and foregoes any rich features that could otherwise be
|
|
47
|
+
serviced by other, better written tools.
|
|
48
|
+
|
|
49
|
+
It is meant to give a high level view of your infrastructure and how it's going,
|
|
50
|
+
as well as query information about state that is otherwise difficult
|
|
51
|
+
to aggregate or obtain ad-hoc.
|
|
52
|
+
|
|
53
|
+
Because reporting sucks, and things could be better.
|
|
54
|
+
|
|
55
|
+
> ## Pre-release Version
|
|
56
|
+
> This is a pre-release version of Exosphere
|
|
57
|
+
> It can be used but is under-documented, and still changing rapidly
|
|
58
|
+
> You may not want to use this this just yet, and wait for 1.0
|
|
59
|
+
|
|
60
|
+
## Development Quick Start
|
|
61
|
+
|
|
62
|
+
tl;dr, use [uv](https://docs.astral.sh/uv/getting-started/installation/)
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
uv sync
|
|
66
|
+
uv run exosphere
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Linting, formatting and testing can be done with poe tasks:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
uv run poe format
|
|
73
|
+
uv run poe check
|
|
74
|
+
uv run poe test
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
For more details, and available tasks, run:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
uv run poe --help
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## UI Development Quick Start
|
|
84
|
+
|
|
85
|
+
The UI is built with [Textual](https://textual.textualize.io/).
|
|
86
|
+
|
|
87
|
+
A quickstart of running the UI with live editing and reloading, with debug
|
|
88
|
+
console is as follows:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Ensure you have the dev dependencies
|
|
92
|
+
uv sync --dev
|
|
93
|
+
# In a separate terminal, run the console
|
|
94
|
+
uv run textual console
|
|
95
|
+
# In another terminal, run the UI
|
|
96
|
+
uv run textual run --dev -c exosphere ui start
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Congratulations, editing any of the tcss files in the `ui/` directory will
|
|
100
|
+
reflect changes immediately.
|
|
101
|
+
|
|
102
|
+
Make sure you run the exosphere ui with 'exosphere ui start' otherwise the
|
|
103
|
+
configuration will not be loaded correctly, and the inventory will not be
|
|
104
|
+
populated.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Exosphere
|
|
2
|
+
|
|
3
|
+
[](https://github.com/mrdaemon/exosphere/actions/workflows/exosphere-test.yml)
|
|
4
|
+
|
|
5
|
+
Exosphere is a command line and Text User Interface driven utility to query and
|
|
6
|
+
get ad-hoc reports from server infrastructure.
|
|
7
|
+
|
|
8
|
+
It can, so far, be used to:
|
|
9
|
+
|
|
10
|
+
- Get an overview of pending package and security updates
|
|
11
|
+
- Aggregate host online status and basic information
|
|
12
|
+
|
|
13
|
+
It is meant to be simple and foregoes any rich features that could otherwise be
|
|
14
|
+
serviced by other, better written tools.
|
|
15
|
+
|
|
16
|
+
It is meant to give a high level view of your infrastructure and how it's going,
|
|
17
|
+
as well as query information about state that is otherwise difficult
|
|
18
|
+
to aggregate or obtain ad-hoc.
|
|
19
|
+
|
|
20
|
+
Because reporting sucks, and things could be better.
|
|
21
|
+
|
|
22
|
+
> ## Pre-release Version
|
|
23
|
+
> This is a pre-release version of Exosphere
|
|
24
|
+
> It can be used but is under-documented, and still changing rapidly
|
|
25
|
+
> You may not want to use this this just yet, and wait for 1.0
|
|
26
|
+
|
|
27
|
+
## Development Quick Start
|
|
28
|
+
|
|
29
|
+
tl;dr, use [uv](https://docs.astral.sh/uv/getting-started/installation/)
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv sync
|
|
33
|
+
uv run exosphere
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Linting, formatting and testing can be done with poe tasks:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv run poe format
|
|
40
|
+
uv run poe check
|
|
41
|
+
uv run poe test
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
For more details, and available tasks, run:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uv run poe --help
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## UI Development Quick Start
|
|
51
|
+
|
|
52
|
+
The UI is built with [Textual](https://textual.textualize.io/).
|
|
53
|
+
|
|
54
|
+
A quickstart of running the UI with live editing and reloading, with debug
|
|
55
|
+
console is as follows:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Ensure you have the dev dependencies
|
|
59
|
+
uv sync --dev
|
|
60
|
+
# In a separate terminal, run the console
|
|
61
|
+
uv run textual console
|
|
62
|
+
# In another terminal, run the UI
|
|
63
|
+
uv run textual run --dev -c exosphere ui start
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Congratulations, editing any of the tcss files in the `ui/` directory will
|
|
67
|
+
reflect changes immediately.
|
|
68
|
+
|
|
69
|
+
Make sure you run the exosphere ui with 'exosphere ui start' otherwise the
|
|
70
|
+
configuration will not be loaded correctly, and the inventory will not be
|
|
71
|
+
populated.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "exosphere-cli"
|
|
3
|
+
version = "0.9.9"
|
|
4
|
+
description = "A simple centralized patch reporting tool for unix systems"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Alexandre Gauthier", email = "alex@underwares.org" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.13"
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Environment :: Console",
|
|
13
|
+
"Intended Audience :: System Administrators",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Natural Language :: English",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Topic :: System :: Monitoring",
|
|
19
|
+
"Topic :: System :: Systems Administration",
|
|
20
|
+
"Topic :: Utilities",
|
|
21
|
+
"Typing :: Typed",
|
|
22
|
+
]
|
|
23
|
+
license = "MIT"
|
|
24
|
+
license-files = [
|
|
25
|
+
"LICENSE"
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
dependencies = [
|
|
29
|
+
"click-shell[readline]>=2.1 ; sys_platform != 'win32'",
|
|
30
|
+
"click-shell>=2.1 ; sys_platform == 'win32'",
|
|
31
|
+
"fabric>=3.2.2",
|
|
32
|
+
"typer>=0.15.2",
|
|
33
|
+
"textual>=3.2.0",
|
|
34
|
+
"pyyaml>=6.0.2",
|
|
35
|
+
"textual-serve>=1.1.2",
|
|
36
|
+
"pyreadline3>=3.5.4 ; sys_platform == 'win32'",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[dependency-groups]
|
|
40
|
+
dev = [
|
|
41
|
+
"fabric[pytest]>=3.2.2",
|
|
42
|
+
"poethepoet>=0.33.1",
|
|
43
|
+
"pyright>=1.1.400",
|
|
44
|
+
"pytest>=8.3.5",
|
|
45
|
+
"pytest-cov>=6.1.1",
|
|
46
|
+
"pytest-json-ctrf>=0.3.5",
|
|
47
|
+
"pytest-mock>=3.14.0",
|
|
48
|
+
"renku-sphinx-theme>=0.5.0",
|
|
49
|
+
"ruff>=0.11.5",
|
|
50
|
+
"sphinx>=8.2.3",
|
|
51
|
+
"sphinx-autobuild>=2024.10.3",
|
|
52
|
+
"sphinx-tabs>=3.4.7",
|
|
53
|
+
"textual-dev>=1.7.0",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[project.urls]
|
|
57
|
+
homepage = "https://github.com/mrdaemon/exosphere"
|
|
58
|
+
repository = "https://github.com/mrdaemon/exosphere"
|
|
59
|
+
issues = "https://github.com/mrdaemon/exosphere/issues"
|
|
60
|
+
|
|
61
|
+
[project.scripts]
|
|
62
|
+
exosphere = "exosphere.main:main"
|
|
63
|
+
|
|
64
|
+
[build-system]
|
|
65
|
+
requires = ["uv_build>=0.7.19,<0.8.0"]
|
|
66
|
+
build-backend = "uv_build"
|
|
67
|
+
|
|
68
|
+
[tool.uv.build-backend]
|
|
69
|
+
module-name = "exosphere"
|
|
70
|
+
|
|
71
|
+
[tool.pytest.ini_options]
|
|
72
|
+
testpaths = ["tests"]
|
|
73
|
+
|
|
74
|
+
[tool.poe.tasks]
|
|
75
|
+
lint = "ruff check src tests"
|
|
76
|
+
isort = "ruff check --select I --fix src tests"
|
|
77
|
+
isort-check = "ruff check --select I src tests --diff"
|
|
78
|
+
typecheck = "pyright src tests"
|
|
79
|
+
ruff-format = "ruff format src tests"
|
|
80
|
+
ruff-format-check = "ruff format --check src tests --diff"
|
|
81
|
+
format = ['ruff-format', 'isort']
|
|
82
|
+
format-check = ['ruff-format-check', 'isort-check']
|
|
83
|
+
check = ['format-check', 'lint', 'typecheck']
|
|
84
|
+
test = "pytest -v --ctrf .tests_report.json"
|
|
85
|
+
coverage = "pytest --cov-report term-missing --cov-report html --cov=src tests/"
|
|
86
|
+
docs = "sphinx-build -b html docs/source docs/build/html"
|
|
87
|
+
livedocs = "sphinx-autobuild -b html docs/source docs/build/html --port 8000 --open-browser"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import importlib.metadata
|
|
2
|
+
|
|
3
|
+
from .config import Configuration
|
|
4
|
+
|
|
5
|
+
# Global Instances: configuration and GlobalState
|
|
6
|
+
# These are set at runtime and should be used as singletons
|
|
7
|
+
# to hold the global state and configuration.
|
|
8
|
+
|
|
9
|
+
app_config = Configuration() # Has default values out of the box
|
|
10
|
+
|
|
11
|
+
# Current software version, imported from pyproject metadata
|
|
12
|
+
__version__ = importlib.metadata.version("exosphere_cli")
|
|
13
|
+
|
|
14
|
+
__all__ = ["__version__", "app_config"]
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
# ------------------win32 readline monkeypatch---------------------
|
|
6
|
+
if sys.platform == "win32":
|
|
7
|
+
try:
|
|
8
|
+
# On windows, we use a wrapper module for pyreadline3 in order
|
|
9
|
+
# to provide readline compatibility.
|
|
10
|
+
from exosphere.compat import win32readline as readline
|
|
11
|
+
|
|
12
|
+
# This needs monkeypatched in order for click_shell to make use
|
|
13
|
+
# of it instead of its internal, broken, legacy pyreadline.
|
|
14
|
+
sys.modules["readline"] = readline
|
|
15
|
+
except ImportError:
|
|
16
|
+
sys.stderr.write(
|
|
17
|
+
"Warning: pyreadline3 not found. "
|
|
18
|
+
"Interactive shell may not enable all features.\n"
|
|
19
|
+
)
|
|
20
|
+
# -----------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
from click_shell import make_click_shell
|
|
23
|
+
from rich import print
|
|
24
|
+
from rich.panel import Panel
|
|
25
|
+
from typer import Argument, Context, Exit, Typer
|
|
26
|
+
|
|
27
|
+
from exosphere import __version__
|
|
28
|
+
from exosphere.commands import config, host, inventory, ui
|
|
29
|
+
|
|
30
|
+
banner = f"""[turquoise4]
|
|
31
|
+
▗▖
|
|
32
|
+
▐▌
|
|
33
|
+
▟█▙ ▝█ █▘ ▟█▙ ▗▟██▖▐▙█▙ ▐▙██▖ ▟█▙ █▟█▌ ▟█▙
|
|
34
|
+
▐▙▄▟▌ ▐█▌ ▐▛ ▜▌▐▙▄▖▘▐▛ ▜▌▐▛ ▐▌▐▙▄▟▌ █▘ ▐▙▄▟▌
|
|
35
|
+
▐▛▀▀▘ ▗█▖ ▐▌ ▐▌ ▀▀█▖▐▌ ▐▌▐▌ ▐▌▐▛▀▀▘ █ ▐▛▀▀▘
|
|
36
|
+
▝█▄▄▌ ▟▀▙ ▝█▄█▘▐▄▄▟▌▐█▄█▘▐▌ ▐▌▝█▄▄▌ █ ▝█▄▄▌
|
|
37
|
+
▝▀▀ ▝▀ ▀▘ ▝▀▘ ▀▀▀ ▐▌▀▘ ▝▘ ▝▘ ▝▀▀ ▀ ▝▀▀
|
|
38
|
+
▐▌ [green]v{__version__}[/green][/turquoise4]
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
app = Typer(
|
|
42
|
+
no_args_is_help=False,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Setup commands from modules
|
|
46
|
+
app.add_typer(inventory.app, name="inventory")
|
|
47
|
+
app.add_typer(host.app, name="host")
|
|
48
|
+
app.add_typer(ui.app, name="ui")
|
|
49
|
+
app.add_typer(config.app, name="config")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@app.command(hidden=True)
|
|
53
|
+
def help(ctx: Context, command: Annotated[str | None, Argument()] = None):
|
|
54
|
+
"""
|
|
55
|
+
Help for interactive REPL use
|
|
56
|
+
|
|
57
|
+
Provides help for the root REPL command when used interactively,
|
|
58
|
+
in a way that is friendler for that specific context.
|
|
59
|
+
If a command is specified, it will show help for that command.
|
|
60
|
+
|
|
61
|
+
This only applies when in the interactive REPL, commands (including
|
|
62
|
+
the root 'exosphere' program) will use the standard Typer help
|
|
63
|
+
system when invoked from the command line or non-interactively.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
msg = "\nUse '<command> --help' or 'help <command>' for help on a specific command."
|
|
67
|
+
|
|
68
|
+
# Show root help if no command is specified
|
|
69
|
+
if not command:
|
|
70
|
+
if ctx.parent and getattr(ctx.parent, "command", None):
|
|
71
|
+
subcommands = getattr(ctx.parent.command, "commands", {})
|
|
72
|
+
lines = []
|
|
73
|
+
for name, cmd in subcommands.items():
|
|
74
|
+
if cmd.hidden:
|
|
75
|
+
continue
|
|
76
|
+
lines.append(
|
|
77
|
+
f"[cyan]{name:<11}[/cyan] {cmd.help or 'No description available.'}"
|
|
78
|
+
)
|
|
79
|
+
content = "\n".join(lines)
|
|
80
|
+
panel = Panel.fit(
|
|
81
|
+
content,
|
|
82
|
+
title="Commands",
|
|
83
|
+
title_align="left",
|
|
84
|
+
)
|
|
85
|
+
print("\nAvailable modules during interactive use:\n")
|
|
86
|
+
print(panel)
|
|
87
|
+
print(msg)
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
# Show command help if one is specified
|
|
91
|
+
subcommand = None
|
|
92
|
+
if ctx.parent and getattr(ctx.parent, "command", None):
|
|
93
|
+
subcommands = getattr(ctx.parent.command, "commands", None)
|
|
94
|
+
subcommand = subcommands.get(command) if subcommands else None
|
|
95
|
+
if subcommand:
|
|
96
|
+
subcommand.get_help(ctx)
|
|
97
|
+
print(f"\nUse '{str(subcommand.name)} <command> --help' for more details.")
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
# Fall through for unknown commands
|
|
101
|
+
print(f"[red]Unkown command '{command}'[/red]")
|
|
102
|
+
print(msg)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@app.callback(invoke_without_command=True)
|
|
106
|
+
def cli(ctx: Context) -> None:
|
|
107
|
+
"""
|
|
108
|
+
Exosphere CLI
|
|
109
|
+
|
|
110
|
+
The main command-line interface for Exosphere.
|
|
111
|
+
It provides a REPL interface for interactive use as a prompt, but can
|
|
112
|
+
also be used to run commands directly from the command line.
|
|
113
|
+
|
|
114
|
+
Run without arguments to start the interactive mode.
|
|
115
|
+
"""
|
|
116
|
+
if ctx.invoked_subcommand is None:
|
|
117
|
+
logger = logging.getLogger(__name__)
|
|
118
|
+
logger.info("Starting Exosphere REPL interface")
|
|
119
|
+
|
|
120
|
+
# Print the banner
|
|
121
|
+
print(banner)
|
|
122
|
+
|
|
123
|
+
# Start interactive REPL
|
|
124
|
+
repl = make_click_shell(
|
|
125
|
+
ctx,
|
|
126
|
+
prompt="exosphere> ",
|
|
127
|
+
)
|
|
128
|
+
repl.cmdloop()
|
|
129
|
+
Exit(0)
|
|
File without changes
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Annotated
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.pretty import Pretty
|
|
7
|
+
from rich.text import Text
|
|
8
|
+
|
|
9
|
+
from exosphere import app_config, context
|
|
10
|
+
from exosphere.config import Configuration
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(
|
|
13
|
+
help="Runtime Configuration Commands",
|
|
14
|
+
no_args_is_help=True,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
console = Console()
|
|
18
|
+
err_console = Console(stderr=True)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@app.command()
|
|
22
|
+
def show(
|
|
23
|
+
option: Annotated[
|
|
24
|
+
str | None,
|
|
25
|
+
typer.Argument(help="Name of the option to show. All if not specified."),
|
|
26
|
+
] = None,
|
|
27
|
+
full: Annotated[
|
|
28
|
+
bool,
|
|
29
|
+
typer.Option(
|
|
30
|
+
"--full",
|
|
31
|
+
"-f",
|
|
32
|
+
help="Show full configuration structure, including inventory.",
|
|
33
|
+
),
|
|
34
|
+
] = False,
|
|
35
|
+
) -> None:
|
|
36
|
+
"""
|
|
37
|
+
Show the current configuration.
|
|
38
|
+
|
|
39
|
+
Displays the current configuration options, or the value of a specific option
|
|
40
|
+
if specified.
|
|
41
|
+
|
|
42
|
+
If `--full` is specified, it will show the entire configuration structure,
|
|
43
|
+
including the inventory, beyond just the "options" section.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
if full:
|
|
47
|
+
if option:
|
|
48
|
+
err_console.print(
|
|
49
|
+
"[yellow]Full configuration requested, ignoring option name.[/yellow]"
|
|
50
|
+
)
|
|
51
|
+
console.print(Pretty(app_config, expand_all=True, max_depth=None))
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
if option:
|
|
55
|
+
if option in app_config["options"]:
|
|
56
|
+
console.print(app_config["options"][option])
|
|
57
|
+
else:
|
|
58
|
+
err_console.print(
|
|
59
|
+
f"[red]Option '{option}' not found in configuration.[/red]"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
console.print(Pretty(app_config["options"], expand_all=True))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@app.command()
|
|
68
|
+
def source(
|
|
69
|
+
env: Annotated[
|
|
70
|
+
bool,
|
|
71
|
+
typer.Option(
|
|
72
|
+
help="Show environment variables that affect the configuration.",
|
|
73
|
+
),
|
|
74
|
+
] = True,
|
|
75
|
+
) -> None:
|
|
76
|
+
"""
|
|
77
|
+
Show the configuration source, where it was loaded from.
|
|
78
|
+
|
|
79
|
+
Displays the path of the configuration file loaded, if any, and
|
|
80
|
+
any environment variables that affect the configuration.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
if context.confpath:
|
|
84
|
+
console.print(f"{context.confpath}")
|
|
85
|
+
else:
|
|
86
|
+
err_console.print("No configuration loaded, using defaults.")
|
|
87
|
+
|
|
88
|
+
if not env:
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
env_lines: list[str] = []
|
|
92
|
+
|
|
93
|
+
prefix = "EXOSPHERE_OPTIONS_"
|
|
94
|
+
|
|
95
|
+
for key, value in os.environ.items():
|
|
96
|
+
if (
|
|
97
|
+
key.startswith(prefix)
|
|
98
|
+
and key.removeprefix(prefix).lower() in app_config["options"]
|
|
99
|
+
):
|
|
100
|
+
env_lines.append(f"{key}={value}")
|
|
101
|
+
|
|
102
|
+
if env_lines:
|
|
103
|
+
console.print()
|
|
104
|
+
console.print("Environment variable overrides:\n")
|
|
105
|
+
for line in env_lines:
|
|
106
|
+
console.print(f" {line}")
|
|
107
|
+
console.print()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@app.command()
|
|
111
|
+
def diff(
|
|
112
|
+
full: Annotated[
|
|
113
|
+
bool,
|
|
114
|
+
typer.Option(
|
|
115
|
+
"--full",
|
|
116
|
+
"-f",
|
|
117
|
+
help="Show full configuration diff, including unmodified options.",
|
|
118
|
+
),
|
|
119
|
+
] = False,
|
|
120
|
+
):
|
|
121
|
+
"""
|
|
122
|
+
Show the differences between the current configuration and the defaults.
|
|
123
|
+
|
|
124
|
+
Exosphere follows convention over configuration, so your configuration
|
|
125
|
+
file can exclusively contain the options you want to change.
|
|
126
|
+
|
|
127
|
+
This command allows you to see exactly what has been changed, optionally
|
|
128
|
+
in its context, using the `--full` option.
|
|
129
|
+
|
|
130
|
+
For a full config dump, use the `show` command instead.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
default_config = Configuration.DEFAULTS["options"]
|
|
134
|
+
current_config = app_config["options"]
|
|
135
|
+
|
|
136
|
+
for key in set(default_config) | set(current_config):
|
|
137
|
+
if default_config.get(key, None) != current_config.get(key, None):
|
|
138
|
+
break
|
|
139
|
+
else:
|
|
140
|
+
console.print("No differences found between current and default configuration.")
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
lines = []
|
|
144
|
+
for key in sorted(set(default_config) | set(current_config)):
|
|
145
|
+
default_value = default_config.get(key, None)
|
|
146
|
+
current_value = current_config.get(key, None)
|
|
147
|
+
|
|
148
|
+
line: Text | None
|
|
149
|
+
|
|
150
|
+
if default_value != current_value:
|
|
151
|
+
line = Text(f"{key!r}: {current_value!r},", style="bold green")
|
|
152
|
+
line.append(f" # default: {default_value!r}", style="yellow")
|
|
153
|
+
else:
|
|
154
|
+
line = Text(f"{key!r}: {current_value!r},", style="dim") if full else None
|
|
155
|
+
|
|
156
|
+
if line:
|
|
157
|
+
lines.append(line)
|
|
158
|
+
|
|
159
|
+
console.print("{")
|
|
160
|
+
for line in lines:
|
|
161
|
+
console.print(" ", end="")
|
|
162
|
+
console.print(line)
|
|
163
|
+
console.print("}")
|