|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +This is a Go client library and CLI tool for InnoGames Serveradmin, a configuration management database system. The library provides both string-based and programmatic query interfaces with support for complex filters, and handles create/update/delete operations with change tracking. |
| 8 | + |
| 9 | +## Development Commands |
| 10 | + |
| 11 | +```bash |
| 12 | +# Build the CLI tool |
| 13 | +make build |
| 14 | + |
| 15 | +# Run all tests |
| 16 | +make test |
| 17 | + |
| 18 | +# Run tests with race detector |
| 19 | +make test-race |
| 20 | + |
| 21 | +# Generate test coverage report (creates coverage.html) |
| 22 | +make test-coverage |
| 23 | + |
| 24 | +# Run linter with auto-fix |
| 25 | +make linter |
| 26 | + |
| 27 | +# Run a specific test |
| 28 | +go test -run TestParseQuery ./adminapi |
| 29 | + |
| 30 | +# Run a specific benchmark |
| 31 | +go test -bench BenchmarkParseQuery_Simple ./adminapi |
| 32 | +``` |
| 33 | + |
| 34 | +## Architecture Overview |
| 35 | + |
| 36 | +### Package Structure |
| 37 | + |
| 38 | +**adminapi/** - Core library package containing all API client functionality: |
| 39 | +- `query.go` - Query building (`Query`, `FromQuery`, `NewQuery`) |
| 40 | +- `parse.go` - Query string parser converting "hostname=web*" to Filters |
| 41 | +- `filters.go` - Filter functions (Regexp, Any, All, Not, Empty) |
| 42 | +- `server_object.go` - ServerObject with change tracking and state management |
| 43 | +- `commit.go` - Commit/rollback operations for objects |
| 44 | +- `transport.go` - HTTP client with SSH and token authentication |
| 45 | +- `config.go` - Configuration loading from environment variables |
| 46 | + |
| 47 | +**examples/** - Standalone example programs demonstrating library usage |
| 48 | + |
| 49 | +### Core Data Flow |
| 50 | + |
| 51 | +1. **Query Creation** → 2. **Fetch** → 3. **Modify** → 4. **Commit** |
| 52 | + |
| 53 | +``` |
| 54 | +FromQuery("hostname=web*") or NewQuery(Filters{...}) |
| 55 | + ↓ |
| 56 | +Query.All() / Query.One() [transport.go sends HTTP request] |
| 57 | + ↓ |
| 58 | +ServerObjects with attributes loaded |
| 59 | + ↓ |
| 60 | +ServerObject.Set(key, value) [tracks oldValues internally] |
| 61 | + ↓ |
| 62 | +ServerObject.Commit() or ServerObjects.Commit() [sends delta to API] |
| 63 | +``` |
| 64 | + |
| 65 | +### Key Architectural Patterns |
| 66 | + |
| 67 | +**Change Tracking**: `ServerObject` maintains an `oldValues` map that records original attribute values on first modification. The `serializeChanges()` method computes deltas, sending only modified fields to the API. This mimics the Python client's behavior. |
| 68 | + |
| 69 | +**Multi-attributes**: Slice-valued attributes use set semantics during commit, computing `add` and `remove` sets rather than replacing the entire slice. See `sliceDiff()` in `server_object.go`. |
| 70 | + |
| 71 | +**State Machine**: ServerObject has four states returned by `CommitState()`: |
| 72 | +- `"created"` - object_id is nil (new object not yet committed) |
| 73 | +- `"deleted"` - marked for deletion |
| 74 | +- `"changed"` - has modifications in oldValues |
| 75 | +- `"consistent"` - no pending changes |
| 76 | + |
| 77 | +**Authentication**: The client supports two auth methods (checked in order): |
| 78 | +1. SSH key signing (via `SERVERADMIN_KEY_PATH` or `SSH_AUTH_SOCK` agent) |
| 79 | +2. Security token (via `SERVERADMIN_TOKEN` with HMAC-SHA1 signing) |
| 80 | + |
| 81 | +Configuration is loaded once via `sync.OnceValues` in `config.go`. |
| 82 | + |
| 83 | +**Filter System**: Two ways to build queries: |
| 84 | +1. String-based: `FromQuery("hostname=regexp(web.*) environment=production")` |
| 85 | +2. Programmatic: `NewQuery(Filters{"hostname": Regexp("web.*")})` |
| 86 | + |
| 87 | +The parser (`parse.go`) handles nested parentheses and converts function names case-insensitively (e.g., "ReGEXP" → "Regexp"). |
| 88 | + |
| 89 | +## Important Implementation Details |
| 90 | + |
| 91 | +### Query Interface |
| 92 | + |
| 93 | +Both `FromQuery` and `NewQuery` return a `Query` struct. Key methods: |
| 94 | +- `SetAttributes([]string)` or `SetAttributes(...string)` - specify which attributes to fetch |
| 95 | +- `AddFilter(key, value)` - add filters incrementally to existing Query |
| 96 | +- `All()` → `ServerObjects` - fetch all matching objects |
| 97 | +- `One()` → `*ServerObject` - fetch exactly one (errors if 0 or >1 results) |
| 98 | + |
| 99 | +### ServerObject Methods |
| 100 | + |
| 101 | +- `Get(attr)` returns `any` (auto-converts JSON float64 to int) |
| 102 | +- `GetString(attr)` returns `string` |
| 103 | +- `Set(key, value)` tracks changes; returns error if attribute doesn't exist |
| 104 | +- `Delete()` marks for deletion (doesn't actually delete until commit) |
| 105 | +- `Rollback()` discards all local changes |
| 106 | +- `Commit()` sends changes to API and clears oldValues on success |
| 107 | + |
| 108 | +### Filter Functions |
| 109 | + |
| 110 | +Implemented in `filters.go`: |
| 111 | +- `Regexp(pattern string)` - regex matching |
| 112 | +- `Not(value)` - negation (works with values or other filters) |
| 113 | +- `Any(values...)` - OR semantics (match any of) |
| 114 | +- `All(values...)` - AND semantics (match all of) |
| 115 | +- `Empty()` - checks for empty/nil values |
| 116 | + |
| 117 | +These can be nested: `Not(Any(Regexp("^test.*"), Regexp("^dev.*")))` |
| 118 | + |
| 119 | +Additional filters exist in the parser's `allFilters` map (GreaterThan, LessThan, etc.) but lack Go helper functions. These can still be used via `FromQuery` string syntax. |
| 120 | + |
| 121 | +### Testing Patterns |
| 122 | + |
| 123 | +Tests use testify/assert and testify/require. The codebase has table-driven tests (see `parse_test.go`). |
| 124 | + |
| 125 | +When writing tests: |
| 126 | +- Use `require.NoError` for setup that must succeed |
| 127 | +- Use `assert.Error` for expected failures with descriptive messages |
| 128 | +- Table-driven tests should have descriptive `name` fields |
| 129 | +- Go 1.25+ uses `b.Loop()` instead of `for i := 0; i < b.N; i++` in benchmarks |
| 130 | + |
| 131 | +### Linting |
| 132 | + |
| 133 | +The project uses golangci-lint with an extensive linter configuration (`.golangci.yml`). Key points: |
| 134 | +- Formatters gci and gofumpt are enabled (imports grouped, strict formatting) |
| 135 | +- Some linters (errcheck, perfsprint) are relaxed for `_test.go` files |
| 136 | +- Examples directory has relaxed rules |
| 137 | +- Run `make linter` to auto-fix issues before committing |
| 138 | +- SHA1 usage is intentional (required by protocol) - use `//nolint:gosec` comments |
| 139 | + |
| 140 | +## Configuration Requirements |
| 141 | + |
| 142 | +The client requires these environment variables: |
| 143 | + |
| 144 | +```bash |
| 145 | +# Required |
| 146 | +export SERVERADMIN_BASE_URL="https://serveradmin.example.com" |
| 147 | + |
| 148 | +# One of these auth methods: |
| 149 | +export SERVERADMIN_TOKEN="your-token" # Token-based auth |
| 150 | +# OR |
| 151 | +export SERVERADMIN_KEY_PATH="/path/to/key" # SSH key file |
| 152 | +# OR |
| 153 | +export SSH_AUTH_SOCK="/path/to/ssh-agent.sock" # SSH agent (auto-detected) |
| 154 | +``` |
| 155 | + |
| 156 | +The client fails fast if `SERVERADMIN_BASE_URL` or auth credentials are missing. |
| 157 | + |
| 158 | +## Examples Directory |
| 159 | + |
| 160 | +The `examples/` directory contains standalone programs demonstrating: |
| 161 | +- `update_example.go` - Single/batch updates, create, delete, rollback |
| 162 | +- `query_example.go` - Query patterns (string vs programmatic, simple vs nested filters) |
| 163 | + |
| 164 | +These use a shorter import alias pattern: `import api "github.com/innogames/serveradmin-go-client/adminapi"` |
| 165 | + |
| 166 | +Examples are excluded from strict linting rules and can ignore errors for brevity. |
| 167 | + |
| 168 | +## Version Compatibility |
| 169 | + |
| 170 | +- Requires Go 1.24+ (per README, using latest Go 1.25 features like `b.Loop()`) |
| 171 | +- API version is hardcoded in `config.go` as `version = "4.9.0"` |
| 172 | +- The client maintains compatibility with the Python Serveradmin client's behavior (change tracking, JSON comparison logic) |
0 commit comments