Jesús Pérez 09a97ac8f5
chore: update platform submodule to monorepo crates structure
Platform restructured into crates/, added AI service and detector,
       migrated control-center-ui to Leptos 0.8
2026-01-08 21:32:59 +00:00

15 KiB

Extension Registry Service

A high-performance Rust microservice that provides a unified REST API for extension discovery, versioning, and download from multiple sources (Gitea releases and OCI registries).

Features

  • Multi-Backend Support: Fetch extensions from Gitea releases and OCI registries
  • Unified REST API: Single API for all extension operations
  • Smart Caching: LRU cache with TTL to reduce backend API calls
  • Prometheus Metrics: Built-in metrics for monitoring
  • Health Monitoring: Health checks for all backends
  • Type-Safe: Strong typing for extension metadata
  • Async/Await: High-performance async operations with Tokio
  • Docker Support: Production-ready containerization

Architecture

┌─────────────────────────────────────────────────────────────┐
│                    Extension Registry API                   │
│                         (axum)                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌────────────────┐  ┌────────────────┐  ┌──────────────┐   │
│  │  Gitea Client  │  │   OCI Client   │  │  LRU Cache   │   │
│  │  (reqwest)     │  │   (reqwest)    │  │  (parking)   │   │
│  └────────────────┘  └────────────────┘  └──────────────┘   │
│         │                    │                    │         │
└─────────┼────────────────────┼────────────────────┼─────────┘
          │                    │                    │
          ▼                    ▼                    ▼
    ┌──────────┐         ┌──────────┐        ┌──────────┐
    │  Gitea   │         │   OCI    │        │  Memory  │
    │ Releases │         │ Registry │        │          │
    └──────────┘         └──────────┘        └──────────┘
```plaintext

## Installation

### Building from Source

```bash
cd provisioning/platform/extension-registry
cargo build --release
```plaintext

### Docker Build

```bash
docker build -t extension-registry:latest .
```plaintext

### Running with Cargo

```bash
cargo run -- --config config.toml --port 8082
```plaintext

### Running with Docker

```bash
docker run -d \
  -p 8082:8082 \
  -v $(pwd)/config.toml:/app/config.toml:ro \
  -v $(pwd)/tokens:/app/tokens:ro \
  extension-registry:latest
```plaintext

## Configuration

Create a `config.toml` file (see `config.example.toml`):

```toml
[server]
host = "0.0.0.0"
port = 8082
workers = 4
enable_cors = true
enable_compression = true

# Gitea backend (optional)
[gitea]
url = "https://gitea.example.com"
organization = "provisioning-extensions"
token_path = "/path/to/gitea-token.txt"
timeout_seconds = 30
verify_ssl = true

# OCI registry backend (optional)
[oci]
registry = "registry.example.com"
namespace = "provisioning"
auth_token_path = "/path/to/oci-token.txt"
timeout_seconds = 30
verify_ssl = true

# Cache configuration
[cache]
capacity = 1000
ttl_seconds = 300
enable_metadata_cache = true
enable_list_cache = true
```plaintext

**Note**: At least one backend (Gitea or OCI) must be configured.

## API Endpoints

### Extension Operations

#### List Extensions

```bash
GET /api/v1/extensions
```plaintext

Query parameters:

- `type` (optional): Filter by extension type (`provider`, `taskserv`, `cluster`)
- `source` (optional): Filter by source (`gitea`, `oci`)
- `limit` (optional): Maximum results (default: 100)
- `offset` (optional): Pagination offset (default: 0)

Example:

```bash
curl http://localhost:8082/api/v1/extensions?type=provider&limit=10
```plaintext

Response:

```json
[
  {
    "name": "aws",
    "type": "provider",
    "version": "1.2.0",
    "description": "AWS provider for provisioning",
    "author": "provisioning-team",
    "repository": "https://gitea.example.com/org/aws_prov",
    "source": "gitea",
    "published_at": "2025-10-06T12:00:00Z",
    "download_url": "https://gitea.example.com/org/aws_prov/releases/download/1.2.0/aws_prov.tar.gz",
    "size": 1024000
  }
]
```plaintext

#### Get Extension

```bash
GET /api/v1/extensions/{type}/{name}
```plaintext

Example:

```bash
curl http://localhost:8082/api/v1/extensions/provider/aws
```plaintext

#### List Versions

```bash
GET /api/v1/extensions/{type}/{name}/versions
```plaintext

Example:

```bash
curl http://localhost:8082/api/v1/extensions/provider/aws/versions
```plaintext

Response:

```json
[
  {
    "version": "1.2.0",
    "published_at": "2025-10-06T12:00:00Z",
    "download_url": "https://gitea.example.com/org/aws_prov/releases/download/1.2.0/aws_prov.tar.gz",
    "size": 1024000
  },
  {
    "version": "1.1.0",
    "published_at": "2025-09-15T10:30:00Z",
    "download_url": "https://gitea.example.com/org/aws_prov/releases/download/1.1.0/aws_prov.tar.gz",
    "size": 980000
  }
]
```plaintext

#### Download Extension

```bash
GET /api/v1/extensions/{type}/{name}/{version}
```plaintext

Example:

```bash
curl -O http://localhost:8082/api/v1/extensions/provider/aws/1.2.0
```plaintext

Returns binary data with `Content-Type: application/octet-stream`.

#### Search Extensions

```bash
GET /api/v1/extensions/search?q={query}
```plaintext

Query parameters:

- `q` (required): Search query
- `type` (optional): Filter by extension type
- `limit` (optional): Maximum results (default: 50)

Example:

```bash
curl http://localhost:8082/api/v1/extensions/search?q=kubernetes&type=taskserv
```plaintext

### System Endpoints

#### Health Check

```bash
GET /api/v1/health
```plaintext

Example:

```bash
curl http://localhost:8082/api/v1/health
```plaintext

Response:

```json
{
  "status": "healthy",
  "version": "0.1.0",
  "uptime": 3600,
  "backends": {
    "gitea": {
      "enabled": true,
      "healthy": true
    },
    "oci": {
      "enabled": true,
      "healthy": true
    }
  }
}
```plaintext

#### Metrics

```bash
GET /api/v1/metrics
```plaintext

Returns Prometheus-formatted metrics:

```plaintext
# HELP http_requests_total Total HTTP requests
# TYPE http_requests_total counter
http_requests_total 1234

# HELP cache_hits_total Total cache hits
# TYPE cache_hits_total counter
cache_hits_total 567

# HELP cache_misses_total Total cache misses
# TYPE cache_misses_total counter
cache_misses_total 123
```plaintext

#### Cache Statistics

```bash
GET /api/v1/cache/stats
```plaintext

Response:

```json
{
  "list_entries": 45,
  "metadata_entries": 120,
  "version_entries": 80,
  "total_entries": 245
}
```plaintext

## Extension Naming Conventions

### Gitea Repositories

Extensions in Gitea follow specific naming patterns:

- **Providers**: `{name}_prov` (e.g., `aws_prov`, `upcloud_prov`)
- **Task Services**: `{name}_taskserv` (e.g., `kubernetes_taskserv`, `postgres_taskserv`)
- **Clusters**: `{name}_cluster` (e.g., `buildkit_cluster`, `ci_cluster`)

### OCI Artifacts

Extensions in OCI registries follow these patterns:

- **Providers**: `{namespace}/{name}-provider` (e.g., `provisioning/aws-provider`)
- **Task Services**: `{namespace}/{name}-taskserv` (e.g., `provisioning/kubernetes-taskserv`)
- **Clusters**: `{namespace}/{name}-cluster` (e.g., `provisioning/buildkit-cluster`)

## Caching Strategy

The service implements a multi-level LRU cache with TTL:

1. **List Cache**: Caches extension lists (filtered by type/source)
2. **Metadata Cache**: Caches individual extension metadata
3. **Version Cache**: Caches version lists per extension

Cache behavior:

- **Capacity**: Configurable (default: 1000 entries)
- **TTL**: Configurable (default: 5 minutes)
- **Eviction**: LRU (Least Recently Used)
- **Invalidation**: Automatic on TTL expiration

Cache keys:

- List: `list:{type}:{source}`
- Metadata: `{type}/{name}`
- Versions: `{type}/{name}/versions`

## Error Handling

The API uses standard HTTP status codes:

- `200 OK`: Successful operation
- `400 Bad Request`: Invalid input (e.g., invalid extension type)
- `401 Unauthorized`: Authentication failed
- `404 Not Found`: Extension not found
- `429 Too Many Requests`: Rate limit exceeded
- `500 Internal Server Error`: Server error

Error response format:

```json
{
  "error": "not_found",
  "message": "Extension provider/nonexistent not found"
}
```plaintext

## Metrics and Monitoring

### Prometheus Metrics

Available metrics:

- `http_requests_total`: Total HTTP requests
- `http_request_duration_seconds`: Request duration histogram
- `cache_hits_total`: Total cache hits
- `cache_misses_total`: Total cache misses
- `extensions_total`: Total extensions served

### Health Checks

The health endpoint checks:

- Service uptime
- Gitea backend connectivity
- OCI backend connectivity
- Overall service status

## Development

### Project Structure

```plaintext
extension-registry/
├── Cargo.toml              # Rust dependencies
├── src/
│   ├── main.rs             # Entry point
│   ├── lib.rs              # Library exports
│   ├── config.rs           # Configuration management
│   ├── error.rs            # Error types
│   ├── api/
│   │   ├── handlers.rs     # HTTP handlers
│   │   └── routes.rs       # Route definitions
│   ├── gitea/
│   │   ├── client.rs       # Gitea API client
│   │   └── models.rs       # Gitea data models
│   ├── oci/
│   │   ├── client.rs       # OCI registry client
│   │   └── models.rs       # OCI data models
│   ├── cache/
│   │   └── lru_cache.rs    # LRU caching
│   └── models/
│       └── extension.rs    # Extension models
├── tests/
│   └── integration_test.rs # Integration tests
├── Dockerfile              # Docker build
└── README.md               # This file
```plaintext

### Running Tests

```bash
# Run all tests
cargo test

# Run with output
cargo test -- --nocapture

# Run specific test
cargo test test_health_check
```plaintext

### Code Quality

```bash
# Format code
cargo fmt

# Run clippy
cargo clippy

# Check for security vulnerabilities
cargo audit
```plaintext

## Deployment

### Systemd Service

Create `/etc/systemd/system/extension-registry.service`:

```ini
[Unit]
Description=Extension Registry Service
After=network.target

[Service]
Type=simple
User=registry
WorkingDirectory=/opt/extension-registry
ExecStart=/usr/local/bin/extension-registry --config /etc/extension-registry/config.toml
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target
```plaintext

Enable and start:

```bash
sudo systemctl enable extension-registry
sudo systemctl start extension-registry
sudo systemctl status extension-registry
```plaintext

### Docker Compose

```yaml
version: '3.8'

services:
  extension-registry:
    image: extension-registry:latest
    ports:
      - "8082:8082"
    volumes:
      - ./config.toml:/app/config.toml:ro
      - ./tokens:/app/tokens:ro
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8082/api/v1/health"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 5s
```plaintext

### Kubernetes Deployment

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: extension-registry
spec:
  replicas: 3
  selector:
    matchLabels:
      app: extension-registry
  template:
    metadata:
      labels:
        app: extension-registry
    spec:
      containers:
      - name: extension-registry
        image: extension-registry:latest
        ports:
        - containerPort: 8082
        volumeMounts:
        - name: config
          mountPath: /app/config.toml
          subPath: config.toml
        - name: tokens
          mountPath: /app/tokens
        livenessProbe:
          httpGet:
            path: /api/v1/health
            port: 8082
          initialDelaySeconds: 5
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /api/v1/health
            port: 8082
          initialDelaySeconds: 5
          periodSeconds: 5
      volumes:
      - name: config
        configMap:
          name: extension-registry-config
      - name: tokens
        secret:
          secretName: extension-registry-tokens
---
apiVersion: v1
kind: Service
metadata:
  name: extension-registry
spec:
  selector:
    app: extension-registry
  ports:
  - port: 8082
    targetPort: 8082
  type: ClusterIP
```plaintext

## Security

### Authentication

- Gitea: Token-based authentication via `token_path`
- OCI: Optional token authentication via `auth_token_path`

### Best Practices

1. **Store tokens securely**: Use file permissions (600) for token files
2. **Enable SSL verification**: Set `verify_ssl = true` in production
3. **Use HTTPS**: Always use HTTPS for Gitea and OCI registries
4. **Limit CORS**: Configure CORS appropriately for production
5. **Rate limiting**: Consider adding rate limiting for public APIs
6. **Network isolation**: Run service in isolated network segments

## Performance

### Benchmarks

Typical performance characteristics:

- **Cached requests**: <5ms response time
- **Uncached requests**: 50-200ms (depends on backend latency)
- **Cache hit ratio**: ~85-95% in typical workloads
- **Throughput**: 1000+ req/s on modern hardware

### Optimization Tips

1. **Increase cache capacity**: For large extension catalogs
2. **Tune TTL**: Balance freshness vs. performance
3. **Use multiple workers**: Scale with CPU cores
4. **Enable compression**: Reduce bandwidth usage
5. **Connection pooling**: Reuse HTTP connections to backends

## Troubleshooting

### Common Issues

#### Service won't start

- Check configuration file syntax
- Verify token files exist and are readable
- Check network connectivity to backends

#### Extensions not found

- Verify backend configuration (URL, organization, namespace)
- Check backend connectivity with health endpoint
- Review logs for authentication errors

#### Slow responses

- Check backend latency
- Increase cache capacity or TTL
- Review Prometheus metrics for bottlenecks

### Logging

Enable debug logging:

```bash
extension-registry --log-level debug
```plaintext

Enable JSON logging for structured logs:

```bash
extension-registry --json-log
```plaintext

## License

Part of the Provisioning Project.

## Contributing

See main project documentation for contribution guidelines.

## Support

For issues and questions, please refer to the main provisioning project documentation.