watchmen-agent-cli 18.0.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.
- watchmen_agent_cli-18.0.0/PKG-INFO +11 -0
- watchmen_agent_cli-18.0.0/pyproject.toml +24 -0
- watchmen_agent_cli-18.0.0/src/agent_cli/__init__.py +3 -0
- watchmen_agent_cli-18.0.0/src/agent_cli/__main__.py +3 -0
- watchmen_agent_cli-18.0.0/src/agent_cli/cli.py +379 -0
- watchmen_agent_cli-18.0.0/src/agent_cli/exceptions.py +14 -0
- watchmen_agent_cli-18.0.0/src/agent_cli/http_client.py +120 -0
- watchmen_agent_cli-18.0.0/src/agent_cli/main.py +504 -0
- watchmen_agent_cli-18.0.0/src/agent_cli/settings.py +22 -0
- watchmen_agent_cli-18.0.0/src/agent_cli/sync_service.py +504 -0
- watchmen_agent_cli-18.0.0/src/agent_cli/vault.py +129 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: watchmen-agent-cli
|
|
3
|
+
Version: 18.0.0
|
|
4
|
+
Summary: Watchmen agent friendly CLI for topic and pipeline synchronization
|
|
5
|
+
Author: imma-team
|
|
6
|
+
Requires-Python: >=3.12,<3.13
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
9
|
+
Requires-Dist: pyyaml (>=6.0.3,<7.0.0)
|
|
10
|
+
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
|
11
|
+
Requires-Dist: typer (>=0.16.1,<0.17.0)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "watchmen-agent-cli"
|
|
3
|
+
version = "18.0.0"
|
|
4
|
+
description = "Watchmen agent friendly CLI for topic and pipeline synchronization"
|
|
5
|
+
authors = ["imma-team"]
|
|
6
|
+
packages = [
|
|
7
|
+
{ include = "agent_cli", from = "src" }
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
[tool.poetry.dependencies]
|
|
11
|
+
python = "~3.12"
|
|
12
|
+
requests = "^2.32.3"
|
|
13
|
+
pyyaml = "^6.0.3"
|
|
14
|
+
typer = "^0.16.1"
|
|
15
|
+
|
|
16
|
+
[tool.poetry.group.dev.dependencies]
|
|
17
|
+
pyinstaller = "^6.19.0"
|
|
18
|
+
|
|
19
|
+
[tool.poetry.scripts]
|
|
20
|
+
agent-cli = "agent_cli.cli:run"
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["poetry-core>=1.0.0"]
|
|
24
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import traceback
|
|
4
|
+
from argparse import Namespace
|
|
5
|
+
from typing import Callable, Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from agent_cli.exceptions import AgentCliException
|
|
10
|
+
from agent_cli.main import (
|
|
11
|
+
handle_config_show,
|
|
12
|
+
handle_discover,
|
|
13
|
+
handle_enum_list,
|
|
14
|
+
handle_enum_list_remote,
|
|
15
|
+
handle_enum_pull,
|
|
16
|
+
handle_enum_pull_name,
|
|
17
|
+
handle_enum_push_file,
|
|
18
|
+
handle_ingest_model_list,
|
|
19
|
+
handle_ingest_model_list_remote,
|
|
20
|
+
handle_ingest_model_pull,
|
|
21
|
+
handle_ingest_model_push_file,
|
|
22
|
+
handle_ingest_module_list,
|
|
23
|
+
handle_ingest_module_list_remote,
|
|
24
|
+
handle_ingest_module_pull,
|
|
25
|
+
handle_ingest_module_push_file,
|
|
26
|
+
handle_ingest_table_list,
|
|
27
|
+
handle_ingest_table_list_remote,
|
|
28
|
+
handle_ingest_table_pull,
|
|
29
|
+
handle_ingest_table_push_file,
|
|
30
|
+
handle_init,
|
|
31
|
+
handle_pipeline_list,
|
|
32
|
+
handle_pipeline_list_remote,
|
|
33
|
+
handle_pipeline_pull,
|
|
34
|
+
handle_pipeline_pull_name,
|
|
35
|
+
handle_pipeline_push_file,
|
|
36
|
+
handle_pull,
|
|
37
|
+
handle_push,
|
|
38
|
+
handle_tenant_info,
|
|
39
|
+
handle_topic_list,
|
|
40
|
+
handle_topic_list_remote,
|
|
41
|
+
handle_topic_pull,
|
|
42
|
+
handle_topic_pull_name,
|
|
43
|
+
handle_topic_push_file,
|
|
44
|
+
output_error,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
app = typer.Typer(help="Watchmen Topic/Pipeline sync CLI powered by Typer")
|
|
48
|
+
topic_app = typer.Typer(help="Fine-grained topic commands")
|
|
49
|
+
pipeline_app = typer.Typer(help="Fine-grained pipeline commands")
|
|
50
|
+
enum_app = typer.Typer(help="Fine-grained enum commands")
|
|
51
|
+
ingest_app = typer.Typer(help="Ingest config YAML commands")
|
|
52
|
+
ingest_table_app = typer.Typer(help="Collector table config commands")
|
|
53
|
+
ingest_model_app = typer.Typer(help="Collector model config commands")
|
|
54
|
+
ingest_module_app = typer.Typer(help="Collector module config commands")
|
|
55
|
+
app.add_typer(topic_app, name="topic")
|
|
56
|
+
app.add_typer(pipeline_app, name="pipeline")
|
|
57
|
+
app.add_typer(enum_app, name="enum")
|
|
58
|
+
app.add_typer(ingest_app, name="ingest")
|
|
59
|
+
ingest_app.add_typer(ingest_table_app, name="table")
|
|
60
|
+
ingest_app.add_typer(ingest_model_app, name="model")
|
|
61
|
+
ingest_app.add_typer(ingest_module_app, name="module")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _namespace(**kwargs) -> Namespace:
|
|
65
|
+
return Namespace(**kwargs)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _run_with_guard(ctx: typer.Context, action: Callable[[], None]) -> None:
|
|
69
|
+
try:
|
|
70
|
+
action()
|
|
71
|
+
except AgentCliException as e:
|
|
72
|
+
output_error(f"{e.__class__.__name__}: {e}")
|
|
73
|
+
if ctx.obj and ctx.obj.get("debug"):
|
|
74
|
+
traceback.print_exc()
|
|
75
|
+
raise typer.Exit(getattr(e, "exit_code", 1))
|
|
76
|
+
except Exception as e:
|
|
77
|
+
output_error(f"UnexpectedError: {e}")
|
|
78
|
+
if ctx.obj and ctx.obj.get("debug"):
|
|
79
|
+
traceback.print_exc()
|
|
80
|
+
raise typer.Exit(10)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@app.callback(invoke_without_command=True)
|
|
84
|
+
def cli_callback(
|
|
85
|
+
ctx: typer.Context,
|
|
86
|
+
debug: bool = typer.Option(False, "--debug", help="Print exception traceback for debugging"),
|
|
87
|
+
) -> None:
|
|
88
|
+
ctx.obj = {"debug": debug}
|
|
89
|
+
if ctx.invoked_subcommand is None:
|
|
90
|
+
typer.echo(ctx.get_help())
|
|
91
|
+
raise typer.Exit()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@app.command("init")
|
|
95
|
+
def init_command(
|
|
96
|
+
ctx: typer.Context,
|
|
97
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
98
|
+
host: Optional[str] = typer.Option(None, "--host"),
|
|
99
|
+
username: Optional[str] = typer.Option(None, "--username"),
|
|
100
|
+
password: Optional[str] = typer.Option(None, "--password"),
|
|
101
|
+
pat: Optional[str] = typer.Option(None, "--pat"),
|
|
102
|
+
) -> None:
|
|
103
|
+
_run_with_guard(ctx, lambda: handle_init(_namespace(vault=vault, host=host, username=username, password=password, pat=pat)))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@app.command("pull")
|
|
107
|
+
def pull_command(
|
|
108
|
+
ctx: typer.Context,
|
|
109
|
+
target: str = typer.Option("all", "--target", help="topic | pipeline | all"),
|
|
110
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
111
|
+
) -> None:
|
|
112
|
+
_run_with_guard(ctx, lambda: handle_pull(_namespace(target=target, vault=vault)))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@app.command("push")
|
|
116
|
+
def push_command(
|
|
117
|
+
ctx: typer.Context,
|
|
118
|
+
target: str = typer.Option("all", "--target", help="topic | pipeline | all"),
|
|
119
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
120
|
+
) -> None:
|
|
121
|
+
_run_with_guard(ctx, lambda: handle_push(_namespace(target=target, vault=vault)))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@app.command("tenant")
|
|
125
|
+
def tenant_command(
|
|
126
|
+
ctx: typer.Context,
|
|
127
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
128
|
+
) -> None:
|
|
129
|
+
_run_with_guard(ctx, lambda: handle_tenant_info(_namespace(vault=vault)))
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@app.command("config")
|
|
133
|
+
def config_command(
|
|
134
|
+
ctx: typer.Context,
|
|
135
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
136
|
+
) -> None:
|
|
137
|
+
_run_with_guard(ctx, lambda: handle_config_show(_namespace(vault=vault)))
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@app.command("discover")
|
|
141
|
+
def discover_command(ctx: typer.Context) -> None:
|
|
142
|
+
_run_with_guard(ctx, lambda: handle_discover(_namespace()))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@topic_app.command("pull")
|
|
146
|
+
def topic_pull_command(
|
|
147
|
+
ctx: typer.Context,
|
|
148
|
+
topic_id: str,
|
|
149
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
150
|
+
) -> None:
|
|
151
|
+
_run_with_guard(ctx, lambda: handle_topic_pull(_namespace(topic_id=topic_id, vault=vault)))
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@topic_app.command("pull-name")
|
|
155
|
+
def topic_pull_name_command(
|
|
156
|
+
ctx: typer.Context,
|
|
157
|
+
topic_name: str,
|
|
158
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
159
|
+
) -> None:
|
|
160
|
+
_run_with_guard(ctx, lambda: handle_topic_pull_name(_namespace(topic_name=topic_name, vault=vault)))
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@topic_app.command("push-file")
|
|
164
|
+
def topic_push_file_command(
|
|
165
|
+
ctx: typer.Context,
|
|
166
|
+
file_path: str,
|
|
167
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
168
|
+
) -> None:
|
|
169
|
+
_run_with_guard(ctx, lambda: handle_topic_push_file(_namespace(file_path=file_path, vault=vault)))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@topic_app.command("list")
|
|
173
|
+
def topic_list_command(
|
|
174
|
+
ctx: typer.Context,
|
|
175
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
176
|
+
) -> None:
|
|
177
|
+
_run_with_guard(ctx, lambda: handle_topic_list(_namespace(vault=vault)))
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@topic_app.command("list-remote")
|
|
181
|
+
def topic_list_remote_command(
|
|
182
|
+
ctx: typer.Context,
|
|
183
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
184
|
+
) -> None:
|
|
185
|
+
_run_with_guard(ctx, lambda: handle_topic_list_remote(_namespace(vault=vault)))
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@pipeline_app.command("pull")
|
|
189
|
+
def pipeline_pull_command(
|
|
190
|
+
ctx: typer.Context,
|
|
191
|
+
pipeline_id: str,
|
|
192
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
193
|
+
) -> None:
|
|
194
|
+
_run_with_guard(ctx, lambda: handle_pipeline_pull(_namespace(pipeline_id=pipeline_id, vault=vault)))
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@pipeline_app.command("pull-name")
|
|
198
|
+
def pipeline_pull_name_command(
|
|
199
|
+
ctx: typer.Context,
|
|
200
|
+
pipeline_name: str,
|
|
201
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
202
|
+
) -> None:
|
|
203
|
+
_run_with_guard(ctx, lambda: handle_pipeline_pull_name(_namespace(pipeline_name=pipeline_name, vault=vault)))
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@pipeline_app.command("push-file")
|
|
207
|
+
def pipeline_push_file_command(
|
|
208
|
+
ctx: typer.Context,
|
|
209
|
+
file_path: str,
|
|
210
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
211
|
+
) -> None:
|
|
212
|
+
_run_with_guard(ctx, lambda: handle_pipeline_push_file(_namespace(file_path=file_path, vault=vault)))
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@pipeline_app.command("list")
|
|
216
|
+
def pipeline_list_command(
|
|
217
|
+
ctx: typer.Context,
|
|
218
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
219
|
+
) -> None:
|
|
220
|
+
_run_with_guard(ctx, lambda: handle_pipeline_list(_namespace(vault=vault)))
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@pipeline_app.command("list-remote")
|
|
224
|
+
def pipeline_list_remote_command(
|
|
225
|
+
ctx: typer.Context,
|
|
226
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
227
|
+
) -> None:
|
|
228
|
+
_run_with_guard(ctx, lambda: handle_pipeline_list_remote(_namespace(vault=vault)))
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@enum_app.command("pull")
|
|
232
|
+
def enum_pull_command(
|
|
233
|
+
ctx: typer.Context,
|
|
234
|
+
enum_id: str,
|
|
235
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
236
|
+
) -> None:
|
|
237
|
+
_run_with_guard(ctx, lambda: handle_enum_pull(_namespace(enum_id=enum_id, vault=vault)))
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@enum_app.command("pull-name")
|
|
241
|
+
def enum_pull_name_command(
|
|
242
|
+
ctx: typer.Context,
|
|
243
|
+
enum_name: str,
|
|
244
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
245
|
+
) -> None:
|
|
246
|
+
_run_with_guard(ctx, lambda: handle_enum_pull_name(_namespace(enum_name=enum_name, vault=vault)))
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@enum_app.command("push-file")
|
|
250
|
+
def enum_push_file_command(
|
|
251
|
+
ctx: typer.Context,
|
|
252
|
+
file_path: str,
|
|
253
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
254
|
+
) -> None:
|
|
255
|
+
_run_with_guard(ctx, lambda: handle_enum_push_file(_namespace(file_path=file_path, vault=vault)))
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@enum_app.command("list")
|
|
259
|
+
def enum_list_command(
|
|
260
|
+
ctx: typer.Context,
|
|
261
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
262
|
+
) -> None:
|
|
263
|
+
_run_with_guard(ctx, lambda: handle_enum_list(_namespace(vault=vault)))
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@enum_app.command("list-remote")
|
|
267
|
+
def enum_list_remote_command(
|
|
268
|
+
ctx: typer.Context,
|
|
269
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
270
|
+
) -> None:
|
|
271
|
+
_run_with_guard(ctx, lambda: handle_enum_list_remote(_namespace(vault=vault)))
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@ingest_table_app.command("pull")
|
|
275
|
+
def ingest_table_pull_command(
|
|
276
|
+
ctx: typer.Context,
|
|
277
|
+
table_name: str,
|
|
278
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
279
|
+
) -> None:
|
|
280
|
+
_run_with_guard(ctx, lambda: handle_ingest_table_pull(_namespace(table_name=table_name, vault=vault)))
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@ingest_table_app.command("push-file")
|
|
284
|
+
def ingest_table_push_file_command(
|
|
285
|
+
ctx: typer.Context,
|
|
286
|
+
file_path: str,
|
|
287
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
288
|
+
) -> None:
|
|
289
|
+
_run_with_guard(ctx, lambda: handle_ingest_table_push_file(_namespace(file_path=file_path, vault=vault)))
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@ingest_table_app.command("list")
|
|
293
|
+
def ingest_table_list_command(
|
|
294
|
+
ctx: typer.Context,
|
|
295
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
296
|
+
) -> None:
|
|
297
|
+
_run_with_guard(ctx, lambda: handle_ingest_table_list(_namespace(vault=vault)))
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@ingest_table_app.command("list-remote")
|
|
301
|
+
def ingest_table_list_remote_command(
|
|
302
|
+
ctx: typer.Context,
|
|
303
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
304
|
+
) -> None:
|
|
305
|
+
_run_with_guard(ctx, lambda: handle_ingest_table_list_remote(_namespace(vault=vault)))
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@ingest_model_app.command("pull")
|
|
309
|
+
def ingest_model_pull_command(
|
|
310
|
+
ctx: typer.Context,
|
|
311
|
+
model_name: str,
|
|
312
|
+
all: bool = typer.Option(False, "--all"),
|
|
313
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
314
|
+
) -> None:
|
|
315
|
+
_run_with_guard(ctx, lambda: handle_ingest_model_pull(_namespace(model_name=model_name, all=all, vault=vault)))
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@ingest_model_app.command("push-file")
|
|
319
|
+
def ingest_model_push_file_command(
|
|
320
|
+
ctx: typer.Context,
|
|
321
|
+
file_path: str,
|
|
322
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
323
|
+
) -> None:
|
|
324
|
+
_run_with_guard(ctx, lambda: handle_ingest_model_push_file(_namespace(file_path=file_path, vault=vault)))
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@ingest_model_app.command("list")
|
|
328
|
+
def ingest_model_list_command(
|
|
329
|
+
ctx: typer.Context,
|
|
330
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
331
|
+
) -> None:
|
|
332
|
+
_run_with_guard(ctx, lambda: handle_ingest_model_list(_namespace(vault=vault)))
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
@ingest_model_app.command("list-remote")
|
|
336
|
+
def ingest_model_list_remote_command(
|
|
337
|
+
ctx: typer.Context,
|
|
338
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
339
|
+
) -> None:
|
|
340
|
+
_run_with_guard(ctx, lambda: handle_ingest_model_list_remote(_namespace(vault=vault)))
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
@ingest_module_app.command("pull")
|
|
344
|
+
def ingest_module_pull_command(
|
|
345
|
+
ctx: typer.Context,
|
|
346
|
+
module_name: str,
|
|
347
|
+
all: bool = typer.Option(False, "--all"),
|
|
348
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
349
|
+
) -> None:
|
|
350
|
+
_run_with_guard(ctx, lambda: handle_ingest_module_pull(_namespace(module_name=module_name, all=all, vault=vault)))
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
@ingest_module_app.command("push-file")
|
|
354
|
+
def ingest_module_push_file_command(
|
|
355
|
+
ctx: typer.Context,
|
|
356
|
+
file_path: str,
|
|
357
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
358
|
+
) -> None:
|
|
359
|
+
_run_with_guard(ctx, lambda: handle_ingest_module_push_file(_namespace(file_path=file_path, vault=vault)))
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@ingest_module_app.command("list")
|
|
363
|
+
def ingest_module_list_command(
|
|
364
|
+
ctx: typer.Context,
|
|
365
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
366
|
+
) -> None:
|
|
367
|
+
_run_with_guard(ctx, lambda: handle_ingest_module_list(_namespace(vault=vault)))
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
@ingest_module_app.command("list-remote")
|
|
371
|
+
def ingest_module_list_remote_command(
|
|
372
|
+
ctx: typer.Context,
|
|
373
|
+
vault: Optional[str] = typer.Option(None, "--vault"),
|
|
374
|
+
) -> None:
|
|
375
|
+
_run_with_guard(ctx, lambda: handle_ingest_module_list_remote(_namespace(vault=vault)))
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def run() -> None:
|
|
379
|
+
app(prog_name="agent-cli")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class AgentCliException(Exception):
|
|
2
|
+
exit_code = 1
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ConfigException(AgentCliException):
|
|
6
|
+
exit_code = 3
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AuthenticationException(AgentCliException):
|
|
10
|
+
exit_code = 4
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ApiException(AgentCliException):
|
|
14
|
+
exit_code = 5
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from agent_cli.exceptions import ApiException, AuthenticationException
|
|
8
|
+
|
|
9
|
+
LOGIN_URL = "/login"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RestClient:
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
host: str,
|
|
16
|
+
pat: Optional[str] = None,
|
|
17
|
+
username: Optional[str] = None,
|
|
18
|
+
password: Optional[str] = None,
|
|
19
|
+
timeout_seconds: int = 30,
|
|
20
|
+
) -> None:
|
|
21
|
+
self.host = host.rstrip("/")
|
|
22
|
+
self.pat = pat
|
|
23
|
+
self.username = username
|
|
24
|
+
self.password = password
|
|
25
|
+
self.timeout_seconds = timeout_seconds
|
|
26
|
+
self._token: Optional[str] = None
|
|
27
|
+
|
|
28
|
+
def auth_header(self) -> Dict[str, str]:
|
|
29
|
+
token = self._login()
|
|
30
|
+
return {"Authorization": token}
|
|
31
|
+
|
|
32
|
+
def get_json(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
|
|
33
|
+
url = self._url(path)
|
|
34
|
+
headers = self.auth_header()
|
|
35
|
+
try:
|
|
36
|
+
response = requests.get(url, params=params, headers=headers, timeout=self.timeout_seconds)
|
|
37
|
+
except requests.RequestException as e:
|
|
38
|
+
raise ApiException(f"GET {path} failed: {e}") from e
|
|
39
|
+
if response.status_code >= 400:
|
|
40
|
+
raise ApiException(f"GET {path} failed with {response.status_code}: {response.text}")
|
|
41
|
+
return response.json()
|
|
42
|
+
|
|
43
|
+
def post_json(self, path: str, payload: Any) -> Any:
|
|
44
|
+
url = self._url(path)
|
|
45
|
+
headers = self.auth_header()
|
|
46
|
+
headers["Content-Type"] = "application/json"
|
|
47
|
+
try:
|
|
48
|
+
response = requests.post(url, json=payload, headers=headers, timeout=self.timeout_seconds)
|
|
49
|
+
except requests.RequestException as e:
|
|
50
|
+
raise ApiException(f"POST {path} failed: {e}") from e
|
|
51
|
+
if response.status_code >= 400:
|
|
52
|
+
raise ApiException(f"POST {path} failed with {response.status_code}: {response.text}")
|
|
53
|
+
if not response.text:
|
|
54
|
+
return None
|
|
55
|
+
return response.json()
|
|
56
|
+
|
|
57
|
+
def get_text(self, path: str, params: Optional[Dict[str, Any]] = None) -> str:
|
|
58
|
+
url = self._url(path)
|
|
59
|
+
headers = self.auth_header()
|
|
60
|
+
try:
|
|
61
|
+
response = requests.get(url, params=params, headers=headers, timeout=self.timeout_seconds)
|
|
62
|
+
except requests.RequestException as e:
|
|
63
|
+
raise ApiException(f"GET {path} failed: {e}") from e
|
|
64
|
+
if response.status_code >= 400:
|
|
65
|
+
raise ApiException(f"GET {path} failed with {response.status_code}: {response.text}")
|
|
66
|
+
return response.text
|
|
67
|
+
|
|
68
|
+
def post_text(self, path: str, payload: str, content_type: str = "application/x-yaml") -> str:
|
|
69
|
+
url = self._url(path)
|
|
70
|
+
headers = self.auth_header()
|
|
71
|
+
headers["Content-Type"] = content_type
|
|
72
|
+
try:
|
|
73
|
+
response = requests.post(url, data=payload.encode('utf-8'), headers=headers, timeout=self.timeout_seconds)
|
|
74
|
+
except requests.RequestException as e:
|
|
75
|
+
raise ApiException(f"POST {path} failed: {e}") from e
|
|
76
|
+
if response.status_code >= 400:
|
|
77
|
+
raise ApiException(f"POST {path} failed with {response.status_code}: {response.text}")
|
|
78
|
+
return response.text
|
|
79
|
+
|
|
80
|
+
def get_enum(self, enum_id: str) -> Any:
|
|
81
|
+
return self.get_json("/enum", params={"id": enum_id})
|
|
82
|
+
|
|
83
|
+
def get_enum_yaml(self, enum_id: str) -> str:
|
|
84
|
+
return self.get_text("/enum/yaml", params={"id": enum_id})
|
|
85
|
+
|
|
86
|
+
def save_enum_yaml(self, yaml_content: str) -> str:
|
|
87
|
+
return self.post_text("/enum/yaml", yaml_content)
|
|
88
|
+
|
|
89
|
+
def _login(self) -> str:
|
|
90
|
+
if self._token:
|
|
91
|
+
return self._token
|
|
92
|
+
if self.pat:
|
|
93
|
+
self._token = f"pat {self.pat}"
|
|
94
|
+
return self._token
|
|
95
|
+
if not self.username or not self.password:
|
|
96
|
+
raise AuthenticationException("Need PAT or username/password")
|
|
97
|
+
try:
|
|
98
|
+
response = requests.post(
|
|
99
|
+
self._url(LOGIN_URL),
|
|
100
|
+
data={"username": self.username, "password": self.password},
|
|
101
|
+
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
102
|
+
timeout=self.timeout_seconds,
|
|
103
|
+
)
|
|
104
|
+
except requests.RequestException as e:
|
|
105
|
+
raise AuthenticationException(f"Login failed: {e}") from e
|
|
106
|
+
if response.status_code >= 400:
|
|
107
|
+
raise AuthenticationException(f"Login failed with {response.status_code}: {response.text}")
|
|
108
|
+
payload = response.json()
|
|
109
|
+
token = payload.get("accessToken")
|
|
110
|
+
if not token:
|
|
111
|
+
raise AuthenticationException("Login response does not contain accessToken")
|
|
112
|
+
self._token = f"Bearer {token}"
|
|
113
|
+
return self._token
|
|
114
|
+
|
|
115
|
+
def _url(self, path: str) -> str:
|
|
116
|
+
if path.startswith("http://") or path.startswith("https://"):
|
|
117
|
+
return path
|
|
118
|
+
if not path.startswith("/"):
|
|
119
|
+
path = f"/{path}"
|
|
120
|
+
return f"{self.host}{path}"
|