Skip to content

Commit 616ef60

Browse files
committed
parse_query: strip query
1 parent 26591fc commit 616ef60

File tree

3 files changed

+430
-2
lines changed

3 files changed

+430
-2
lines changed

adminapi/CLAUDE.md

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
## Overview
2+
3+
`adminapi` is a Python library for interacting with Serveradmin, a system for querying and managing server attributes. It provides both a Python API and a CLI tool for server management operations.
4+
5+
Documentation: https://serveradmin.readthedocs.io/en/latest/python-api.html
6+
7+
## Development Commands
8+
9+
### Setup
10+
```bash
11+
# Install from parent directory, like
12+
cd ~/projects/serveradmin
13+
pip install -e .
14+
```
15+
16+
### Testing
17+
```bash
18+
# Run all tests
19+
python -m unittest discover adminapi/tests
20+
21+
# Run specific test file
22+
python -m unittest adminapi.tests.test_cli
23+
python -m unittest adminapi.tests.test_dataset
24+
25+
# Run single test
26+
python -m unittest adminapi.tests.test_cli.TestCommandlineInterface.test_one_argument
27+
```
28+
29+
### CLI Usage
30+
```bash
31+
# Query servers
32+
adminapi 'hostname=web01'
33+
adminapi 'project=adminapi' --attr hostname --attr state
34+
35+
# Update servers
36+
adminapi 'hostname=web01' --update state=maintenance
37+
```
38+
39+
### Python API Examples
40+
41+
**Query and modify servers**
42+
```python
43+
from adminapi.dataset import Query
44+
45+
# Query servers matching criteria
46+
servers = Query({
47+
'servertype': 'vm',
48+
'project': 'test_project',
49+
'state': 'offline',
50+
})
51+
52+
# Modify server attributes
53+
for server in servers:
54+
print(f"Bringing {server['hostname']} online")
55+
server['state'] = 'online'
56+
57+
# Commit changes to persist them
58+
servers.commit()
59+
```
60+
61+
**Query with restricted attributes**
62+
```python
63+
from adminapi.dataset import Query
64+
65+
# Only fetch specific attributes to reduce payload
66+
servers = Query(
67+
{'project': 'test_project', 'state': 'maintenance'},
68+
restrict=['hostname', 'state']
69+
)
70+
71+
for server in servers:
72+
print(f"{server['hostname']}: {server['state']}")
73+
```
74+
75+
**Get single server**
76+
```python
77+
from adminapi.dataset import Query
78+
79+
# Use get() when expecting exactly one result
80+
server = Query({'hostname': 'web01'}).get()
81+
server['state'] = 'maintenance'
82+
server.commit()
83+
```
84+
85+
## Architecture
86+
87+
### Core Components
88+
89+
**Query System** (`dataset.py`)
90+
- `Query`: Main class for filtering and fetching server objects from Serveradmin
91+
- `BaseQuery`: Base class with common query operations (filtering, ordering, committing)
92+
- Queries are lazy-loaded and cached until explicitly fetched
93+
- Always ensures `object_id` is included in restrict lists for correlation during commits
94+
95+
**Data Objects** (`dataset.py`)
96+
- `DatasetObject`: Dict-like wrapper for server data with change tracking
97+
- `MultiAttr`: Set-like wrapper for multi-value attributes with automatic change propagation
98+
- Track state: `created`, `changed`, `deleted`, or `consistent`
99+
- Old values stored in `old_values` dict for rollback/commit operations
100+
101+
**Filters** (`filters.py`)
102+
- `BaseFilter`: Exact match filter (default)
103+
- Comparison: `GreaterThan`, `LessThan`, `GreaterThanOrEquals`, `LessThanOrEquals`
104+
- Pattern: `Regexp`, `StartsWith`, `Contains`, `ContainedBy`, `Overlaps`
105+
- Logical: `Any` (OR), `All` (AND), `Not`
106+
- Special: `Empty` (null check), `ContainedOnlyBy` (IP network containment)
107+
108+
**Request Layer** (`request.py`)
109+
- Handles HTTP communication with Serveradmin server
110+
- Authentication via SSH keys (paramiko), SSH agent, or auth tokens (HMAC-SHA1)
111+
- Automatic retry logic (3 attempts, 5s interval) for network failures
112+
- Gzip compression support for responses
113+
- Environment variables: `SERVERADMIN_BASE_URL`, `SERVERADMIN_KEY_PATH`, `SERVERADMIN_TOKEN`
114+
115+
**Query Parser** (`parse.py`)
116+
- Converts string queries to filter dictionaries
117+
- Supports function syntax: `attribute=Function(value)`
118+
- Regex detection: triggers `Regexp` filter for patterns with `.*`, `.+`, `[`, `]`, etc.
119+
- Hostname shorthand: first token without `=` treated as hostname filter
120+
121+
**API Calls** (`api.py`)
122+
- `FunctionGroup`: Dynamic proxy for calling Serveradmin API functions
123+
- Pattern: `FunctionGroup('group_name').function_name(*args, **kwargs)`
124+
- Example: `api.get('nagios').commit('push', 'user', project='foo')`
125+
126+
### Data Flow
127+
128+
1. **Query Construction**: Create `Query` with filters dict, restrict list, order_by
129+
2. **Lazy Fetch**: Results fetched on first iteration/access via `_get_results()`
130+
3. **Modification**: Change `DatasetObject` attributes, tracked in `old_values`
131+
4. **Commit**: Build commit object with created/changed/deleted arrays, send to server
132+
5. **Confirm**: Clear `old_values`, update object state after successful commit
133+
134+
### Change Tracking
135+
136+
- **Single attributes**: Save original value on first modification to `old_values`
137+
- **Multi attributes**: `MultiAttr` operations create new sets, trigger parent `__setitem__`
138+
- **Validation**: Type checking against existing attribute types (bool, multi, datatype)
139+
- **Serialization**: Changed objects serialize with action (`update` or `multi`) and old/new values
140+
141+
### Authentication Flow
142+
143+
1. Check for `SERVERADMIN_KEY_PATH` → load private key file
144+
2. Else check for `SERVERADMIN_TOKEN` → use HMAC authentication
145+
3. Else try SSH agent (`paramiko.agent.Agent`)
146+
4. Sign timestamp + request body, include signature in `X-Signatures` header
147+
5. Server validates signature using stored public key
148+
149+
## Important Patterns
150+
151+
- Always use `commit()` after modifying objects to persist changes
152+
- Use `restrict` parameter to limit fetched attributes (reduces payload size)
153+
- `get()` expects exactly one result; use for single-server queries
154+
- Multi-value attributes must be modified via `MultiAttr` methods or reassignment
155+
- Query filters are immutable; changes create new filter instances
156+
- The API is marked as draft and may change in future versions

adminapi/parse.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010

1111

1212
def parse_query(term, hostname=None): # NOQA: C901
13-
# Ignore newlines to allow queries across multiple lines
14-
term = term.replace('\n', '')
13+
# Replace newlines with spaces to allow queries across multiple lines
14+
term = term.replace('\n', ' ').strip()
1515

1616
parsed_args = parse_function_string(term, strict=True)
1717
if not parsed_args:

0 commit comments

Comments
 (0)