360 lines
13 KiB
Markdown
360 lines
13 KiB
Markdown
# MiniDiscovery API Documentation
|
|
|
|
This document describes the HTTP API endpoints provided by MiniDiscovery.
|
|
|
|
## Authentication
|
|
|
|
Most API endpoints require authentication using an API token. Tokens must be passed in the `X-API-Token` HTTP header.
|
|
|
|
```http
|
|
GET /v1/catalog/services HTTP/1.1
|
|
Host: your-minidiscovery-host:8500
|
|
X-API-Token: your_secret_api_token
|
|
```
|
|
|
|
Tokens have associated permissions:
|
|
|
|
* **`read`**: Allows reading service information (catalog, health).
|
|
* **`write`**: Allows registering and deregistering services.
|
|
* **`admin`**: Allows all `read` and `write` operations, plus managing ACL tokens.
|
|
|
|
The initial admin token is set using the `MINIDISCOVERY_ADMIN_TOKEN` environment variable on the *first* run when the database is empty. Subsequent tokens are managed via the API.
|
|
|
|
## Data Models
|
|
|
|
### `ServiceInstance`
|
|
|
|
This model represents a single instance of a registered service.
|
|
|
|
```json
|
|
{
|
|
"id": "web-instance-1",
|
|
"name": "web",
|
|
"address": "192.168.1.100",
|
|
"port": 80,
|
|
"tags": ["frontend", "nginx"],
|
|
"metadata": {
|
|
"version": "1.2.3",
|
|
"region": "us-east-1"
|
|
},
|
|
"health": "passing",
|
|
"last_updated": 1678886400.123456
|
|
}
|
|
```
|
|
|
|
* `id` (string, required): Unique identifier for this specific instance.
|
|
* `name` (string, required): Logical name of the service (e.g., 'web', 'api', 'db').
|
|
* `address` (string, required): IP address or resolvable hostname where the service listens.
|
|
* `port` (integer, required): Port number (1-65535).
|
|
* `tags` (array of strings, optional): List of tags for filtering/grouping. Defaults to `[]`.
|
|
* `metadata` (object, optional): Key-value string pairs for arbitrary metadata. Defaults to `{}`.
|
|
* `health` (string, optional): Current health status ('passing', 'failing', 'unknown'). Defaults to 'passing' on registration. Automatically updated by the health checker.
|
|
* `last_updated` (float, internal): Unix timestamp of the last update (set automatically).
|
|
|
|
## Endpoints
|
|
|
|
### Agent Endpoints
|
|
|
|
These endpoints interact with the local agent state (registering/deregistering services).
|
|
|
|
#### Register Service
|
|
|
|
* **`POST /v1/agent/service/register`**
|
|
* **Description:** Registers a new service instance or updates an existing one with the same `id`. This operation is idempotent based on the service `id`.
|
|
* **Authentication:** Requires `write` permission.
|
|
* **Request Body:** `ServiceInstance` JSON object. The `health` and `last_updated` fields in the request body are ignored; health is managed internally, and `last_updated` is set automatically.
|
|
* **Success Response:** `200 OK`
|
|
```json
|
|
{
|
|
"status": "registered",
|
|
"service_id": "web-instance-1"
|
|
}
|
|
```
|
|
* **Error Responses:**
|
|
* `400 Bad Request`: Invalid request body format.
|
|
* `401 Unauthorized`: Missing `X-API-Token`.
|
|
* `403 Forbidden`: Invalid token or insufficient permissions.
|
|
* `500 Internal Server Error`: Database error during registration.
|
|
* **`curl` Example:**
|
|
```bash
|
|
curl -X POST http://localhost:8500/v1/agent/service/register \
|
|
-H "X-API-Token: your_write_token" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"id": "api-instance-01",
|
|
"name": "api",
|
|
"address": "10.0.1.5",
|
|
"port": 8080,
|
|
"tags": ["backend", "v2"],
|
|
"metadata": {"git_sha": "a1b2c3d"}
|
|
}'
|
|
```
|
|
|
|
#### Deregister Service
|
|
|
|
* **`PUT /v1/agent/service/deregister/{service_id}`**
|
|
*(Note: Consul uses PUT here, although DELETE might feel more intuitive)*
|
|
* **Description:** Removes a service instance by its ID.
|
|
* **Authentication:** Requires `write` permission.
|
|
* **Path Parameters:**
|
|
* `service_id` (string): The unique ID of the service instance to deregister.
|
|
* **Success Response:** `200 OK`
|
|
```json
|
|
{
|
|
"status": "deregistered",
|
|
"service_id": "api-instance-01"
|
|
}
|
|
```
|
|
* **Error Responses:**
|
|
* `401 Unauthorized`: Missing `X-API-Token`.
|
|
* `403 Forbidden`: Invalid token or insufficient permissions.
|
|
* `404 Not Found`: Service with the given `service_id` does not exist.
|
|
* **`curl` Example:**
|
|
```bash
|
|
curl -X PUT http://localhost:8500/v1/agent/service/deregister/api-instance-01 \
|
|
-H "X-API-Token: your_write_token"
|
|
```
|
|
|
|
### Catalog Endpoints
|
|
|
|
These endpoints provide information about registered services across the system.
|
|
|
|
#### List Services
|
|
|
|
* **`GET /v1/catalog/services`**
|
|
* **Description:** Returns a map of all registered service names to a list of unique tags associated with instances of that service.
|
|
* **Authentication:** Requires `read` permission.
|
|
* **Success Response:** `200 OK`
|
|
```json
|
|
{
|
|
"web": ["frontend", "nginx"],
|
|
"api": ["backend", "v2"],
|
|
"db": []
|
|
}
|
|
```
|
|
* **Error Responses:**
|
|
* `401 Unauthorized`: Missing `X-API-Token`.
|
|
* `403 Forbidden`: Invalid token or insufficient permissions.
|
|
* **`curl` Example:**
|
|
```bash
|
|
curl http://localhost:8500/v1/catalog/services \
|
|
-H "X-API-Token: your_read_token"
|
|
```
|
|
|
|
#### List Service Instances
|
|
|
|
* **`GET /v1/catalog/service/{service_name}`**
|
|
* **Description:** Returns a list of all registered instances for a given service name.
|
|
* **Authentication:** Requires `read` permission.
|
|
* **Path Parameters:**
|
|
* `service_name` (string): The name of the service (e.g., 'web', 'api').
|
|
* **Query Parameters:**
|
|
* `tag` (string, optional): Filter instances by tag. Only instances with this tag will be returned.
|
|
* **Success Response:** `200 OK` (Returns an array of `ServiceInstance` objects)
|
|
```json
|
|
[
|
|
{
|
|
"id": "web-instance-1",
|
|
"name": "web",
|
|
"address": "192.168.1.100",
|
|
"port": 80,
|
|
"tags": ["frontend", "nginx"],
|
|
"metadata": {"version": "1.2.3"},
|
|
"health": "passing",
|
|
"last_updated": 1678886400.123
|
|
},
|
|
{
|
|
"id": "web-instance-2",
|
|
"name": "web",
|
|
"address": "192.168.1.101",
|
|
"port": 80,
|
|
"tags": ["frontend"],
|
|
"metadata": {"version": "1.2.4"},
|
|
"health": "failing",
|
|
"last_updated": 1678886405.456
|
|
}
|
|
]
|
|
```
|
|
* Returns `[]` if the service name is not found.
|
|
* **Error Responses:**
|
|
* `401 Unauthorized`: Missing `X-API-Token`.
|
|
* `403 Forbidden`: Invalid token or insufficient permissions.
|
|
* **`curl` Examples:**
|
|
```bash
|
|
# Get all 'web' instances
|
|
curl http://localhost:8500/v1/catalog/service/web \
|
|
-H "X-API-Token: your_read_token"
|
|
|
|
# Get 'web' instances tagged with 'nginx'
|
|
curl http://localhost:8500/v1/catalog/service/web?tag=nginx \
|
|
-H "X-API-Token: your_read_token"
|
|
```
|
|
|
|
### Health Endpoints
|
|
|
|
These endpoints provide service discovery filtered by health status.
|
|
|
|
#### List Healthy Service Instances
|
|
|
|
* **`GET /v1/health/service/{service_name}`**
|
|
* **Description:** Returns a list of service instances, similar to the catalog endpoint, but allows filtering by health status.
|
|
* **Authentication:** Requires `read` permission.
|
|
* **Path Parameters:**
|
|
* `service_name` (string): The name of the service.
|
|
* **Query Parameters:**
|
|
* `tag` (string, optional): Filter instances by tag.
|
|
* `passing` (boolean, optional): If `true`, only return instances with a `passing` health status. Defaults to `false` (returns all instances regardless of health).
|
|
* **Success Response:** `200 OK` (Returns an array of `ServiceInstance` objects)
|
|
* Response format is identical to `/v1/catalog/service/{service_name}` but filtered according to query parameters.
|
|
* **Error Responses:**
|
|
* `401 Unauthorized`: Missing `X-API-Token`.
|
|
* `403 Forbidden`: Invalid token or insufficient permissions.
|
|
* **`curl` Examples:**
|
|
```bash
|
|
# Get all 'api' instances (healthy or not)
|
|
curl http://localhost:8500/v1/health/service/api \
|
|
-H "X-API-Token: your_read_token"
|
|
|
|
# Get only healthy ('passing') 'api' instances
|
|
curl http://localhost:8500/v1/health/service/api?passing=true \
|
|
-H "X-API-Token: your_read_token"
|
|
|
|
# Get only healthy 'api' instances tagged 'v2'
|
|
curl 'http://localhost:8500/v1/health/service/api?passing=true&tag=v2' \
|
|
-H "X-API-Token: your_read_token"
|
|
```
|
|
|
|
### ACL Token Endpoints
|
|
|
|
These endpoints manage API access tokens.
|
|
|
|
#### Create Token
|
|
|
|
* **`POST /v1/acl/token`**
|
|
* **Description:** Creates a new API token with specified permissions.
|
|
* **Authentication:** Requires `admin` permission.
|
|
* **Request Body:**
|
|
```json
|
|
{
|
|
"name": "my-app-token",
|
|
"permissions": ["read", "write"]
|
|
}
|
|
```
|
|
* `name` (string, required): A unique descriptive name for the token.
|
|
* `permissions` (array of strings, optional): List of permissions (`read`, `write`, `admin`). Defaults to `["read", "write"]`.
|
|
* **Success Response:** `201 Created`
|
|
```json
|
|
{
|
|
"token": "YOUR_NEW_SECRET_TOKEN_VALUE",
|
|
"name": "my-app-token",
|
|
"permissions": ["read", "write"]
|
|
}
|
|
```
|
|
* **IMPORTANT:** The `token` value is the actual secret token. It is **only shown once** in this response. Store it securely immediately.
|
|
* **Error Responses:**
|
|
* `400 Bad Request`: Invalid request body or token name already exists.
|
|
* `401 Unauthorized`: Missing `X-API-Token`.
|
|
* `403 Forbidden`: Invalid token or insufficient permissions (not admin).
|
|
* `500 Internal Server Error`: Database error during token creation.
|
|
* **`curl` Example:**
|
|
```bash
|
|
curl -X POST http://localhost:8500/v1/acl/token \
|
|
-H "X-API-Token: your_admin_token" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"name": "read-only-dashboard",
|
|
"permissions": ["read"]
|
|
}'
|
|
```
|
|
|
|
#### Revoke Token
|
|
|
|
* **`DELETE /v1/acl/token/{token_to_revoke}`**
|
|
* **Description:** Revokes (deletes) an existing API token.
|
|
* **Authentication:** Requires `admin` permission.
|
|
* **Path Parameters:**
|
|
* `token_to_revoke` (string): The **actual secret token value** of the token you want to revoke.
|
|
* **Success Response:** `200 OK`
|
|
```json
|
|
{
|
|
"status": "revoked",
|
|
"token_info": "Token revoked successfully"
|
|
}
|
|
```
|
|
* **Error Responses:**
|
|
* `401 Unauthorized`: Missing `X-API-Token`.
|
|
* `403 Forbidden`: Invalid token or insufficient permissions (not admin).
|
|
* `404 Not Found`: The token specified in the path does not exist or was already revoked.
|
|
* **`curl` Example:**
|
|
```bash
|
|
# Replace YOUR_OLD_SECRET_TOKEN_VALUE with the token to be deleted
|
|
curl -X DELETE http://localhost:8500/v1/acl/token/YOUR_OLD_SECRET_TOKEN_VALUE \
|
|
-H "X-API-Token: your_admin_token"
|
|
```
|
|
|
|
### DNS over HTTPS (DoH)
|
|
|
|
* **`GET /dns-query`**
|
|
* **Description:** Provides a simplified DNS-over-HTTPS interface (RFC 8484 GET format) for querying service addresses and SRV records. Primarily intended for simple DNS clients or scripts. **Note:** Requires service names to end with the configured DNS suffix (default: `.laiska.local`).
|
|
* **Authentication:** None (typically).
|
|
* **Query Parameters:**
|
|
* `name` (string, required): The DNS query name (e.g., `web.laiska.local`, `_frontend._tcp.web.laiska.local`).
|
|
* `type` (string, optional): The DNS record type (e.g., `A`, `SRV`, `TXT`). Defaults to `A`. Case-insensitive.
|
|
* **Success Response:** `200 OK` (JSON object following RFC 8484 structure)
|
|
* **Example (A query for `web.laiska.local`):**
|
|
```json
|
|
{
|
|
"Status": 0, "TC": false, "RD": true, "RA": false, "AD": false, "CD": false,
|
|
"Question": [{"name": "web.laiska.local.", "type": 1}],
|
|
"Answer": [
|
|
{"name": "web.laiska.local.", "type": 1, "TTL": 60, "data": "192.168.1.100"},
|
|
{"name": "web.laiska.local.", "type": 1, "TTL": 60, "data": "192.168.1.101"}
|
|
]
|
|
}
|
|
```
|
|
* **Example (SRV query for `_frontend._tcp.web.laiska.local`):**
|
|
```json
|
|
{
|
|
"Status": 0, "TC": false, "RD": true, "RA": false, "AD": false, "CD": false,
|
|
"Question": [{"name": "_frontend._tcp.web.laiska.local.", "type": 33}],
|
|
"Answer": [
|
|
{"name": "_frontend._tcp.web.laiska.local.", "type": 33, "TTL": 60, "data": "0 10 80 web-instance-1.laiska.local."},
|
|
{"name": "_frontend._tcp.web.laiska.local.", "type": 33, "TTL": 60, "data": "0 10 80 web-instance-2.laiska.local."}
|
|
],
|
|
"Additional": [ // Note: MiniDiscovery doesn't currently populate Additional well for DoH
|
|
// Ideally A records for targets would be here
|
|
]
|
|
}
|
|
```
|
|
* **Error/NXDOMAIN Response:** Returns JSON with `"Status"` other than `0` (e.g., `2` for NXDOMAIN, `4` for Not Implemented type).
|
|
* **`curl` Example:**
|
|
```bash
|
|
# Query for A records
|
|
curl "http://localhost:8500/dns-query?name=web.laiska.local&type=A"
|
|
|
|
# Query for SRV records
|
|
curl "http://localhost:8500/dns-query?name=_frontend._tcp.web.laiska.local&type=SRV"
|
|
```
|
|
|
|
### Other Endpoints
|
|
|
|
#### Root
|
|
|
|
* **`GET /`**
|
|
* **Description:** Provides a basic entry point. Redirects browsers to `/status`. API clients receive a simple JSON message.
|
|
* **Authentication:** None.
|
|
* **Response (Browser):** HTTP 307 Redirect to `/status`.
|
|
* **Response (API Client):** `200 OK`
|
|
```json
|
|
{
|
|
"message": "MiniDiscovery API Root. See /status for HTML view or /docs for API documentation."
|
|
}
|
|
```
|
|
|
|
#### Status Page
|
|
|
|
* **`GET /status`**
|
|
* **Description:** Serves a simple HTML status page showing node health based on registered services. Useful for a quick visual overview. Automatically refreshes.
|
|
* **Authentication:** None.
|
|
* **Response:** `200 OK` with `Content-Type: text/html`.
|