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.
- qod_cli-0.3.7/.gitignore +125 -0
- qod_cli-0.3.7/PKG-INFO +17 -0
- qod_cli-0.3.7/README.md +297 -0
- qod_cli-0.3.7/pyproject.toml +32 -0
- qod_cli-0.3.7/src/qod_cli/__init__.py +1 -0
- qod_cli-0.3.7/src/qod_cli/commands/__init__.py +0 -0
- qod_cli-0.3.7/src/qod_cli/commands/_run.py +35 -0
- qod_cli-0.3.7/src/qod_cli/commands/auth.py +67 -0
- qod_cli-0.3.7/src/qod_cli/commands/catalog.py +173 -0
- qod_cli-0.3.7/src/qod_cli/commands/config_cmd.py +17 -0
- qod_cli-0.3.7/src/qod_cli/commands/database.py +69 -0
- qod_cli-0.3.7/src/qod_cli/commands/federation.py +82 -0
- qod_cli-0.3.7/src/qod_cli/commands/group.py +28 -0
- qod_cli-0.3.7/src/qod_cli/commands/maintenance.py +81 -0
- qod_cli-0.3.7/src/qod_cli/commands/manifest.py +34 -0
- qod_cli-0.3.7/src/qod_cli/commands/membership.py +50 -0
- qod_cli-0.3.7/src/qod_cli/commands/node.py +58 -0
- qod_cli-0.3.7/src/qod_cli/commands/pool.py +137 -0
- qod_cli-0.3.7/src/qod_cli/commands/role.py +150 -0
- qod_cli-0.3.7/src/qod_cli/commands/sql_cmd.py +86 -0
- qod_cli-0.3.7/src/qod_cli/commands/tag.py +47 -0
- qod_cli-0.3.7/src/qod_cli/commands/telemetry.py +95 -0
- qod_cli-0.3.7/src/qod_cli/commands/tenant.py +61 -0
- qod_cli-0.3.7/src/qod_cli/commands/user.py +62 -0
- qod_cli-0.3.7/src/qod_cli/config.py +107 -0
- qod_cli-0.3.7/src/qod_cli/main.py +96 -0
- qod_cli-0.3.7/src/qod_cli/output.py +66 -0
- qod_cli-0.3.7/src/qod_cli/rest.py +73 -0
- qod_cli-0.3.7/src/qod_cli/sql.py +80 -0
- qod_cli-0.3.7/tests/__init__.py +0 -0
- qod_cli-0.3.7/tests/conftest.py +18 -0
- qod_cli-0.3.7/tests/test_auth.py +68 -0
- qod_cli-0.3.7/tests/test_catalog_selectors.py +10 -0
- qod_cli-0.3.7/tests/test_commands_table.py +412 -0
- qod_cli-0.3.7/tests/test_config.py +60 -0
- qod_cli-0.3.7/tests/test_integration.py +38 -0
- qod_cli-0.3.7/tests/test_main.py +12 -0
- qod_cli-0.3.7/tests/test_manifest_io.py +32 -0
- qod_cli-0.3.7/tests/test_output.py +40 -0
- qod_cli-0.3.7/tests/test_repl.py +47 -0
- qod_cli-0.3.7/tests/test_rest.py +106 -0
- qod_cli-0.3.7/tests/test_sql_render.py +32 -0
- qod_cli-0.3.7/tests/test_sql_usage.py +7 -0
- qod_cli-0.3.7/tests/test_user_create_prompt.py +27 -0
- qod_cli-0.3.7/tests/test_wiring.py +56 -0
qod_cli-0.3.7/.gitignore
ADDED
|
@@ -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'
|
qod_cli-0.3.7/README.md
ADDED
|
@@ -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")
|