qod-cli 0.3.7__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 (45) hide show
  1. qod_cli-0.3.7/.gitignore +125 -0
  2. qod_cli-0.3.7/PKG-INFO +17 -0
  3. qod_cli-0.3.7/README.md +297 -0
  4. qod_cli-0.3.7/pyproject.toml +32 -0
  5. qod_cli-0.3.7/src/qod_cli/__init__.py +1 -0
  6. qod_cli-0.3.7/src/qod_cli/commands/__init__.py +0 -0
  7. qod_cli-0.3.7/src/qod_cli/commands/_run.py +35 -0
  8. qod_cli-0.3.7/src/qod_cli/commands/auth.py +67 -0
  9. qod_cli-0.3.7/src/qod_cli/commands/catalog.py +173 -0
  10. qod_cli-0.3.7/src/qod_cli/commands/config_cmd.py +17 -0
  11. qod_cli-0.3.7/src/qod_cli/commands/database.py +69 -0
  12. qod_cli-0.3.7/src/qod_cli/commands/federation.py +82 -0
  13. qod_cli-0.3.7/src/qod_cli/commands/group.py +28 -0
  14. qod_cli-0.3.7/src/qod_cli/commands/maintenance.py +81 -0
  15. qod_cli-0.3.7/src/qod_cli/commands/manifest.py +34 -0
  16. qod_cli-0.3.7/src/qod_cli/commands/membership.py +50 -0
  17. qod_cli-0.3.7/src/qod_cli/commands/node.py +58 -0
  18. qod_cli-0.3.7/src/qod_cli/commands/pool.py +137 -0
  19. qod_cli-0.3.7/src/qod_cli/commands/role.py +150 -0
  20. qod_cli-0.3.7/src/qod_cli/commands/sql_cmd.py +86 -0
  21. qod_cli-0.3.7/src/qod_cli/commands/tag.py +47 -0
  22. qod_cli-0.3.7/src/qod_cli/commands/telemetry.py +95 -0
  23. qod_cli-0.3.7/src/qod_cli/commands/tenant.py +61 -0
  24. qod_cli-0.3.7/src/qod_cli/commands/user.py +62 -0
  25. qod_cli-0.3.7/src/qod_cli/config.py +107 -0
  26. qod_cli-0.3.7/src/qod_cli/main.py +96 -0
  27. qod_cli-0.3.7/src/qod_cli/output.py +66 -0
  28. qod_cli-0.3.7/src/qod_cli/rest.py +73 -0
  29. qod_cli-0.3.7/src/qod_cli/sql.py +80 -0
  30. qod_cli-0.3.7/tests/__init__.py +0 -0
  31. qod_cli-0.3.7/tests/conftest.py +18 -0
  32. qod_cli-0.3.7/tests/test_auth.py +68 -0
  33. qod_cli-0.3.7/tests/test_catalog_selectors.py +10 -0
  34. qod_cli-0.3.7/tests/test_commands_table.py +412 -0
  35. qod_cli-0.3.7/tests/test_config.py +60 -0
  36. qod_cli-0.3.7/tests/test_integration.py +38 -0
  37. qod_cli-0.3.7/tests/test_main.py +12 -0
  38. qod_cli-0.3.7/tests/test_manifest_io.py +32 -0
  39. qod_cli-0.3.7/tests/test_output.py +40 -0
  40. qod_cli-0.3.7/tests/test_repl.py +47 -0
  41. qod_cli-0.3.7/tests/test_rest.py +106 -0
  42. qod_cli-0.3.7/tests/test_sql_render.py +32 -0
  43. qod_cli-0.3.7/tests/test_sql_usage.py +7 -0
  44. qod_cli-0.3.7/tests/test_user_create_prompt.py +27 -0
  45. qod_cli-0.3.7/tests/test_wiring.py +56 -0
@@ -0,0 +1,125 @@
1
+ target/
2
+ !.mvn/wrapper/maven-wrapper.jar
3
+ !**/src/main/**/target/
4
+ !**/src/test/**/target/
5
+
6
+ ### IntelliJ IDEA ###
7
+ .idea/modules.xml
8
+ .idea/jarRepositories.xml
9
+ .idea/compiler.xml
10
+ .idea/libraries/
11
+ *.iws
12
+ *.iml
13
+ *.ipr
14
+ out/
15
+ !**/src/main/**/out/
16
+ !**/src/test/**/out/
17
+
18
+ ### Eclipse ###
19
+ .apt_generated
20
+ .classpath
21
+ .factorypath
22
+ .project
23
+ .settings
24
+ .springBeans
25
+ .sts4-cache
26
+ /bin/
27
+ !**/src/main/**/bin/
28
+ !**/src/test/**/bin/
29
+
30
+ ### NetBeans ###
31
+ /nbproject/private/
32
+ /nbbuild/
33
+ /dist/
34
+ /nbdist/
35
+ /.nb-gradle/
36
+ build/
37
+ !**/src/main/**/build/
38
+ !**/src/test/**/build/
39
+
40
+ ### VS Code ###
41
+ .vscode/
42
+
43
+ ### Mac OS ###
44
+ .DS_Store
45
+
46
+ ### Scala ###
47
+ .bsp/
48
+
49
+ # UI build output
50
+ src/main/resources/ui/
51
+ ui/node_modules/
52
+ ui/dist/
53
+
54
+ # Local state file
55
+ /state/
56
+
57
+ # Runtime logs from run-jar.sh
58
+ /logs/
59
+
60
+ # Bootstrapped sbt (run-jar.sh downloads here when sbt is not on PATH)
61
+ /.sbt-bootstrap/
62
+
63
+ # IDE / OS
64
+ .bsp/
65
+ .metals/
66
+ .vscode/
67
+ .DS_Store
68
+
69
+ # TypeScript build byproducts
70
+ ui/*.tsbuildinfo
71
+ ui/vite.config.d.ts
72
+ ui/vite.config.js
73
+
74
+ .claude/
75
+ distrib/
76
+ ducklake/
77
+ certs/
78
+
79
+ # DuckDB CLI + libduckdb self-installed by scripts/run-jar.sh, plus the
80
+ # spill scratch dirs the load-{tpch,tpcds}-dbgen.sh scripts anchor for
81
+ # SF>=10 dsdgen / dbgen runs. Both regenerate on demand from the network.
82
+ .duckdb/
83
+ .tmp/
84
+
85
+ datasets/
86
+ project/.bloop/
87
+ project/project/
88
+
89
+ # Local docker-compose overrides - may hold real PG_PASSWORD/ADMIN_PASSWORD/API_KEY.
90
+ # .env.example is the tracked template.
91
+ .env
92
+ .env.local
93
+ pgdata/
94
+ seaweedfs/
95
+ seaweedfs-config/
96
+
97
+ # Native quackwire JNI shim - local CMake build tree.
98
+ # Per-platform `libquackwire.{so,dylib}` binaries are published to Maven
99
+ # Central as classifier jars (`ai.starlake:libquackwire:<v>:<platform>`)
100
+ # by `.github/workflows/quackwire.yml`. The manager declares them as
101
+ # library dependencies, so the source tree never carries them.
102
+ native/quackwire/build*/
103
+
104
+ # CI-populated binaries staging dir for `sbt libquackwire/publishSigned`.
105
+ # Each per-platform classifier jar is built by zipping
106
+ # libquackwire/binaries/<platform>/libquackwire.<so|dylib>.
107
+ libquackwire/binaries/
108
+
109
+ # Defense in depth: src/main/resources/native/ is no longer how
110
+ # libquackwire reaches the assembly (the classifier deps in
111
+ # libraryDependencies handle that). Any binaries that land here would
112
+ # be stale relative to the published Maven coord; ignore them.
113
+ src/main/resources/native/
114
+
115
+ # Docs site (Docusaurus). The build, dependency, and generated-reference
116
+ # outputs are produced by `sbt genConfigDocs genOpenApi` + `npm run build`
117
+ # in CI, so they are never committed (kept in sync with the code, not the tree).
118
+ website/node_modules/
119
+ website/build/
120
+ website/.docusaurus/
121
+ website/static/openapi.yaml
122
+ website/docs/reference/configuration.md
123
+ src/main/scala/ai/starlake/quack/edge/.metals-scala-cli
124
+ screens
125
+ .venv-release/
qod_cli-0.3.7/PKG-INFO ADDED
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: qod-cli
3
+ Version: 0.3.7
4
+ Summary: Command-line client for quack-on-demand: admin REST plane and FlightSQL plane
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: adbc-driver-flightsql>=1.0
7
+ Requires-Dist: httpx>=0.27
8
+ Requires-Dist: platformdirs>=4.0
9
+ Requires-Dist: pyarrow>=16.0
10
+ Requires-Dist: pyreadline3>=3.4; sys_platform == 'win32'
11
+ Requires-Dist: rich>=13.7
12
+ Requires-Dist: tomli-w>=1.0
13
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
14
+ Requires-Dist: typer>=0.12
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest>=8.0; extra == 'dev'
17
+ Requires-Dist: respx>=0.21; extra == 'dev'
@@ -0,0 +1,297 @@
1
+ # qod - the quack-on-demand CLI
2
+
3
+ `qod` is a Python command-line client for quack-on-demand. It wraps the
4
+ manager's admin REST surface (tenants, databases, pools, nodes, RBAC, catalog
5
+ browsing, federation, audit, usage) and the FlightSQL query plane (`qod sql`,
6
+ one-shot or interactive REPL) behind one noun-verb command tree.
7
+
8
+ ## Install
9
+
10
+ From the repo root, against a local checkout:
11
+
12
+ ```bash
13
+ pip install ./cli
14
+ ```
15
+
16
+ For development (editable install, plus pytest/respx):
17
+
18
+ ```bash
19
+ pip install -e 'cli[dev]'
20
+ ```
21
+
22
+ Released versions are published to PyPI as `qod-cli`:
23
+
24
+ ```bash
25
+ pip install qod-cli
26
+ ```
27
+
28
+ Both routes install the `qod` console script.
29
+
30
+ ## Quick start
31
+
32
+ ```bash
33
+ qod login --url http://localhost:20900 --username admin
34
+ qod tenant list
35
+ qod sql "SELECT 1" --tenant acme --pool bi
36
+ ```
37
+
38
+ `qod login` prompts for the password (it is never a command-line flag, so it
39
+ never lands in shell history), mints a session, and stores the session token
40
+ plus the edge host/port/TLS settings in the active profile - one login
41
+ configures both the REST plane and the SQL plane.
42
+
43
+ ## Configuration
44
+
45
+ Settings resolve in this order for every field, highest priority first:
46
+
47
+ 1. Command-line flag (e.g. `--tenant`, `--url`)
48
+ 2. `QOD_*` environment variable
49
+ 3. Value saved in the active profile
50
+ 4. Built-in default
51
+
52
+ Profiles are named sections in a TOML file at the platform config
53
+ directory: `~/.config/qod/config.toml` on Linux, `~/Library/Application
54
+ Support/qod/config.toml` on macOS, `%APPDATA%\qod\config.toml` on Windows.
55
+ `QOD_CONFIG_FILE` overrides the full path. The file is written with mode
56
+ 0600 because it can hold a session token and an opt-in SQL password. Select
57
+ a profile with `--profile NAME` or `QOD_PROFILE`; the default profile is
58
+ named `default`.
59
+
60
+ | Setting | Env var | Default | Notes |
61
+ |---|---|---|---|
62
+ | `manager_url` | `QOD_MANAGER_URL` | `http://localhost:20900` | REST plane base URL. |
63
+ | `api_key` | `QOD_API_KEY` | `""` | Static API key; wins over `token` when both are set. |
64
+ | `token` | `QOD_TOKEN` | `""` | Session JWT, normally written by `qod login`, not set by hand. |
65
+ | `edge_host` | `QOD_HOST` | `localhost` | FlightSQL edge host. |
66
+ | `edge_port` | `QOD_PORT` | `31338` | FlightSQL edge port. |
67
+ | `edge_tls` | `QOD_TLS` | `true` | TLS to the edge. |
68
+ | `edge_tls_verify` | `QOD_TLS_VERIFY` | `false` | Verify the edge TLS cert; off by default since the edge ships a self-signed cert. |
69
+ | `tenant` | `QOD_TENANT` | `""` | Default tenant for `qod sql`. |
70
+ | `pool` | `QOD_POOL` | `""` | Default pool for `qod sql`. |
71
+ | `sql_user` | `QOD_USER` | `""` | FlightSQL username; set by `qod login`. |
72
+ | `sql_password` | `QOD_PASSWORD` | `""` | FlightSQL password; opt-in storage, see "SQL plane" below. |
73
+ | `superuser` | `QOD_SUPERUSER` | `false` | Send the superuser call header on `qod sql`. |
74
+
75
+ ## Command reference
76
+
77
+ Output columns below are one-line purposes; run `qod <noun> <verb> --help`
78
+ for the full flag list of any command.
79
+
80
+ ### Top level
81
+
82
+ | Command | Purpose |
83
+ |---|---|
84
+ | `qod login` | Mint a session, store it and the edge settings in the active profile. |
85
+ | `qod logout` | Revoke the current session token. |
86
+ | `qod whoami` | Verify the current session. |
87
+ | `qod health` | Liveness plus pool/node counts (open endpoint). |
88
+ | `qod usage` | Usage accounting rollups. |
89
+ | `qod sql` | Run SQL against the FlightSQL edge; one-shot or interactive REPL. |
90
+
91
+ ### auth - authentication mode discovery
92
+
93
+ | Verb | Purpose |
94
+ |---|---|
95
+ | `mode` | Show the auth mode (db or oidc) the manager expects. |
96
+
97
+ ### config - server-published configuration
98
+
99
+ | Verb | Purpose |
100
+ |---|---|
101
+ | `client` | Edge host/port/TLS for client bootstrapping (open endpoint). |
102
+ | `server` | Effective manager configuration. |
103
+
104
+ ### tenant - tenant CRUD
105
+
106
+ | Verb | Purpose |
107
+ |---|---|
108
+ | `list` | List tenants. |
109
+ | `create` | Create a tenant. |
110
+ | `delete` | Delete a tenant (must have no pools). |
111
+ | `set-disabled` | Enable or disable a tenant. |
112
+ | `set-auth` | Change a tenant's auth provider/config. |
113
+
114
+ ### database - tenant databases (DuckLake catalogs)
115
+
116
+ | Verb | Purpose |
117
+ |---|---|
118
+ | `list` | List databases for a tenant. |
119
+ | `create` | Create a tenant database. |
120
+ | `update` | Update a tenant database's metastore/object-store/init settings. |
121
+ | `delete` | Delete a tenant database. |
122
+
123
+ ### pool - pool lifecycle and pool-level access grants
124
+
125
+ | Verb | Purpose |
126
+ |---|---|
127
+ | `list` | List pools. |
128
+ | `create` | Create a pool. |
129
+ | `scale` | Change a pool's target size and role distribution. |
130
+ | `stop` | Stop a pool's nodes. |
131
+ | `delete` | Delete a pool. |
132
+ | `set-disabled` | Enable or disable a pool. |
133
+ | `set-resources` | Set CPU/memory requests for a pool's nodes. |
134
+ | `set-pod-template` | Set the Kubernetes pod template YAML for a pool. |
135
+ | `permission list` | List pool access grants. |
136
+ | `permission grant` | Grant pool access to a user or group. |
137
+ | `permission revoke` | Revoke a pool access grant. |
138
+
139
+ ### node - node lifecycle and statement inspection
140
+
141
+ | Verb | Purpose |
142
+ |---|---|
143
+ | `quarantine` | Take a node out of routing rotation. |
144
+ | `unquarantine` | Return a node to routing rotation. |
145
+ | `restart` | Restart a node. |
146
+ | `set-max-concurrent` | Set a node's max concurrent statement limit. |
147
+ | `statements` | Recent statement history, newest first. |
148
+ | `active-statements` | Currently running statements. |
149
+
150
+ ### statement - statement control
151
+
152
+ | Verb | Purpose |
153
+ |---|---|
154
+ | `kill` | Kill a running statement by id. |
155
+
156
+ ### user - user principals
157
+
158
+ | Verb | Purpose |
159
+ |---|---|
160
+ | `list` | List users. |
161
+ | `create` | Create a user (tenant-scoped, or `--superuser` for tenant-less). |
162
+ | `update` | Update a user's tenant, password, or role. |
163
+ | `delete` | Delete a user. |
164
+ | `effective` | Closure of roles, groups, table permissions, and pool grants. |
165
+
166
+ ### role - roles, table permissions, and row/column policies
167
+
168
+ | Verb | Purpose |
169
+ |---|---|
170
+ | `list` | List roles for a tenant. |
171
+ | `create` | Create a role. |
172
+ | `delete` | Delete a role. |
173
+ | `permission list` | List table permissions attached to a role. |
174
+ | `permission grant` | Grant a table permission to a role. |
175
+ | `permission revoke` | Revoke a table permission from a role. |
176
+ | `row-policy list` | List row-level security predicates on a role. |
177
+ | `row-policy create` | Add a row-level security predicate. |
178
+ | `row-policy update` | Update a row-level security predicate. |
179
+ | `row-policy delete` | Delete a row-level security predicate. |
180
+ | `column-policy list` | List column-level security policies on a role. |
181
+ | `column-policy create` | Add a column-level security policy. |
182
+ | `column-policy update` | Update a column-level security policy. |
183
+ | `column-policy delete` | Delete a column-level security policy. |
184
+
185
+ ### group - groups
186
+
187
+ | Verb | Purpose |
188
+ |---|---|
189
+ | `list` | List groups for a tenant. |
190
+ | `create` | Create a group. |
191
+ | `delete` | Delete a group. |
192
+
193
+ ### membership - RBAC membership edges
194
+
195
+ | Verb | Purpose |
196
+ |---|---|
197
+ | `user-role add` | Add a user to a role. |
198
+ | `user-role remove` | Remove a user from a role. |
199
+ | `user-group add` | Add a user to a group. |
200
+ | `user-group remove` | Remove a user from a group. |
201
+ | `group-role add` | Add a role to a group. |
202
+ | `group-role remove` | Remove a role from a group. |
203
+ | `group-role list` | List roles held by a group. |
204
+
205
+ ### catalog - DuckLake catalog browsing, time travel, and recovery
206
+
207
+ | Verb | Purpose |
208
+ |---|---|
209
+ | `schemas` | List schemas in a tenant database. |
210
+ | `tables` | List tables in a schema. |
211
+ | `describe` | Describe a table's columns, optionally as of a snapshot/tag/timestamp. |
212
+ | `snapshots` | List snapshots for a tenant database, optionally filtered to a table. |
213
+ | `history` | Snapshot history for a table (operation, author, time range filters). |
214
+ | `preview` | Preview table rows, optionally as of a snapshot/tag/timestamp. |
215
+ | `data-diff` | Row-level diff of a table between two snapshot selectors. |
216
+ | `schema-diff` | Schema diff of a table between two snapshot selectors. |
217
+ | `recoverable` | Dropped tables still recoverable via undrop. |
218
+ | `undrop` | Recover a dropped table, optionally under a different name. |
219
+
220
+ ### tag - snapshot tags (create, delete, protect)
221
+
222
+ | Verb | Purpose |
223
+ |---|---|
224
+ | `create` | Tag a snapshot, optionally marking it protected. |
225
+ | `delete` | Delete a tag. |
226
+ | `protect` | Change a tag's protected flag. |
227
+
228
+ ### maintenance - managed maintenance policies and runs
229
+
230
+ | Verb | Purpose |
231
+ |---|---|
232
+ | `policy` | Show the maintenance policy for a scope. |
233
+ | `policy-upsert` | Create or update a maintenance policy. |
234
+ | `policy-delete` | Delete a maintenance policy. |
235
+ | `run` | Trigger a maintenance run. |
236
+ | `runs` | List past maintenance runs. |
237
+
238
+ ### manifest - topology manifest export/import (YAML)
239
+
240
+ | Verb | Purpose |
241
+ |---|---|
242
+ | `export` | Export the whole control-plane topology as YAML. |
243
+ | `import` | Import a topology manifest (YAML) into the control plane. |
244
+
245
+ ### federation - federated sources per (tenant, tenant-db)
246
+
247
+ | Verb | Purpose |
248
+ |---|---|
249
+ | `list` | List federated sources. |
250
+ | `get` | Show one federated source. |
251
+ | `create` | Create a federated source. |
252
+ | `delete` | Delete a federated source. |
253
+ | `secret list` | List secrets referenced by a federated source's setup SQL. |
254
+ | `secret set` | Set a secret (inline value or external reference). |
255
+ | `secret delete` | Delete a secret. |
256
+
257
+ ### audit - audit log
258
+
259
+ | Verb | Purpose |
260
+ |---|---|
261
+ | `list` | List audit log entries with filters. |
262
+ | `actions` | Distinct audit action names, for filter values. |
263
+
264
+ ### history - statement history and trends
265
+
266
+ | Verb | Purpose |
267
+ |---|---|
268
+ | `statements` | List past statements with filters. |
269
+ | `trends` | Aggregate statement trends over time. |
270
+
271
+ ## Output
272
+
273
+ Human-readable Rich tables are the default. Pass `--json` (a top-level flag,
274
+ before the noun) for the raw JSON response body - the stable interface for
275
+ scripting. `qod sql` additionally supports `--csv`.
276
+
277
+ Exit codes: `0` success, `1` a server/API error (the server's error body is
278
+ shown verbatim, e.g. `tenant_forbidden`), `2` a usage error (missing or
279
+ invalid arguments, the Typer/Click default).
280
+
281
+ ## SQL plane
282
+
283
+ `qod sql "SELECT ..."` runs one statement and prints the result; `qod sql`
284
+ with no statement argument opens an interactive REPL (statements end with
285
+ `;`, `\q` or Ctrl-D exits, errors from one statement do not exit the REPL).
286
+
287
+ Credentials for the FlightSQL edge are resolved in this order:
288
+
289
+ 1. `QOD_PASSWORD` environment variable
290
+ 2. `sql_password` in the active profile (opt-in; not written by `qod login`)
291
+ 3. An interactive prompt, once per invocation
292
+
293
+ `sql_user` comes from `qod login` (or `QOD_USER`/`--tenant` overrides).
294
+ `--superuser` sends the superuser call header instead of a tenant/pool pair.
295
+ TLS to the edge defaults to on with certificate verification off, since the
296
+ edge ships a self-signed certificate; set `edge_tls_verify = true` or
297
+ `QOD_TLS_VERIFY=true` to opt into verification against a real certificate.
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "qod-cli"
7
+ dynamic = ["version"]
8
+ description = "Command-line client for quack-on-demand: admin REST plane and FlightSQL plane"
9
+ requires-python = ">=3.10"
10
+ dependencies = [
11
+ "typer>=0.12",
12
+ "rich>=13.7",
13
+ "httpx>=0.27",
14
+ "platformdirs>=4.0",
15
+ "tomli>=2.0; python_version < '3.11'",
16
+ "tomli-w>=1.0",
17
+ "adbc-driver-flightsql>=1.0",
18
+ "pyarrow>=16.0",
19
+ "pyreadline3>=3.4; sys_platform == 'win32'",
20
+ ]
21
+
22
+ [project.optional-dependencies]
23
+ dev = ["pytest>=8.0", "respx>=0.21"]
24
+
25
+ [project.scripts]
26
+ qod = "qod_cli.main:main"
27
+
28
+ [tool.hatch.version]
29
+ path = "src/qod_cli/__init__.py"
30
+
31
+ [tool.hatch.build.targets.wheel]
32
+ packages = ["src/qod_cli"]
@@ -0,0 +1 @@
1
+ __version__ = "0.3.7"
File without changes
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import typer
6
+
7
+ from ..output import render
8
+ from ..rest import ApiError, RestClient
9
+
10
+
11
+ def call(
12
+ ctx: typer.Context,
13
+ method: str,
14
+ path: str,
15
+ params: dict | None = None,
16
+ body: Any | None = None,
17
+ text: bool = False,
18
+ ) -> Any:
19
+ try:
20
+ data = RestClient(ctx.obj.settings).request(method, path, params=params, body=body, text=text)
21
+ except ApiError as exc:
22
+ typer.echo(f"error: {exc}", err=True)
23
+ raise typer.Exit(1)
24
+ render(data, ctx.obj.json_output)
25
+ return data
26
+
27
+
28
+ def kv_pairs(values: list[str] | None) -> dict:
29
+ out: dict = {}
30
+ for item in values or []:
31
+ if "=" not in item:
32
+ raise typer.BadParameter(f"expected KEY=VALUE, got {item!r}")
33
+ key, _, value = item.partition("=")
34
+ out[key] = value
35
+ return out
@@ -0,0 +1,67 @@
1
+ from urllib.parse import urlparse
2
+
3
+ import typer
4
+
5
+ from ..config import save_profile
6
+ from ..output import render
7
+ from ..rest import ApiError, RestClient
8
+ from ._run import call
9
+
10
+ app = typer.Typer(help="Authentication mode discovery.")
11
+
12
+
13
+ @app.command()
14
+ def mode(ctx: typer.Context):
15
+ """Show the auth mode (db or oidc) the manager expects."""
16
+ call(ctx, "GET", "/api/auth/mode")
17
+
18
+
19
+ def login(
20
+ ctx: typer.Context,
21
+ url: str = typer.Option(None, "--url", help="Manager URL; defaults to the profile value."),
22
+ username: str = typer.Option("admin", "--username"),
23
+ tenant: str = typer.Option(None, "--tenant", help="Tenant for tenant-scoped admins."),
24
+ ):
25
+ """Mint a session, store it and the edge settings in the active profile."""
26
+ settings = ctx.obj.settings
27
+ manager_url = url or settings.manager_url
28
+ password = typer.prompt("Password", hide_input=True)
29
+ settings.manager_url = manager_url
30
+ client = RestClient(settings)
31
+ body = {"username": username, "password": password}
32
+ if tenant is not None:
33
+ body["tenant"] = tenant
34
+ try:
35
+ login_resp = client.request("POST", "/api/auth/login", body=body)
36
+ edge = client.request("GET", "/api/config/client")
37
+ except ApiError as exc:
38
+ typer.echo(f"error: {exc}", err=True)
39
+ raise typer.Exit(1)
40
+ edge_host = edge.get("flightSqlHost", "")
41
+ if edge_host in ("", "0.0.0.0"):
42
+ edge_host = urlparse(manager_url).hostname or "localhost"
43
+ save_profile(
44
+ ctx.obj.profile,
45
+ {
46
+ "manager_url": manager_url,
47
+ "token": login_resp["token"],
48
+ "sql_user": username,
49
+ "edge_host": edge_host,
50
+ "edge_port": edge.get("flightSqlPort", 31338),
51
+ "edge_tls": edge.get("flightSqlTls", True),
52
+ },
53
+ )
54
+ render(
55
+ {"username": login_resp.get("username", username), "profile": ctx.obj.profile},
56
+ ctx.obj.json_output,
57
+ )
58
+
59
+
60
+ def logout(ctx: typer.Context):
61
+ """Revoke the current session token."""
62
+ call(ctx, "POST", "/api/auth/logout")
63
+
64
+
65
+ def whoami(ctx: typer.Context):
66
+ """Verify the current session."""
67
+ call(ctx, "GET", "/api/auth/whoami")