solrctl 0.5.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.
solrctl-0.5.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Thomas Baer, Sächsische Landesbibliothek - Staats- und Universitätsbibliothek Dresden (SLUB)
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.
solrctl-0.5.0/PKG-INFO ADDED
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: solrctl
3
+ Version: 0.5.0
4
+ Summary: CLI-Tool zur Verwaltung von Apache Solr 9.x – Cores tauschen, Status abfragen, Konfiguration einsehen
5
+ Author-email: Thomas Baer <thomas.baer@slub-dresden.de>
6
+ License-Expression: MIT
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Environment :: Console
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Intended Audience :: System Administrators
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: System :: Systems Administration
16
+ Requires-Python: >=3.11
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: httpx>=0.27
20
+ Requires-Dist: pydantic>=2.7
21
+ Requires-Dist: click>=8.1
22
+ Requires-Dist: loguru>=0.7
23
+ Provides-Extra: prefect
24
+ Requires-Dist: prefect==3.6.19; extra == "prefect"
25
+ Requires-Dist: python-dotenv>=1.0; extra == "prefect"
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8.2; extra == "dev"
28
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
29
+ Requires-Dist: pytest-env>=1.1; extra == "dev"
30
+ Requires-Dist: respx>=0.21; extra == "dev"
31
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
32
+ Requires-Dist: pysolr>=3.10; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # solrctl
36
+
37
+ CLI-Tool zur Verwaltung von Apache Solr 9.x – ohne Downtime Cores tauschen, Status abfragen, Konfiguration einsehen.
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ uv tool install solrctl
43
+ ```
44
+
45
+ Oder aus dem Checkout:
46
+
47
+ ```bash
48
+ uv pip install -e .
49
+ ```
50
+
51
+ ## CLI-Befehle
52
+
53
+ ```bash
54
+ # Solr erreichbar?
55
+ solrctl ping
56
+
57
+ # Cores anzeigen
58
+ solrctl core list
59
+
60
+ # Core-Infos (Pfade, Dokumentanzahl, Größe)
61
+ solrctl core info <name>
62
+
63
+ # solrconfig.xml abrufen
64
+ solrctl core config <name>
65
+
66
+ # Schema als XML abrufen
67
+ solrctl core schema <name>
68
+
69
+ # Core-Swap (unterbrechungsfrei)
70
+ solrctl core swap --source <neuer_core> --target <aktiver_core>
71
+
72
+ # Core löschen
73
+ solrctl core delete <name> [--delete-index]
74
+
75
+ # Alias-Operationen (SolrCloud)
76
+ solrctl alias list
77
+ solrctl alias swap --alias <name> --collection <ziel>
78
+ solrctl alias delete <name>
79
+ ```
80
+
81
+ Alle Befehle akzeptieren globale Optionen:
82
+
83
+ ```bash
84
+ --solr-url Basis-URL der Solr-Instanz (Default: http://localhost:8983, Env: SOLR_URL)
85
+ --user Basic-Auth Benutzername (Env: SOLR_USER)
86
+ --password Basic-Auth Passwort (Env: SOLR_PASSWORD)
87
+ ```
88
+
89
+ ## Voraussetzungen
90
+
91
+ - Python 3.11+
92
+ - Zugriff auf eine Solr 9.x Instanz (Standalone für Core-Befehle, SolrCloud für Alias-Befehle)
93
+
94
+ ## Lokale Entwicklung
95
+
96
+ ```bash
97
+ # Abhängigkeiten inkl. Dev- und Prefect-Extras installieren
98
+ uv sync --all-extras
99
+
100
+ # Tests ausführen
101
+ uv run pytest
102
+
103
+ # CLI lokal ausführen
104
+ uv run solrctl --help
105
+ ```
106
+
107
+ ### Docker-basiertes Test-Setup
108
+
109
+ ```bash
110
+ docker compose up -d
111
+ solrctl --solr-url http://localhost:8983 ping
112
+ ```
113
+
114
+ ## Prefect-Integration
115
+
116
+ solrctl liefert optionale Prefect-Tasks und -Flows für automatisierte Pipelines. Die Prefect-Abhängigkeiten werden nicht standardmäßig installiert:
117
+
118
+ ```bash
119
+ uv pip install -e ".[prefect]"
120
+ ```
121
+
122
+ Verfügbare Tasks und Flows:
123
+
124
+ | Modul | Beschreibung |
125
+ |---|---|
126
+ | `solrctl.tasks.core_swap` | Task: Core-Swap mit Retries |
127
+ | `solrctl.tasks.alias_swap` | Task: Alias-Swap mit Retries |
128
+ | `solrctl.tasks.cleanup` | Task: Optionales Löschen alter Cores/Collections |
129
+ | `solrctl.flows.swap_flow` | Flow: Kompletter Swap inkl. optionalem Cleanup |
130
+
131
+ Lokaler Testlauf:
132
+
133
+ ```bash
134
+ uv run python -m solrctl.flows.swap_flow
135
+ ```
136
+
137
+ ## Projektstruktur
138
+
139
+ ```
140
+ src/
141
+ solrctl/
142
+ cli.py # Click-CLI
143
+ client.py # SolrClient (HTTP-API-Wrapper)
144
+ tasks/ # Prefect-Tasks (optional)
145
+ alias_swap.py
146
+ core_swap.py
147
+ cleanup.py
148
+ flows/ # Prefect-Flows (optional)
149
+ swap_flow.py
150
+ tests/
151
+ docs/
152
+ idea.md
153
+ plan.md
154
+ technical.md
155
+ ```
156
+
157
+ ## Weitere Dokumentation
158
+
159
+ - [`docs/plan.md`](docs/plan.md) – Ausführungsplan und aktueller Status
160
+ - [`docs/technical.md`](docs/technical.md) – Architekturentscheidungen
161
+
162
+ ## KI-Unterstützung
163
+
164
+ Dieses Projekt wurde mit Unterstützung von KI-Tools (Large Language Models) entwickelt. Code-Struktur, Implementierung und Dokumentation wurden dabei unterstützt durch automatiserte Vorschläge. Alle Entscheidungen wurden vom Entwickler geprüft und bestätigt. Transparenz über den Entwicklungsprozess ist uns wichtig.
@@ -0,0 +1,130 @@
1
+ # solrctl
2
+
3
+ CLI-Tool zur Verwaltung von Apache Solr 9.x – ohne Downtime Cores tauschen, Status abfragen, Konfiguration einsehen.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ uv tool install solrctl
9
+ ```
10
+
11
+ Oder aus dem Checkout:
12
+
13
+ ```bash
14
+ uv pip install -e .
15
+ ```
16
+
17
+ ## CLI-Befehle
18
+
19
+ ```bash
20
+ # Solr erreichbar?
21
+ solrctl ping
22
+
23
+ # Cores anzeigen
24
+ solrctl core list
25
+
26
+ # Core-Infos (Pfade, Dokumentanzahl, Größe)
27
+ solrctl core info <name>
28
+
29
+ # solrconfig.xml abrufen
30
+ solrctl core config <name>
31
+
32
+ # Schema als XML abrufen
33
+ solrctl core schema <name>
34
+
35
+ # Core-Swap (unterbrechungsfrei)
36
+ solrctl core swap --source <neuer_core> --target <aktiver_core>
37
+
38
+ # Core löschen
39
+ solrctl core delete <name> [--delete-index]
40
+
41
+ # Alias-Operationen (SolrCloud)
42
+ solrctl alias list
43
+ solrctl alias swap --alias <name> --collection <ziel>
44
+ solrctl alias delete <name>
45
+ ```
46
+
47
+ Alle Befehle akzeptieren globale Optionen:
48
+
49
+ ```bash
50
+ --solr-url Basis-URL der Solr-Instanz (Default: http://localhost:8983, Env: SOLR_URL)
51
+ --user Basic-Auth Benutzername (Env: SOLR_USER)
52
+ --password Basic-Auth Passwort (Env: SOLR_PASSWORD)
53
+ ```
54
+
55
+ ## Voraussetzungen
56
+
57
+ - Python 3.11+
58
+ - Zugriff auf eine Solr 9.x Instanz (Standalone für Core-Befehle, SolrCloud für Alias-Befehle)
59
+
60
+ ## Lokale Entwicklung
61
+
62
+ ```bash
63
+ # Abhängigkeiten inkl. Dev- und Prefect-Extras installieren
64
+ uv sync --all-extras
65
+
66
+ # Tests ausführen
67
+ uv run pytest
68
+
69
+ # CLI lokal ausführen
70
+ uv run solrctl --help
71
+ ```
72
+
73
+ ### Docker-basiertes Test-Setup
74
+
75
+ ```bash
76
+ docker compose up -d
77
+ solrctl --solr-url http://localhost:8983 ping
78
+ ```
79
+
80
+ ## Prefect-Integration
81
+
82
+ solrctl liefert optionale Prefect-Tasks und -Flows für automatisierte Pipelines. Die Prefect-Abhängigkeiten werden nicht standardmäßig installiert:
83
+
84
+ ```bash
85
+ uv pip install -e ".[prefect]"
86
+ ```
87
+
88
+ Verfügbare Tasks und Flows:
89
+
90
+ | Modul | Beschreibung |
91
+ |---|---|
92
+ | `solrctl.tasks.core_swap` | Task: Core-Swap mit Retries |
93
+ | `solrctl.tasks.alias_swap` | Task: Alias-Swap mit Retries |
94
+ | `solrctl.tasks.cleanup` | Task: Optionales Löschen alter Cores/Collections |
95
+ | `solrctl.flows.swap_flow` | Flow: Kompletter Swap inkl. optionalem Cleanup |
96
+
97
+ Lokaler Testlauf:
98
+
99
+ ```bash
100
+ uv run python -m solrctl.flows.swap_flow
101
+ ```
102
+
103
+ ## Projektstruktur
104
+
105
+ ```
106
+ src/
107
+ solrctl/
108
+ cli.py # Click-CLI
109
+ client.py # SolrClient (HTTP-API-Wrapper)
110
+ tasks/ # Prefect-Tasks (optional)
111
+ alias_swap.py
112
+ core_swap.py
113
+ cleanup.py
114
+ flows/ # Prefect-Flows (optional)
115
+ swap_flow.py
116
+ tests/
117
+ docs/
118
+ idea.md
119
+ plan.md
120
+ technical.md
121
+ ```
122
+
123
+ ## Weitere Dokumentation
124
+
125
+ - [`docs/plan.md`](docs/plan.md) – Ausführungsplan und aktueller Status
126
+ - [`docs/technical.md`](docs/technical.md) – Architekturentscheidungen
127
+
128
+ ## KI-Unterstützung
129
+
130
+ Dieses Projekt wurde mit Unterstützung von KI-Tools (Large Language Models) entwickelt. Code-Struktur, Implementierung und Dokumentation wurden dabei unterstützt durch automatiserte Vorschläge. Alle Entscheidungen wurden vom Entwickler geprüft und bestätigt. Transparenz über den Entwicklungsprozess ist uns wichtig.
@@ -0,0 +1,58 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "solrctl"
7
+ version = "0.5.0"
8
+ description = "CLI-Tool zur Verwaltung von Apache Solr 9.x – Cores tauschen, Status abfragen, Konfiguration einsehen"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.11"
12
+ authors = [
13
+ {name = "Thomas Baer", email = "thomas.baer@slub-dresden.de"},
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Environment :: Console",
18
+ "Intended Audience :: Developers",
19
+ "Intended Audience :: System Administrators",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: System :: Systems Administration",
25
+ ]
26
+ dependencies = [
27
+ "httpx>=0.27",
28
+ "pydantic>=2.7",
29
+ "click>=8.1",
30
+ "loguru>=0.7",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ prefect = [
35
+ "prefect==3.6.19",
36
+ "python-dotenv>=1.0",
37
+ ]
38
+ dev = [
39
+ "pytest>=8.2",
40
+ "pytest-asyncio>=0.23",
41
+ "pytest-env>=1.1",
42
+ "respx>=0.21",
43
+ "pytest-cov>=5.0",
44
+ "pysolr>=3.10",
45
+ ]
46
+
47
+ [project.scripts]
48
+ solrctl = "solrctl.cli:main"
49
+
50
+ [tool.setuptools.packages.find]
51
+ where = ["src"]
52
+
53
+ [tool.pytest.ini_options]
54
+ asyncio_mode = "auto"
55
+ testpaths = ["tests"]
56
+ env = [
57
+ "PREFECT_API_URL=",
58
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,231 @@
1
+ """solrctl CLI – Verwaltung von Solr-Cores und Aliases."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+ from loguru import logger
7
+
8
+ from solrctl.client import SolrClient, SolrClientError
9
+
10
+
11
+ class SolrContext:
12
+ """Hält den SolrClient für die Laufzeit des CLI-Aufrufs."""
13
+
14
+ def __init__(self, solr_url: str, auth: tuple[str, str] | None) -> None:
15
+ self.client = SolrClient(solr_url, auth=auth)
16
+
17
+ def close(self) -> None:
18
+ self.client.close()
19
+
20
+
21
+ pass_solr_ctx = click.make_pass_decorator(SolrContext)
22
+
23
+
24
+ def _handle_error(exc: SolrClientError) -> None:
25
+ click.secho(f"Fehler: {exc}", fg="red", err=True)
26
+ raise SystemExit(1)
27
+
28
+
29
+ @click.group()
30
+ @click.option(
31
+ "--solr-url",
32
+ envvar="SOLR_URL",
33
+ default="http://localhost:8983",
34
+ show_default=True,
35
+ help="Basis-URL der Solr-Instanz.",
36
+ )
37
+ @click.option(
38
+ "--user", envvar="SOLR_USER", default=None, help="Basic-Auth Benutzername."
39
+ )
40
+ @click.option(
41
+ "--password", envvar="SOLR_PASSWORD", default=None, help="Basic-Auth Passwort."
42
+ )
43
+ @click.pass_context
44
+ def cli(
45
+ ctx: click.Context, solr_url: str, user: str | None, password: str | None
46
+ ) -> None:
47
+ """solrctl – Verwaltung von Solr-Cores und Aliases."""
48
+ auth = (user, password) if user and password else None
49
+ ctx.obj = SolrContext(solr_url, auth)
50
+
51
+
52
+ @cli.command()
53
+ @pass_solr_ctx
54
+ def ping(ctx: SolrContext) -> None:
55
+ """Prüft, ob Solr erreichbar ist."""
56
+ if ctx.client.ping():
57
+ click.echo("Solr erreichbar.")
58
+ else:
59
+ click.secho("Solr NICHT erreichbar.", fg="red", err=True)
60
+ raise SystemExit(1)
61
+
62
+
63
+ # ── core ──────────────────────────────────────────────────────────
64
+
65
+
66
+ @cli.group()
67
+ def core() -> None:
68
+ """Standalone Core-Operationen."""
69
+
70
+
71
+ @core.command("list")
72
+ @pass_solr_ctx
73
+ def core_list(ctx: SolrContext) -> None:
74
+ """Listet alle vorhandenen Cores auf."""
75
+ try:
76
+ cores = ctx.client.list_cores()
77
+ except SolrClientError as exc:
78
+ _handle_error(exc)
79
+ return
80
+
81
+ if not cores:
82
+ click.echo("Keine Cores gefunden.")
83
+ return
84
+ for name in cores:
85
+ click.echo(name)
86
+
87
+
88
+ @core.command()
89
+ @click.option("--source", required=True, help="Name des neuen Cores (frische Daten).")
90
+ @click.option(
91
+ "--target", required=True, help="Name des aktiven Cores (wird getauscht)."
92
+ )
93
+ @pass_solr_ctx
94
+ def swap(ctx: SolrContext, source: str, target: str) -> None:
95
+ """Tauscht zwei Solr-Cores (CoreAdmin SWAP-Action)."""
96
+ logger.info("Swap: '{}' <-> '{}'", target, source)
97
+ try:
98
+ ctx.client.swap_cores(target, source)
99
+ click.secho(f"Swap erfolgreich: '{target}' <-> '{source}'.", fg="green")
100
+ except SolrClientError as exc:
101
+ _handle_error(exc)
102
+
103
+
104
+ @core.command()
105
+ @click.argument("name")
106
+ @click.option("--delete-index", is_flag=True, help="Index-Daten auf Disk löschen.")
107
+ @pass_solr_ctx
108
+ def delete(ctx: SolrContext, name: str, delete_index: bool) -> None:
109
+ """Löscht (unload) einen Solr-Core."""
110
+ try:
111
+ ctx.client.unload_core(name, delete_index=delete_index)
112
+ click.secho(f"Core '{name}' gelöscht.", fg="green")
113
+ except SolrClientError as exc:
114
+ _handle_error(exc)
115
+
116
+
117
+ @core.command("info")
118
+ @click.argument("name")
119
+ @pass_solr_ctx
120
+ def core_info(ctx: SolrContext, name: str) -> None:
121
+ """Zeigt detaillierte Informationen zu einem Core an."""
122
+ try:
123
+ info = ctx.client.get_core_info(name)
124
+ except SolrClientError as exc:
125
+ _handle_error(exc)
126
+ return
127
+
128
+ click.echo(f"Core: {info.get('name', name)}")
129
+ click.echo(f"InstanceDir: {info.get('instanceDir')}")
130
+ click.echo(f"DataDir: {info.get('dataDir')}")
131
+ click.echo(f"Config: {info.get('config')}")
132
+ click.echo(f"Schema: {info.get('schema')}")
133
+ click.echo(f"StartTime: {info.get('startTime')}")
134
+ uptime = info.get("uptime", 0)
135
+ click.echo(f"Uptime: {uptime // 1000}s")
136
+
137
+ index = info.get("index", {})
138
+ if index:
139
+ click.echo()
140
+ click.secho("Index:", bold=True)
141
+ click.echo(f" numDocs: {index.get('numDocs')}")
142
+ click.echo(f" maxDoc: {index.get('maxDoc')}")
143
+ click.echo(f" deletedDocs: {index.get('deletedDocs')}")
144
+ click.echo(f" segments: {index.get('segmentCount')}")
145
+ click.echo(f" size: {index.get('size')}")
146
+ click.echo(f" lastModified: {index.get('lastModified')}")
147
+
148
+
149
+ @core.command("config")
150
+ @click.argument("name")
151
+ @pass_solr_ctx
152
+ def core_config(ctx: SolrContext, name: str) -> None:
153
+ """Gibt die solrconfig.xml eines Cores als XML aus."""
154
+ try:
155
+ config_xml = ctx.client.get_core_config(name)
156
+ except SolrClientError as exc:
157
+ _handle_error(exc)
158
+ return
159
+ click.echo(config_xml)
160
+
161
+
162
+ @core.command("schema")
163
+ @click.argument("name")
164
+ @pass_solr_ctx
165
+ def core_schema(ctx: SolrContext, name: str) -> None:
166
+ """Gibt das Schema eines Cores als XML aus."""
167
+ try:
168
+ schema_xml = ctx.client.get_core_schema(name)
169
+ except SolrClientError as exc:
170
+ _handle_error(exc)
171
+ return
172
+ click.echo(schema_xml)
173
+
174
+
175
+ # ── alias ─────────────────────────────────────────────────────────
176
+
177
+
178
+ @cli.group()
179
+ def alias() -> None:
180
+ """SolrCloud Alias-Operationen."""
181
+
182
+
183
+ @alias.command("list")
184
+ @pass_solr_ctx
185
+ def alias_list(ctx: SolrContext) -> None:
186
+ """Listet alle Aliases auf."""
187
+ try:
188
+ aliases = ctx.client.list_aliases()
189
+ except SolrClientError as exc:
190
+ _handle_error(exc)
191
+ return
192
+
193
+ if not aliases:
194
+ click.echo("Keine Aliases gefunden.")
195
+ return
196
+ for alias_name, collection in aliases.items():
197
+ click.echo(f"{alias_name} -> {collection}")
198
+
199
+
200
+ @alias.command()
201
+ @click.option("--alias", required=True, help="Name des Alias.")
202
+ @click.option("--collection", required=True, help="Ziel-Collection.")
203
+ @pass_solr_ctx
204
+ def swap(ctx: SolrContext, alias: str, collection: str) -> None:
205
+ """Setzt einen Alias auf eine neue Collection um."""
206
+ try:
207
+ ctx.client.create_or_update_alias(alias, collection)
208
+ click.secho(f"Alias '{alias}' -> '{collection}' gesetzt.", fg="green")
209
+ except SolrClientError as exc:
210
+ _handle_error(exc)
211
+
212
+
213
+ @alias.command()
214
+ @click.argument("name")
215
+ @pass_solr_ctx
216
+ def delete(ctx: SolrContext, name: str) -> None:
217
+ """Löscht einen Alias."""
218
+ try:
219
+ ctx.client.delete_alias(name)
220
+ click.secho(f"Alias '{name}' gelöscht.", fg="green")
221
+ except SolrClientError as exc:
222
+ _handle_error(exc)
223
+
224
+
225
+ def main() -> None:
226
+ """Einstiegspunkt für die CLI."""
227
+ cli()
228
+
229
+
230
+ if __name__ == "__main__":
231
+ main()