diff --git a/apps/engineering/content/docs/architecture/services/meta.json b/apps/engineering/content/docs/architecture/services/meta.json
index a37713fdb8..f23e38677c 100644
--- a/apps/engineering/content/docs/architecture/services/meta.json
+++ b/apps/engineering/content/docs/architecture/services/meta.json
@@ -7,6 +7,7 @@
"clickhouse",
"clickhouse-proxy",
"deploy",
+ "gw",
"healthcheck",
"quotacheck",
"run",
diff --git a/apps/engineering/content/docs/cli/deploy/index.mdx b/apps/engineering/content/docs/cli/deploy/index.mdx
index 8c23276116..e909be1376 100644
--- a/apps/engineering/content/docs/cli/deploy/index.mdx
+++ b/apps/engineering/content/docs/cli/deploy/index.mdx
@@ -13,7 +13,7 @@ Use --init to create a configuration template file. This generates an unkey.json
## Deployment Process
1. Load configuration from unkey.json or flags
-2. Build Docker image from your application
+2. Build Docker image from your application
3. Push image to container registry
4. Create deployment version on Unkey platform
5. Monitor deployment status until active
@@ -122,6 +122,13 @@ Project ID
- **Environment:** `UNKEY_PROJECT_ID`
+
+Keyspace ID for API key authentication
+
+- **Type:** string
+- **Environment:** `UNKEY_KEYSPACE_ID`
+
+
Build context path
@@ -162,6 +169,13 @@ Container registry
- **Environment:** `UNKEY_REGISTRY`
+
+Environment slug to deploy to
+
+- **Type:** string
+- **Default:** `"preview"`
+
+
Skip pushing to registry (for local testing)
@@ -176,6 +190,13 @@ Show detailed output for build and deployment operations
- **Default:** `false`
+
+Build Docker image for linux/amd64 platform (for deployment to cloud clusters)
+
+- **Type:** boolean
+- **Default:** `false`
+
+
Control plane URL
@@ -189,3 +210,10 @@ Control plane auth token
- **Type:** string
- **Default:** `"ctrl-secret-token"`
+
+
+API key for ctrl service authentication
+
+- **Type:** string
+- **Environment:** `API_KEY`
+
diff --git a/apps/engineering/content/docs/cli/run/api/index.mdx b/apps/engineering/content/docs/cli/run/api/index.mdx
index 7de41f974b..30e25b64c3 100644
--- a/apps/engineering/content/docs/cli/run/api/index.mdx
+++ b/apps/engineering/content/docs/cli/run/api/index.mdx
@@ -65,7 +65,7 @@ Geographic region identifier. Used for logging and routing. Default: unknown
Unique identifier for this instance. Auto-generated if not provided.
- **Type:** string
-- **Default:** `"ins_65sRey"`
+- **Default:** `"ins_6xwwLj"`
- **Environment:** `UNKEY_INSTANCE_ID`
@@ -162,19 +162,11 @@ S3 access key ID
- **Environment:** `UNKEY_VAULT_S3_ACCESS_KEY_ID`
-
+
S3 secret access key
- **Type:** string
-- **Environment:** `UNKEY_VAULT_S3_SECRET_ACCESS_KEY`
-
-
-
-Enable ClickHouse proxy endpoints for high-throughput event collection
-
-- **Type:** boolean
-- **Default:** `false`
-- **Environment:** `UNKEY_CHPROXY_ENABLED`
+- **Environment:** `UNKEY_VAULT_S3_ACCESS_KEY_SECRET`
@@ -183,3 +175,9 @@ Authentication token for ClickHouse proxy endpoints. Required when proxy is enab
- **Type:** string
- **Environment:** `UNKEY_CHPROXY_AUTH_TOKEN`
+
+
+Maximum allowed request body size in bytes. Set to 0 or negative to disable limit. Default: 10485760 (10MB)
+
+- **Type:** unknown
+
diff --git a/apps/engineering/content/docs/cli/run/ctrl/index.mdx b/apps/engineering/content/docs/cli/run/ctrl/index.mdx
index 35064c3f11..874e9dab4e 100644
--- a/apps/engineering/content/docs/cli/run/ctrl/index.mdx
+++ b/apps/engineering/content/docs/cli/run/ctrl/index.mdx
@@ -57,7 +57,7 @@ Geographic region identifier. Used for logging and routing. Default: unknown
Unique identifier for this instance. Auto-generated if not provided.
- **Type:** string
-- **Default:** `"ins_26qK8q"`
+- **Default:** `"ins_5PkxT8"`
- **Environment:** `UNKEY_INSTANCE_ID`
@@ -68,6 +68,13 @@ MySQL connection string for primary database. Required for all deployments. Exam
- **Environment:** `UNKEY_DATABASE_PRIMARY`
+
+MySQL connection string for partition database. Required for all deployments. Example: user:pass@host:3306/partition_002?parseTime=true
+
+- **Type:** string
+- **Environment:** `UNKEY_DATABASE_PARTITION`
+
+
MySQL connection string for hydra database. Required for all deployments. Example: user:pass@host:3306/hydra?parseTime=true
@@ -112,11 +119,18 @@ Authentication token for control plane API access. Required for secure deploymen
- **Environment:** `UNKEY_AUTH_TOKEN`
-
-Full URL of the metald service for VM operations. Required for deployments. Example: https://metald.example.com:8080
+
+Full URL of the krane service for VM operations. Required for deployments. Example: https://krane.example.com:8080
+
+- **Type:** string
+- **Environment:** `UNKEY_KRANE_ADDRESS`
+
+
+
+API key for simple authentication (demo purposes only). Will be replaced with JWT authentication.
- **Type:** string
-- **Environment:** `UNKEY_METALD_ADDRESS`
+- **Environment:** `UNKEY_API_KEY`
@@ -126,3 +140,69 @@ Path to SPIFFE agent socket for mTLS authentication. Default: /var/lib/spire/age
- **Default:** `"/var/lib/spire/agent/agent.sock"`
- **Environment:** `UNKEY_SPIFFE_SOCKET_PATH`
+
+
+Vault master keys for encryption
+
+- **Type:** string[]
+- **Environment:** `UNKEY_VAULT_MASTER_KEYS`
+
+
+
+S3 Compatible Endpoint URL
+
+- **Type:** string
+- **Environment:** `UNKEY_VAULT_S3_URL`
+
+
+
+S3 bucket name
+
+- **Type:** string
+- **Environment:** `UNKEY_VAULT_S3_BUCKET`
+
+
+
+S3 access key ID
+
+- **Type:** string
+- **Environment:** `UNKEY_VAULT_S3_ACCESS_KEY_ID`
+
+
+
+S3 secret access key
+
+- **Type:** string
+- **Environment:** `UNKEY_VAULT_S3_ACCESS_KEY_SECRET`
+
+
+
+Enable Let's Encrypt for acme challenges
+
+- **Type:** boolean
+- **Default:** `false`
+- **Environment:** `UNKEY_ACME_ENABLED`
+
+
+
+Enable Cloudflare for wildcard certificates
+
+- **Type:** boolean
+- **Default:** `false`
+- **Environment:** `UNKEY_ACME_CLOUDFLARE_ENABLED`
+
+
+
+Cloudflare API token for Let's Encrypt
+
+- **Type:** string
+- **Environment:** `UNKEY_ACME_CLOUDFLARE_API_TOKEN`
+
+
+
+Default domain for auto-generated hostnames
+
+- **Type:** string
+- **Default:** `"unkey.app"`
+- **Environment:** `UNKEY_DEFAULT_DOMAIN`
+
diff --git a/apps/engineering/content/docs/cli/run/gw/index.mdx b/apps/engineering/content/docs/cli/run/gw/index.mdx
new file mode 100644
index 0000000000..b7366561c9
--- /dev/null
+++ b/apps/engineering/content/docs/cli/run/gw/index.mdx
@@ -0,0 +1,199 @@
+---
+title: "Gw"
+description: "Run the Unkey Gateway server"
+---
+
+## Command Syntax
+
+```bash
+unkey run gw [flags]
+```
+
+
+Some flags are required for this command to work properly.
+
+
+## Flags
+
+
+HTTP port for the GW server to listen on. Default: 6060
+
+- **Type:** integer
+- **Default:** `6060`
+- **Environment:** `UNKEY_HTTP_PORT`
+
+
+
+HTTPS port for the GW server to listen on. Default: 6433
+
+- **Type:** integer
+- **Default:** `6433`
+- **Environment:** `UNKEY_HTTPS_PORT`
+
+
+
+Enable TLS termination for the gateway. Default: false
+
+- **Type:** boolean
+- **Default:** `false`
+- **Environment:** `UNKEY_TLS_ENABLED`
+
+
+
+Cloud platform identifier for this node. Used for logging and metrics.
+
+- **Type:** string
+- **Environment:** `UNKEY_PLATFORM`
+
+
+
+Container image identifier. Used for logging and metrics.
+
+- **Type:** string
+- **Environment:** `UNKEY_IMAGE`
+
+
+
+Geographic region identifier. Used for logging and routing. Default: unknown
+
+- **Type:** string
+- **Default:** `"unknown"`
+- **Environment:** `AWS_REGION`
+
+
+
+Unique identifier for this instance. Auto-generated if not provided.
+
+- **Type:** string
+- **Default:** `"gw_5TuSkS"`
+- **Environment:** `UNKEY_GATEWAY_ID`
+
+
+
+Domain to use for fallback TLS certificate when a domain has no cert configured
+
+- **Type:** string
+- **Environment:** `UNKEY_DEFAULT_CERT_DOMAIN`
+
+
+
+Main gateway domain for internal endpoints (e.g., gateway.unkey.com)
+
+- **Type:** string
+- **Environment:** `UNKEY_MAIN_DOMAIN`
+
+
+
+Address for the control plane to connect to
+
+- **Type:** string
+- **Environment:** `UNKEY_CTRL_ADDR`
+
+
+
+MySQL connection string for partitioned primary database (gateway operations). Required. Example: user:pass@host:3306/partition_001?parseTime=true
+
+- **Type:** string
+- **Environment:** `UNKEY_DATABASE_PRIMARY`
+
+
+
+MySQL connection string for partitioned read-replica (gateway operations). Format same as database-primary.
+
+- **Type:** string
+- **Environment:** `UNKEY_DATABASE_REPLICA`
+
+
+
+MySQL connection string for keys service primary database (non-partitioned). Required. Example: user:pass@host:3306/unkey?parseTime=true
+
+- **Type:** string
+- **Environment:** `UNKEY_KEYS_DATABASE_PRIMARY`
+
+
+
+MySQL connection string for keys service read-replica (non-partitioned). Format same as main-database-primary.
+
+- **Type:** string
+- **Environment:** `UNKEY_KEYS_DATABASE_REPLICA`
+
+
+
+ClickHouse connection string for analytics. Recommended for production. Example: clickhouse://user:pass@host:9000/unkey
+
+- **Type:** string
+- **Environment:** `UNKEY_CLICKHOUSE_URL`
+
+
+
+Redis connection string for caching. Recommended for production. Example: redis://user:pass@host:6379/0
+
+- **Type:** string
+- **Environment:** `UNKEY_REDIS_URL`
+
+
+
+Enable OpenTelemetry tracing and metrics
+
+- **Type:** boolean
+- **Default:** `false`
+- **Environment:** `UNKEY_OTEL`
+
+
+
+Sampling rate for OpenTelemetry traces (0.0-1.0). Only used when --otel is provided. Default: 0.25
+
+- **Type:** float
+- **Default:** `0.25`
+- **Environment:** `UNKEY_OTEL_TRACE_SAMPLING_RATE`
+
+
+
+Enable Prometheus /metrics endpoint on specified port. Set to 0 to disable.
+
+- **Type:** integer
+- **Environment:** `UNKEY_PROMETHEUS_PORT`
+
+
+
+Vault master keys for encryption
+
+- **Type:** string[]
+- **Environment:** `UNKEY_VAULT_MASTER_KEYS`
+
+
+
+S3 Compatible Endpoint URL
+
+- **Type:** string
+- **Environment:** `UNKEY_VAULT_S3_URL`
+
+
+
+S3 bucket name
+
+- **Type:** string
+- **Environment:** `UNKEY_VAULT_S3_BUCKET`
+
+
+
+S3 access key ID
+
+- **Type:** string
+- **Environment:** `UNKEY_VAULT_S3_ACCESS_KEY_ID`
+
+
+
+S3 secret access key
+
+- **Type:** string
+- **Environment:** `UNKEY_VAULT_S3_ACCESS_KEY_SECRET`
+
+
+
+Generate and use self-signed certificate for *.unkey.local if it doesn't exist
+
+- **Type:** boolean
+- **Default:** `false`
+- **Environment:** `UNKEY_REQUIRE_LOCAL_CERT`
+
diff --git a/apps/engineering/content/docs/cli/run/index.mdx b/apps/engineering/content/docs/cli/run/index.mdx
index 3aaae08d08..b98c36ecfc 100644
--- a/apps/engineering/content/docs/cli/run/index.mdx
+++ b/apps/engineering/content/docs/cli/run/index.mdx
@@ -10,6 +10,8 @@ This command starts different Unkey microservices. Each service can be configure
- api: The main API server for validating and managing API keys
- ctrl: The control plane service for managing infrastructure and deployments
+- gw: The gateway service for routing requests to the appropriate API
+- krane: The VM management service for infrastructure
## Command Syntax
@@ -19,4 +21,6 @@ unkey run
## Quick Reference
- `unkey run api` - Run the Unkey API server for validating and managing API keys
-- `unkey run ctrl` - Run the Unkey control plane service for managing infrastructure and services
\ No newline at end of file
+- `unkey run ctrl` - Run the Unkey control plane service for managing infrastructure and services
+- `unkey run gw` - Run the Unkey Gateway server
+- `unkey run krane` - Run the k8s management service
\ No newline at end of file
diff --git a/apps/engineering/content/docs/cli/run/krane/index.mdx b/apps/engineering/content/docs/cli/run/krane/index.mdx
new file mode 100644
index 0000000000..a0b6061001
--- /dev/null
+++ b/apps/engineering/content/docs/cli/run/krane/index.mdx
@@ -0,0 +1,61 @@
+---
+title: "Krane"
+description: "Run the k8s management service"
+---
+krane (/kreɪn/) is the kubernetes deployment service for Unkey infrastructure.
+
+It manages the lifecycle of deployments in a kubernetes cluster:
+
+## Command Syntax
+
+```bash
+unkey run krane [flags]
+```
+
+## Examples
+
+### Run with default configuration
+
+```bash
+unkey run krane
+```
+
+## Flags
+
+
+Port for the server to listen on. Default: 8080
+
+- **Type:** integer
+- **Default:** `8080`
+- **Environment:** `UNKEY_HTTP_PORT`
+
+
+
+Unique identifier for this instance. Auto-generated if not provided.
+
+- **Type:** string
+- **Default:** `"ins_6EFeEJ"`
+- **Environment:** `UNKEY_INSTANCE_ID`
+
+
+
+Backend type for the service. Either kubernetes or docker. Default: kubernetes
+
+- **Type:** string
+- **Default:** `"kubernetes"`
+- **Environment:** `UNKEY_KRANE_BACKEND`
+
+
+
+Path to the docker socket. Only used if backend is docker. Default: /var/run/docker.sock
+
+- **Type:** string
+- **Default:** `"/var/run/docker.sock"`
+- **Environment:** `UNKEY_DOCKER_SOCKET`
+
+
+
+Automatically delete deployments after some time. Use go duration formats such as 2h30m
+
+- **Type:** duration
+
diff --git a/deploy.bash b/deploy.bash
deleted file mode 100644
index 7e795a1d0e..0000000000
--- a/deploy.bash
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/bash
-
-
-
-for i in {1..100}; do
- go run . deploy \
- --context=./demo_api \
- --workspace-id="ws_4pqNeih1b62QPv6g" \
- --project-id="proj_4pqPKyhJ3b9fCnuH" \
- --control-plane-url="http://localhost:7091" \
- --env=production &
- sleep 1
-done
-
-wait
diff --git a/deployment/05-seed-chronark.sql b/deployment/05-seed-chronark.sql
new file mode 100644
index 0000000000..664a6c3146
--- /dev/null
+++ b/deployment/05-seed-chronark.sql
@@ -0,0 +1,54 @@
+-- We should really have this as part of our local cli,
+-- it was very helpful to quickly seed the database with
+-- project data.
+
+
+
+
+-- Insert test workspace
+INSERT INTO workspaces (
+ id,
+ org_id,
+ name,
+ slug,
+ created_at_m,
+ beta_features,
+ features
+) VALUES (
+ 'ws_chronark',
+ 'org_chronark',
+ 'Chronark',
+ 'chronark',
+ UNIX_TIMESTAMP() * 1000,
+ '{"deployments":true}',
+ '{}'
+) ON DUPLICATE KEY UPDATE created_at_m = UNIX_TIMESTAMP() * 1000;
+
+-- Insert project
+INSERT INTO projects (
+ id,
+ workspace_id,
+ name,
+ slug,
+ created_at
+) VALUES (
+ 'proj_chronark',
+ 'ws_chronark',
+ 'API',
+ 'api',
+ UNIX_TIMESTAMP() * 1000
+) ON DUPLICATE KEY UPDATE created_at = UNIX_TIMESTAMP() * 1000;
+
+INSERT INTO environments (
+ id,
+ workspace_id,
+ project_id,
+ slug,
+ created_at
+) VALUES (
+ 'env_chronark',
+ 'ws_chronark',
+ 'proj_chronark',
+ 'production',
+ UNIX_TIMESTAMP() * 1000
+) ON DUPLICATE KEY UPDATE created_at = UNIX_TIMESTAMP() * 1000;
diff --git a/deployment/docker-compose.yaml b/deployment/docker-compose.yaml
index 86a0eb2a95..09a74841cc 100644
--- a/deployment/docker-compose.yaml
+++ b/deployment/docker-compose.yaml
@@ -264,45 +264,27 @@ services:
UNKEY_VAULT_S3_ACCESS_KEY_SECRET: "minio_root_password"
UNKEY_VAULT_MASTER_KEYS: "Ch9rZWtfMmdqMFBJdVhac1NSa0ZhNE5mOWlLSnBHenFPENTt7an5MRogENt9Si6wms4pQ2XIvqNSIgNpaBenJmXgcInhu6Nfv2U="
- metald:
+ krane:
build:
context: ../go
dockerfile: Dockerfile
args:
VERSION: "latest"
- container_name: metald
- command: ["run", "metald"]
+ container_name: krane
+ command: ["run", "krane"]
ports:
- "8090:8080"
- depends_on:
- - mysql
volumes:
# Mount Docker socket for Docker backend support
- /var/run/docker.sock:/var/run/docker.sock
- # Mount SQLite database locally for direct access
- - ./data/metald:/var/lib/metald
environment:
# Server configuration
- UNKEY_METALD_ADDRESS: "0.0.0.0"
- UNKEY_METALD_PORT: "8080"
+ UNKEY_HTTP_PORT: "8080"
# Backend configuration - use Docker backend for development
- UNKEY_METALD_BACKEND: "docker"
-
- # Database configuration
- UNKEY_METALD_DATABASE_DIR: "/var/lib/metald"
-
- # Asset manager configuration (disabled for local development)
- UNKEY_METALD_ASSETMANAGER_ENABLED: "false"
-
- # Billing configuration (disabled for local development)
- UNKEY_METALD_BILLING_ENABLED: "false"
-
- # TLS configuration (disabled for local development)
- UNKEY_METALD_TLS_MODE: "disabled"
-
- # OpenTelemetry configuration
- UNKEY_METALD_OTEL_ENABLED: "false"
+ UNKEY_KRANE_BACKEND: "docker"
+ UNKEY_DOCKER_SOCKET: "/var/run/docker.sock"
+ UNKEY_DEPLOYMENT_EVICTION_TTL: "10m"
ctrl:
networks:
@@ -323,7 +305,7 @@ services:
s3:
condition: service_healthy
required: true
- metald:
+ krane:
condition: service_started
required: true
volumes:
@@ -335,7 +317,7 @@ services:
# Control plane configuration
UNKEY_HTTP_PORT: "7091"
- UNKEY_METALD_ADDRESS: "http://metald:8080"
+ UNKEY_KRANE_ADDRESS: "http://krane:8080"
UNKEY_DEFAULT_DOMAIN: "unkey.local"
UNKEY_VAULT_S3_URL: "http://s3:3902"
diff --git a/go/Dockerfile b/go/Dockerfile
index 3c05a3c6b4..81b8d7dbee 100644
--- a/go/Dockerfile
+++ b/go/Dockerfile
@@ -1,10 +1,10 @@
FROM golang:1.25 AS builder
WORKDIR /go/src/github.com/unkeyed/unkey/go
-# Copy everything first because go.mod has replace directives pointing to ./deploy/pkg/ modules
-# This ensures all local dependencies are available before go mod download
-COPY . .
+
+COPY go.mod go.sum ./
RUN go mod download
+COPY . .
ARG VERSION
ENV CGO_ENABLED=0
RUN go build -o bin/unkey -ldflags="-X 'github.com/unkeyed/unkey/go/pkg/version.Version=${VERSION}'" ./main.go
diff --git a/go/Makefile b/go/Makefile
index 606dc1dc10..049d5c1305 100644
--- a/go/Makefile
+++ b/go/Makefile
@@ -37,8 +37,6 @@ generate:
buf generate
go generate ./...
-generate-builder:
- buf generate --path proto/deploy/builderd
test: test-unit
@@ -89,6 +87,7 @@ k8s-up: k8s-check ## Deploy all services to current Kubernetes cluster
@kubectl wait --for=condition=ready pod -l app=api -n unkey --timeout=180s
@kubectl wait --for=condition=ready pod -l app=gw -n unkey --timeout=180s
@kubectl wait --for=condition=ready pod -l app=ctrl -n unkey --timeout=180s
+ @kubectl wait --for=condition=ready pod -l app=krane -n unkey --timeout=180s
@kubectl wait --for=condition=ready pod -l app=dashboard -n unkey --timeout=180s
@echo "Kubernetes environment is ready!"
@echo ""
diff --git a/go/Tiltfile b/go/Tiltfile
index f4b0b6294c..9df63c0fe0 100644
--- a/go/Tiltfile
+++ b/go/Tiltfile
@@ -6,7 +6,7 @@ load('ext://namespace', 'namespace_create')
load('ext://restart_process', 'docker_build_with_restart')
# Tilt configuration
-config.define_string_list("services", args=False, usage="Services to run (mysql,ctrl,metald,clickhouse,s3,observability,planetscale or all)")
+config.define_string_list("services", args=False, usage="Services to run (mysql,ctrl,clickhouse,s3,observability,planetscale or all)")
config.define_bool("debug", args=False, usage="Enable debug mode")
cfg = config.parse()
@@ -27,7 +27,7 @@ start_planetscale = 'all' in services or 'planetscale' in services
start_api = 'all' in services or 'api' in services
start_gw = 'all' in services or 'gateway' in services or 'gw' in services
start_ctrl = 'all' in services or 'ctrl' in services
-start_metald = 'all' in services or 'metald' in services
+start_krane = 'all' in services or 'krane' in services
start_dashboard = 'all' in services or 'dashboard' in services
# Apply RBAC
@@ -128,7 +128,7 @@ if start_observability:
)
# Build Unkey binary locally (independent of infrastructure)
-if start_api or start_gw or start_ctrl or start_metald:
+if start_api or start_gw or start_ctrl or start_krane:
print("Building Unkey binary...")
# Build locally first for faster updates
local_resource(
@@ -241,15 +241,15 @@ if start_ctrl:
trigger_mode=TRIGGER_MODE_MANUAL if debug_mode else TRIGGER_MODE_AUTO
)
-# Metald service (1 replica)
-if start_metald:
- print("Setting up Metald service...")
+# Krane service (1 replica)
+if start_krane:
+ print("Setting up krane service...")
docker_build_with_restart(
- 'unkey-metald:latest',
+ 'unkey-krane:latest',
'.',
dockerfile='Dockerfile.tilt',
- entrypoint=['/unkey', 'run', 'metald'],
+ entrypoint=['/unkey', 'run', 'krane'],
only=['./bin'],
live_update=[
sync('./bin/unkey', '/unkey'),
@@ -257,21 +257,20 @@ if start_metald:
ignore=['./cmd/api', './cmd/gw', './cmd/ctrl', './apps/api', './apps/gw', './apps/ctrl']
)
- k8s_yaml('k8s/manifests/metald.yaml')
+ k8s_yaml('k8s/manifests/krane.yaml')
# Build dependency list
- metald_deps = []
- # Metald uses SQLite so no MySQL dependency needed
+ krane_deps = []
# Add compilation dependency for Unkey services
- metald_deps.append('unkey-compile')
+ krane_deps.append('unkey-compile')
k8s_resource(
- 'metald',
+ 'krane',
port_forwards='8090:8080',
- resource_deps=metald_deps,
+ resource_deps=krane_deps,
labels=['unkey'],
auto_init=True,
- trigger_mode=TRIGGER_MODE_AUTO if debug_mode else TRIGGER_MODE_MANUAL
+ trigger_mode=TRIGGER_MODE_AUTO
)
# Dashboard service
@@ -314,7 +313,6 @@ if start_observability: active_services.extend(['prometheus', 'otel-collector'])
if start_api: active_services.append('api')
if start_gw: active_services.append('gw')
if start_ctrl: active_services.append('ctrl')
-if start_metald: active_services.append('metald')
if start_dashboard: active_services.append('dashboard')
print("""
@@ -329,7 +327,7 @@ Dashboard: http://localhost:3000
API: http://localhost:7070
Gateway: http://localhost:8080
Ctrl: http://localhost:7091
-Metald: http://localhost:8090
+Krane: http://localhost:8090
Prometheus: http://localhost:9090
S3 Console: http://localhost:9000
ClickHouse: http://localhost:8123
diff --git a/go/apps/assetmanagerd/.gitignore b/go/apps/assetmanagerd/.gitignore
deleted file mode 100644
index eee88e3f29..0000000000
--- a/go/apps/assetmanagerd/.gitignore
+++ /dev/null
@@ -1,86 +0,0 @@
-# Compiled binaries (SECURITY: Never commit compiled binaries)
-build/
-*.exe
-*.dll
-*.so
-*.dylib
-
-# Test binaries, built with `go test -c`
-*.test
-
-# Output of the go coverage tool
-*.out
-
-# Dependency directories (remove the comment below to include it)
-vendor/
-
-# Go workspace file
-go.work
-go.work.sum
-
-# IDE files
-.vscode/
-.idea/
-*.swp
-*.swo
-*~
-
-# OS files
-.DS_Store
-Thumbs.db
-
-# Local development files
-.env
-.env.local
-.env.development
-.env.test
-.env.production
-
-# Temporary files
-tmp/
-temp/
-*.tmp
-
-# Logs
-*.log
-logs/
-
-# Database files
-*.db
-*.sqlite
-*.sqlite3
-
-# Build artifacts and cache
-dist/
-cache/
-.cache/
-
-# Coverage reports
-coverage.html
-coverage.out
-profile.out
-
-# Backup files
-*.bak
-*.backup
-
-# Docker build context (if using dockerignore isn't sufficient)
-.dockerignore
-
-# Certificate files (never commit certificates or keys)
-*.pem
-*.key
-*.crt
-*.p12
-*.pfx
-
-# Secret files
-secrets.yaml
-secrets.json
-.secrets
-
-# Local storage directories for development
-data/
-scratch/
-rootfs/
-workspace/
diff --git a/go/apps/assetmanagerd/CHANGELOG.md b/go/apps/assetmanagerd/CHANGELOG.md
deleted file mode 100644
index d98ec70d18..0000000000
--- a/go/apps/assetmanagerd/CHANGELOG.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# Changelog
-
-All notable changes to assetmanagerd will be documented in this file.
-
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-
-## [0.5.1] - 2025-07-02
-
-### Changed
-- Update client.go,types.go,main.go,
-
diff --git a/go/apps/assetmanagerd/Makefile b/go/apps/assetmanagerd/Makefile
deleted file mode 100644
index f598c05814..0000000000
--- a/go/apps/assetmanagerd/Makefile
+++ /dev/null
@@ -1,136 +0,0 @@
-# AssetManagerd Makefile
-# Unified Makefile following Unkey service standards
-
-# Service name and binary
-SERVICE_NAME := assetmanagerd
-BINARY_NAME := $(SERVICE_NAME)
-
-# Versioning
-VERSION ?= 0.5.1
-COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
-BUILD_TIME := $(shell date -u +%Y%m%d-%H%M%S)
-
-# Build flags
-LDFLAGS := -ldflags "-X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.buildTime=$(BUILD_TIME)"
-
-# Directories
-BUILD_DIR := build
-PROTO_DIR := proto
-GEN_DIR := gen
-INSTALL_DIR := /usr/local/bin
-SYSTEMD_DIR := /etc/systemd/system
-CONFIG_DIR := /etc/$(SERVICE_NAME)
-DATA_DIR := /var/lib/$(SERVICE_NAME)
-LOG_DIR := /var/log/$(SERVICE_NAME)
-ASSETS_DIR := /opt/vm-assets
-
-# Go commands
-GOCMD := go
-GOBUILD := $(GOCMD) build
-GOTEST := $(GOCMD) test
-GOVET := $(GOCMD) vet
-GOFMT := goimports
-GOLINT := golangci-lint
-
-# Default target
-.DEFAULT_GOAL := help
-
-# Targets (alphabetically ordered)
-.PHONY: all build check clean deps dev env-example fmt generate help install lint lint-proto run service-logs service-logs-tail service-restart service-start service-status service-stop setup test test-coverage uninstall version vet
-
-all: clean build ## Clean, generate, and build
-
-build: deps ## Build the binary
- @mkdir -p $(BUILD_DIR)
- @$(GOBUILD) $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME) ./cmd/$(SERVICE_NAME)
-
-check: fmt vet lint test ## Run all checks (fmt, vet, lint with proto, test)
-
-clean: ## Clean build artifacts
- @rm -rf $(BUILD_DIR)
- @rm -rf $(GEN_DIR)
-
-deps: ## Download and tidy dependencies
- @go mod download
- @go mod tidy
-
-dev: generate ## Run in development mode
- @go run ./cmd/$(SERVICE_NAME)
-
-fmt: ## Format code
- @$(GOFMT) -w .
-
-help: ## Show this help message
- @echo 'Usage: make [target]'
- @echo ''
- @echo 'Targets:'
- @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-20s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
-
-install: build ## Install the service (requires sudo)
- @sudo systemctl stop $(SERVICE_NAME) 2>/dev/null || true
- @sudo mkdir -p $(CONFIG_DIR)
- @sudo cp $(BUILD_DIR)/$(BINARY_NAME) $(INSTALL_DIR)/
- @sudo chmod +x $(INSTALL_DIR)/$(BINARY_NAME)
- @sudo cp contrib/systemd/$(SERVICE_NAME).service $(SYSTEMD_DIR)/
- @sudo systemctl daemon-reload
- @sudo systemctl enable $(SERVICE_NAME) >/dev/null 2>&1
- @sudo systemctl start $(SERVICE_NAME) 2>/dev/null || true
- @echo "✓ $(SERVICE_NAME) installed and started"
-
-lint: lint-proto ## Run linter (includes protobuf linting)
- @which $(GOLINT) >/dev/null || go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
- @$(GOLINT) run
-
-lint-proto: ## Run protobuf linter
- @buf lint
-
-run: build ## Build and run the service
- @./$(BUILD_DIR)/$(BINARY_NAME)
-
-service-logs: ## Follow service logs
- @sudo journalctl -u $(SERVICE_NAME) -f
-
-service-logs-tail: ## Show last 50 lines of logs
- @sudo journalctl -u $(SERVICE_NAME) -n 50 --no-pager
-
-service-restart: ## Restart the service
- @sudo systemctl restart $(SERVICE_NAME)
- @echo "✓ $(SERVICE_NAME) restarted"
-
-service-start: ## Start the service
- @sudo systemctl start $(SERVICE_NAME)
- @echo "✓ $(SERVICE_NAME) started"
-
-service-status: ## Check service status
- @sudo systemctl status $(SERVICE_NAME) --no-pager
-
-service-stop: ## Stop the service
- @sudo systemctl stop $(SERVICE_NAME)
- @echo "✓ $(SERVICE_NAME) stopped"
-
-setup: deps generate ## Complete development setup
-
-test: ## Run tests
- @$(GOTEST) -v ./...
-
-test-coverage: ## Run tests with coverage
- @$(GOTEST) -v -coverprofile=coverage.out ./...
- @go tool cover -html=coverage.out -o coverage.html
- @echo "✓ Coverage report: coverage.html"
-
-uninstall: ## Uninstall the service (requires sudo)
- @sudo systemctl stop $(SERVICE_NAME) 2>/dev/null || true
- @sudo systemctl disable $(SERVICE_NAME) 2>/dev/null || true
- @sudo rm -f $(SYSTEMD_DIR)/$(SERVICE_NAME).service
- @sudo rm -f $(INSTALL_DIR)/$(BINARY_NAME)
- @sudo systemctl daemon-reload
- @echo "✓ $(SERVICE_NAME) uninstalled (config/data preserved)"
-
-version: ## Show version information
- @echo "$(SERVICE_NAME) version: $(VERSION)"
- @echo "Commit: $(COMMIT)"
- @echo "Build time: $(BUILD_TIME)"
-
-vet: ## Run go vet
- @$(GOVET) ./...
-
diff --git a/go/apps/assetmanagerd/README.md b/go/apps/assetmanagerd/README.md
deleted file mode 100644
index ede271a728..0000000000
--- a/go/apps/assetmanagerd/README.md
+++ /dev/null
@@ -1,245 +0,0 @@
-# AssetManagerd - Centralized VM Asset Management Service
-
-AssetManagerd is a centralized asset repository and lifecycle management service for virtual machine resources in the Unkey Deploy platform. It provides efficient storage, versioning, and distribution of VM assets like kernels, rootfs images, initrd, and disk images.
-
-## Quick Links
-
-- [API Documentation](./docs/api/README.md) - Complete API reference with examples
-- [Architecture & Dependencies](./docs/architecture/README.md) - Service design and integrations
-- [Operations Guide](./docs/operations/README.md) - Production deployment and monitoring
-- [Development Setup](./docs/development/README.md) - Build, test, and local development
-
-## Service Overview
-
-**Purpose**: Centralized management and distribution of VM assets with reference counting, lease management, and garbage collection.
-
-### Key Features
-
-- **Asset Registry**: Centralized metadata store for all VM assets with SQLite backend
-- **Pluggable Storage**: Support for local filesystem, S3, NFS, and HTTP backends
-- **Reference Counting**: Track asset usage with lease management for safe lifecycle control
-- **Garbage Collection**: Automatic cleanup of expired leases and unreferenced assets
-- **Asset Preparation**: Efficient asset deployment to VM jailer paths via hard links or copies
-- **Label-based Discovery**: Flexible asset filtering using key-value labels
-- **Checksum Verification**: SHA256 integrity verification for all assets
-- **High Observability**: OpenTelemetry tracing, Prometheus metrics, structured logging
-
-### Dependencies
-
-- [builderd](../builderd/docs/README.md) - Registers built VM images as assets
-- [metald](../metald/docs/README.md) - Consumes assets for VM provisioning
-
-## Quick Start
-
-### Installation
-
-```bash
-# Build from source
-cd assetmanagerd
-make build
-
-# Install with systemd
-sudo make install
-```
-
-### Basic Configuration
-
-```bash
-# Minimal configuration for development
-export UNKEY_ASSETMANAGERD_PORT=8083
-export UNKEY_ASSETMANAGERD_STORAGE_TYPE=local
-export UNKEY_ASSETMANAGERD_LOCAL_PATH=/opt/vm-assets
-export UNKEY_ASSETMANAGERD_DATABASE_PATH=/opt/assetmanagerd/assets.db
-export UNKEY_ASSETMANAGERD_TLS_MODE=spiffe
-
-./assetmanagerd
-```
-
-### Register Your First Asset
-
-```bash
-# Register a kernel asset
-curl -X POST http://localhost:8083/asset.v1.AssetManagerService/RegisterAsset \
- -H "Content-Type: application/json" \
- -d '{
- "name": "vmlinux",
- "type": "ASSET_TYPE_KERNEL",
- "backend": "STORAGE_BACKEND_LOCAL",
- "location": "ab/abcd1234...",
- "size_bytes": 10485760,
- "checksum": "abcd1234...",
- "labels": {
- "arch": "x86_64",
- "version": "5.10",
- "default": "true"
- },
- "created_by": "manual"
- }'
-```
-
-## Architecture Overview
-
-```mermaid
-graph TB
- subgraph "API Layer"
- API[ConnectRPC API
:8083]
- AUTH[Auth Middleware]
- end
-
- subgraph "Core Services"
- SVC[Asset Service]
- REG[Asset Registry]
- GC[Garbage Collector]
- end
-
- subgraph "Storage Layer"
- STORE[Storage Interface]
- LOCAL[Local FS]
- S3[S3 Backend]
- NFS[NFS Backend]
- end
-
- subgraph "Data Layer"
- DB[(SQLite DB)]
- FS[File Storage]
- end
-
- subgraph "External Services"
- BUILD[Builderd]
- METAL[Metald]
- end
-
- BUILD -->|RegisterAsset| API
- METAL -->|PrepareAssets| API
-
- API --> AUTH --> SVC
- SVC --> REG
- SVC --> GC
- REG --> DB
-
- SVC --> STORE
- STORE --> LOCAL
- STORE -.-> S3
- STORE -.-> NFS
-
- LOCAL --> FS
-```
-
-## Asset Types
-
-AssetManagerd supports the following asset types:
-
-- **KERNEL**: Linux kernel images for VM boot
-- **ROOTFS**: Root filesystem images (ext4, squashfs)
-- **INITRD**: Initial ramdisk images
-- **DISK_IMAGE**: Additional disk images for data volumes
-
-## Production Deployment
-
-### System Requirements
-
-- **OS**: Linux (any modern distribution)
-- **CPU**: 2+ cores recommended
-- **Memory**: 4GB+ for metadata and caching
-- **Storage**: Depends on asset volume (100GB+ recommended)
-- **Network**: Low latency to metald instances
-
-### Security Considerations
-
-1. **TLS/mTLS**: Enable SPIFFE for service-to-service authentication
-2. **Storage Permissions**: Secure asset storage directories
-3. **Database Security**: Protect SQLite database file
-4. **Access Control**: Implement proper authorization for asset operations
-
-### High Availability
-
-- **Metadata**: Regular SQLite backups
-- **Storage**: Use distributed storage backends (S3, NFS)
-- **Service**: Multiple instances with shared storage
-- **Caching**: Local cache for frequently accessed assets
-
-## API Highlights
-
-The service exposes a ConnectRPC API with the following main operations:
-
-- `RegisterAsset` - Register new asset metadata
-- `GetAsset` - Retrieve asset information
-- `ListAssets` - List assets with filtering and pagination
-- `AcquireAsset` - Acquire lease on an asset
-- `ReleaseAsset` - Release asset lease
-- `PrepareAssets` - Prepare assets for VM deployment
-- `DeleteAsset` - Mark asset for deletion
-- `GarbageCollect` - Manually trigger garbage collection
-- `QueryAssets` - Enhanced asset query with automatic build triggering
-
-See [API Documentation](./docs/api/README.md) for complete reference.
-
-## Monitoring
-
-Key metrics to monitor in production:
-
-- `assetmanager_assets_total` - Total assets by type and status
-- `assetmanager_leases_active` - Active leases per asset
-- `assetmanager_storage_bytes_used` - Storage usage by type
-- `assetmanager_gc_duration_seconds` - Garbage collection performance
-- `assetmanager_prepare_duration_seconds` - Asset preparation latency
-
-See [Operations Guide](./docs/operations/README.md) for complete monitoring setup.
-
-## Development
-
-### Building from Source
-
-```bash
-git clone https://github.com/unkeyed/unkey
-cd go/apps/assetmanagerd
-make test
-make build
-```
-
-### Running Tests
-
-```bash
-# Unit tests
-make test
-
-# Integration tests
-make test-integration
-
-# Benchmark tests
-make bench
-```
-
-See [Development Setup](./docs/development/README.md) for detailed instructions.
-
-## Support
-
-- **Issues**: [GitHub Issues](https://github.com/unkeyed/unkey/issues)
-- **Documentation**: [Full Documentation](./docs/README.md)
-- **Version**: v0.3.0
-
-## Automatic Asset Building
-
-AssetManagerd integrates with builderd to automatically create missing assets. When QueryAssets is called with:
-- `enable_auto_build: true`
-- A docker_image label in the query
-- No matching assets found
-
-The service will automatically trigger builderd to create the rootfs and register it upon completion.
-
-```bash
-# Query with auto-build enabled
-curl -X POST http://localhost:8083/asset.v1.AssetManagerService/QueryAssets \
- -H "Content-Type: application/json" \
- -d '{
- "type": "ASSET_TYPE_ROOTFS",
- "label_selector": {
- "docker_image": "nginx:latest"
- },
- "build_options": {
- "enable_auto_build": true,
- "wait_for_completion": true,
- "build_timeout_seconds": 1800
- }
- }'
-```
diff --git a/go/apps/assetmanagerd/TODO.md b/go/apps/assetmanagerd/TODO.md
deleted file mode 100644
index 8c48787cae..0000000000
--- a/go/apps/assetmanagerd/TODO.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# AssetManagerd TODO
-
-## High Priority
-
-- [ ] Add Grafana dashboards for monitoring
- - Asset registration/deletion rates
- - Storage usage by type
- - Garbage collection metrics
- - API latency percentiles
-
-- [ ] Add packaging infrastructure
- - Create debian/ directory with control files
- - Create RPM spec file
- - Add Makefile targets for package building
-
-## Medium Priority
-
-- [ ] Implement S3 backend storage
- - Already designed in storage interface
- - Add AWS SDK dependencies
- - Configuration for bucket/prefix
-
-- [ ] Add asset replication
- - Cross-region replication for availability
- - Configurable replication factor
- - Health checks for replicas
-
-- [ ] Implement content deduplication
- - Use SHA256 for content addressing
- - Reference counting for deduplicated assets
- - Migration tool for existing assets
-
-## Low Priority
-
-- [ ] Add remote asset sources
- - HTTP/HTTPS download support
- - S3 download support
- - Caching and retry logic
-
-- [ ] Implement asset versioning
- - Version history tracking
- - Rollback capabilities
- - Version garbage collection
-
-- [ ] Add asset compression
- - Transparent compression/decompression
- - Multiple compression algorithm support
- - Storage savings metrics
-
-## Completed
-
-- [x] Basic service implementation
-- [x] Local storage backend
-- [x] SQLite database for metadata
-- [x] Garbage collection
-- [x] ConnectRPC API
-- [x] Prometheus metrics
-- [x] SPIFFE/mTLS support
-- [x] Integration with metald
-- [x] Unified health endpoint
\ No newline at end of file
diff --git a/go/apps/assetmanagerd/client/Makefile b/go/apps/assetmanagerd/client/Makefile
deleted file mode 100644
index 817096b4b7..0000000000
--- a/go/apps/assetmanagerd/client/Makefile
+++ /dev/null
@@ -1,38 +0,0 @@
-# Makefile for assetmanagerd CLI client
-
-# Variables
-BINARY_NAME := assetmanagerd-cli
-BUILD_DIR := build
-VERSION ?= 0.5.1
-
-# Default target
-.DEFAULT_GOAL := help
-
-# Targets (alphabetically ordered)
-
-.PHONY: build
-build: ## Build the assetmanagerd CLI client
- @echo "Building $(BINARY_NAME)..."
- @mkdir -p $(BUILD_DIR)
- @go build -o $(BUILD_DIR)/$(BINARY_NAME) ../cmd/assetmanagerd-cli/main.go
- @echo "Build complete: $(BUILD_DIR)/$(BINARY_NAME)"
-
-.PHONY: clean
-clean: ## Clean build artifacts
- @echo "Cleaning..."
- @rm -rf $(BUILD_DIR)
-
-.PHONY: help
-help: ## Show this help message
- @echo "Available targets:"
- @echo " build - Build the assetmanagerd CLI client"
- @echo " clean - Clean build artifacts"
- @echo " install - Install the CLI client to /usr/local/bin"
- @echo " help - Show this help message"
-
-.PHONY: install
-install: build ## Install the CLI client to /usr/local/bin
- @echo "Installing $(BINARY_NAME) to /usr/local/bin..."
- @sudo mv $(BUILD_DIR)/$(BINARY_NAME) /usr/local/bin/$(BINARY_NAME)
- @sudo chmod +x /usr/local/bin/$(BINARY_NAME)
- @echo "Installation complete"
\ No newline at end of file
diff --git a/go/apps/assetmanagerd/client/client.go b/go/apps/assetmanagerd/client/client.go
deleted file mode 100644
index 0f2cf3afb3..0000000000
--- a/go/apps/assetmanagerd/client/client.go
+++ /dev/null
@@ -1,318 +0,0 @@
-package client
-
-import (
- "context"
- "fmt"
- "net/http"
- "time"
-
- "connectrpc.com/connect"
- "github.com/unkeyed/unkey/go/deploy/pkg/tls"
- assetv1 "github.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1"
- "github.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1/assetmanagerdv1connect"
-)
-
-// AIDEV-NOTE: AssetManagerd client with SPIFFE/SPIRE socket integration
-// This client provides a high-level interface for assetmanagerd operations with proper authentication
-
-// Config holds the configuration for the assetmanagerd client
-type Config struct {
- // ServerAddress is the assetmanagerd server endpoint (e.g., "https://assetmanagerd:8083")
- ServerAddress string
-
- // UserID is the user identifier for authentication
- UserID string
-
- // TLS configuration
- TLSMode string // "disabled", "file", or "spiffe"
- SPIFFESocketPath string // Path to SPIFFE agent socket
- TLSCertFile string // TLS certificate file (for file mode)
- TLSKeyFile string // TLS key file (for file mode)
- TLSCAFile string // TLS CA file (for file mode)
- EnableCertCaching bool // Enable certificate caching
- CertCacheTTL time.Duration // Certificate cache TTL
-
- // Optional HTTP client timeout
- Timeout time.Duration
-}
-
-// Client provides a high-level interface to assetmanagerd services
-type Client struct {
- assetService assetmanagerdv1connect.AssetManagerServiceClient
- tlsProvider tls.Provider
- userID string
- serverAddr string
-}
-
-// New creates a new assetmanagerd client with SPIFFE/SPIRE integration
-func New(ctx context.Context, config Config) (*Client, error) {
- // Set defaults
- if config.SPIFFESocketPath == "" {
- config.SPIFFESocketPath = "/var/lib/spire/agent/agent.sock"
- }
- if config.TLSMode == "" {
- config.TLSMode = "spiffe"
- }
- if config.Timeout == 0 {
- config.Timeout = 30 * time.Second
- }
- if config.CertCacheTTL == 0 {
- config.CertCacheTTL = 5 * time.Second
- }
-
- // Create TLS provider
- tlsConfig := tls.Config{
- Mode: tls.Mode(config.TLSMode),
- CertFile: config.TLSCertFile,
- KeyFile: config.TLSKeyFile,
- CAFile: config.TLSCAFile,
- SPIFFESocketPath: config.SPIFFESocketPath,
- EnableCertCaching: config.EnableCertCaching,
- CertCacheTTL: config.CertCacheTTL,
- }
-
- tlsProvider, err := tls.NewProvider(ctx, tlsConfig)
- if err != nil {
- return nil, fmt.Errorf("failed to create TLS provider: %w", err)
- }
-
- // Get HTTP client with SPIFFE mTLS
- httpClient := tlsProvider.HTTPClient()
- httpClient.Timeout = config.Timeout
-
- // Add authentication
- httpClient.Transport = &transport{
- Base: httpClient.Transport,
- UserID: config.UserID,
- }
-
- // Create ConnectRPC client
- assetService := assetmanagerdv1connect.NewAssetManagerServiceClient(
- httpClient,
- config.ServerAddress,
- )
-
- return &Client{
- assetService: assetService,
- tlsProvider: tlsProvider,
- userID: config.UserID,
- serverAddr: config.ServerAddress,
- }, nil
-}
-
-// Close closes the client and cleans up resources
-func (c *Client) Close() error {
- if c.tlsProvider != nil {
- return c.tlsProvider.Close()
- }
- return nil
-}
-
-// RegisterAsset registers a new asset with assetmanagerd
-func (c *Client) RegisterAsset(ctx context.Context, req *RegisterAssetRequest) (*RegisterAssetResponse, error) {
- pbReq := &assetv1.RegisterAssetRequest{
- Name: req.Name,
- Type: req.Type,
- Backend: req.Backend,
- Location: req.Location,
- SizeBytes: req.SizeBytes,
- Checksum: req.Checksum,
- Labels: req.Labels,
- CreatedBy: req.CreatedBy,
- }
-
- resp, err := c.assetService.RegisterAsset(ctx, connect.NewRequest(pbReq))
- if err != nil {
- return nil, fmt.Errorf("failed to register asset: %w", err)
- }
-
- return &RegisterAssetResponse{
- Asset: resp.Msg.Asset,
- }, nil
-}
-
-// GetAsset retrieves asset information by ID
-func (c *Client) GetAsset(ctx context.Context, assetID string) (*GetAssetResponse, error) {
- req := &assetv1.GetAssetRequest{
- Id: assetID,
- }
-
- resp, err := c.assetService.GetAsset(ctx, connect.NewRequest(req))
- if err != nil {
- return nil, fmt.Errorf("failed to get asset: %w", err)
- }
-
- return &GetAssetResponse{
- Asset: resp.Msg.Asset,
- }, nil
-}
-
-// ListAssets retrieves a list of assets with optional filtering
-func (c *Client) ListAssets(ctx context.Context, req *ListAssetsRequest) (*ListAssetsResponse, error) {
- pbReq := &assetv1.ListAssetsRequest{
- Type: req.Type,
- Status: req.Status,
- LabelSelector: req.Labels,
- PageSize: req.PageSize,
- PageToken: req.PageToken,
- }
-
- resp, err := c.assetService.ListAssets(ctx, connect.NewRequest(pbReq))
- if err != nil {
- return nil, fmt.Errorf("failed to list assets: %w", err)
- }
-
- return &ListAssetsResponse{
- Assets: resp.Msg.Assets,
- NextPageToken: resp.Msg.NextPageToken,
- }, nil
-}
-
-// QueryAssets queries assets with automatic build triggering if not found
-func (c *Client) QueryAssets(ctx context.Context, req *QueryAssetsRequest) (*QueryAssetsResponse, error) {
- pbReq := &assetv1.QueryAssetsRequest{
- Type: req.Type,
- LabelSelector: req.Labels,
- }
-
- resp, err := c.assetService.QueryAssets(ctx, connect.NewRequest(pbReq))
- if err != nil {
- return nil, fmt.Errorf("failed to query assets: %w", err)
- }
-
- return &QueryAssetsResponse{
- Assets: resp.Msg.Assets,
- }, nil
-}
-
-// PrepareAssets pre-stages assets for a specific host/jailer
-func (c *Client) PrepareAssets(ctx context.Context, req *PrepareAssetsRequest) (*PrepareAssetsResponse, error) {
- pbReq := &assetv1.PrepareAssetsRequest{
- AssetIds: req.AssetIds,
- TargetPath: req.CacheDir,
- PreparedFor: req.JailerId,
- }
-
- resp, err := c.assetService.PrepareAssets(ctx, connect.NewRequest(pbReq))
- if err != nil {
- return nil, fmt.Errorf("failed to prepare assets: %w", err)
- }
-
- // Convert map to slice of paths
- var preparedPaths []string
- for _, path := range resp.Msg.AssetPaths {
- preparedPaths = append(preparedPaths, path)
- }
-
- return &PrepareAssetsResponse{
- PreparedPaths: preparedPaths,
- Success: len(resp.Msg.AssetPaths) > 0,
- }, nil
-}
-
-// AcquireAsset marks an asset as in-use (reference counting for GC)
-func (c *Client) AcquireAsset(ctx context.Context, assetID string) (*AcquireAssetResponse, error) {
- req := &assetv1.AcquireAssetRequest{
- AssetId: assetID,
- }
-
- resp, err := c.assetService.AcquireAsset(ctx, connect.NewRequest(req))
- if err != nil {
- return nil, fmt.Errorf("failed to acquire asset: %w", err)
- }
-
- return &AcquireAssetResponse{
- Success: resp.Msg.Asset != nil,
- ReferenceCount: int32(len(resp.Msg.LeaseId)), // Use lease ID length as proxy
- }, nil
-}
-
-// ReleaseAsset releases an asset reference (decrements ref count)
-func (c *Client) ReleaseAsset(ctx context.Context, leaseID string) (*ReleaseAssetResponse, error) {
- req := &assetv1.ReleaseAssetRequest{
- LeaseId: leaseID,
- }
-
- resp, err := c.assetService.ReleaseAsset(ctx, connect.NewRequest(req))
- if err != nil {
- return nil, fmt.Errorf("failed to release asset: %w", err)
- }
-
- return &ReleaseAssetResponse{
- Success: resp.Msg.Asset != nil,
- ReferenceCount: 0, // Not available in response
- }, nil
-}
-
-// DeleteAsset deletes an asset (only if ref count is 0)
-func (c *Client) DeleteAsset(ctx context.Context, assetID string) (*DeleteAssetResponse, error) {
- req := &assetv1.DeleteAssetRequest{
- Id: assetID,
- }
-
- resp, err := c.assetService.DeleteAsset(ctx, connect.NewRequest(req))
- if err != nil {
- return nil, fmt.Errorf("failed to delete asset: %w", err)
- }
-
- return &DeleteAssetResponse{
- Success: resp.Msg.Deleted,
- }, nil
-}
-
-// GarbageCollect triggers garbage collection of unused assets
-func (c *Client) GarbageCollect(ctx context.Context, req *GarbageCollectRequest) (*GarbageCollectResponse, error) {
- pbReq := &assetv1.GarbageCollectRequest{
- DryRun: req.DryRun,
- MaxAgeSeconds: int64(req.MaxAgeHours) * 3600, // Convert hours to seconds
- DeleteUnreferenced: req.ForceCleanup,
- }
-
- resp, err := c.assetService.GarbageCollect(ctx, connect.NewRequest(pbReq))
- if err != nil {
- return nil, fmt.Errorf("failed to garbage collect: %w", err)
- }
-
- // Extract asset IDs from deleted assets
- var removedAssets []string
- for _, asset := range resp.Msg.DeletedAssets {
- removedAssets = append(removedAssets, asset.Id)
- }
-
- return &GarbageCollectResponse{
- RemovedAssets: removedAssets,
- FreedBytes: resp.Msg.BytesFreed,
- Success: len(resp.Msg.DeletedAssets) >= 0, // Always consider it successful
- }, nil
-}
-
-// GetServerAddress returns the server address this client is connected to
-func (c *Client) GetServerAddress() string {
- return c.serverAddr
-}
-
-// transport adds authentication headers to all requests
-type transport struct {
- Base http.RoundTripper
- UserID string
-}
-
-func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
- // Clone the request to avoid modifying the original
- req2 := req.Clone(req.Context())
- if req2.Header == nil {
- req2.Header = make(http.Header)
- }
-
- // Set Authorization header with development token format
- // AIDEV-BUSINESS_RULE: In development, use "dev_user_" format
- // TODO: Update to proper JWT tokens in production
- req2.Header.Set("Authorization", fmt.Sprintf("Bearer dev_user_%s", t.UserID))
-
- // Use the base transport, or default if nil
- base := t.Base
- if base == nil {
- base = http.DefaultTransport
- }
- return base.RoundTrip(req2)
-}
diff --git a/go/apps/assetmanagerd/client/types.go b/go/apps/assetmanagerd/client/types.go
deleted file mode 100644
index 6d9b028bb8..0000000000
--- a/go/apps/assetmanagerd/client/types.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package client
-
-import (
- assetv1 "github.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1"
-)
-
-// AIDEV-NOTE: Type definitions for assetmanagerd client requests and responses
-// These provide a clean interface that wraps the protobuf types
-
-// RegisterAssetRequest represents a request to register a new asset
-type RegisterAssetRequest struct {
- Name string
- Type assetv1.AssetType
- Backend assetv1.StorageBackend
- Location string
- SizeBytes int64
- Checksum string
- Labels map[string]string
- CreatedBy string
-}
-
-// RegisterAssetResponse represents the response from registering an asset
-type RegisterAssetResponse struct {
- Asset *assetv1.Asset
-}
-
-// GetAssetResponse represents the response from getting an asset
-type GetAssetResponse struct {
- Asset *assetv1.Asset
-}
-
-// ListAssetsRequest represents a request to list assets
-type ListAssetsRequest struct {
- Type assetv1.AssetType
- Status assetv1.AssetStatus
- Labels map[string]string
- PageSize int32
- PageToken string
-}
-
-// ListAssetsResponse represents the response from listing assets
-type ListAssetsResponse struct {
- Assets []*assetv1.Asset
- NextPageToken string
-}
-
-// QueryAssetsRequest represents a request to query assets with auto-build
-type QueryAssetsRequest struct {
- Type assetv1.AssetType
- Labels map[string]string
-}
-
-// QueryAssetsResponse represents the response from querying assets
-type QueryAssetsResponse struct {
- Assets []*assetv1.Asset
-}
-
-// PrepareAssetsRequest represents a request to prepare assets for a host
-type PrepareAssetsRequest struct {
- AssetIds []string
- HostId string
- JailerId string
- CacheDir string
-}
-
-// PrepareAssetsResponse represents the response from preparing assets
-type PrepareAssetsResponse struct {
- PreparedPaths []string
- Success bool
-}
-
-// AcquireAssetResponse represents the response from acquiring an asset
-type AcquireAssetResponse struct {
- Success bool
- ReferenceCount int32
-}
-
-// ReleaseAssetResponse represents the response from releasing an asset
-type ReleaseAssetResponse struct {
- Success bool
- ReferenceCount int32
-}
-
-// DeleteAssetResponse represents the response from deleting an asset
-type DeleteAssetResponse struct {
- Success bool
-}
-
-// GarbageCollectRequest represents a request to perform garbage collection
-type GarbageCollectRequest struct {
- DryRun bool
- MaxAgeHours int32
- ForceCleanup bool
-}
-
-// GarbageCollectResponse represents the response from garbage collection
-type GarbageCollectResponse struct {
- RemovedAssets []string
- FreedBytes int64
- Success bool
-}
diff --git a/go/apps/assetmanagerd/cmd/assetmanagerd-cli/main.go b/go/apps/assetmanagerd/cmd/assetmanagerd-cli/main.go
deleted file mode 100644
index 83275ed0df..0000000000
--- a/go/apps/assetmanagerd/cmd/assetmanagerd-cli/main.go
+++ /dev/null
@@ -1,396 +0,0 @@
-package main
-
-import (
- "context"
- "encoding/json"
- "flag"
- "fmt"
- "log"
- "os"
- "time"
-
- "github.com/unkeyed/unkey/go/apps/assetmanagerd/client"
- assetv1 "github.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1"
-)
-
-// AIDEV-NOTE: CLI tool demonstrating assetmanagerd client usage with SPIFFE integration
-// This provides a command-line interface for asset management operations
-
-func main() {
- var (
- serverAddr = flag.String("server", getEnvOrDefault("UNKEY_ASSETMANAGERD_SERVER_ADDRESS", "https://localhost:8083"), "assetmanagerd server address")
- userID = flag.String("user", getEnvOrDefault("UNKEY_ASSETMANAGERD_USER_ID", "cli-user"), "user ID for authentication")
- tlsMode = flag.String("tls-mode", getEnvOrDefault("UNKEY_ASSETMANAGERD_TLS_MODE", "spiffe"), "TLS mode: disabled, file, or spiffe")
- spiffeSocket = flag.String("spiffe-socket", getEnvOrDefault("UNKEY_ASSETMANAGERD_SPIFFE_SOCKET", "/var/lib/spire/agent/agent.sock"), "SPIFFE agent socket path")
- tlsCert = flag.String("tls-cert", "", "TLS certificate file (for file mode)")
- tlsKey = flag.String("tls-key", "", "TLS key file (for file mode)")
- tlsCA = flag.String("tls-ca", "", "TLS CA file (for file mode)")
- timeout = flag.Duration("timeout", 30*time.Second, "request timeout")
- jsonOutput = flag.Bool("json", false, "output results as JSON")
- )
- flag.Parse()
-
- if flag.NArg() == 0 {
- printUsage()
- os.Exit(1)
- }
-
- ctx := context.Background()
-
- // Create assetmanagerd client
- config := client.Config{
- ServerAddress: *serverAddr,
- UserID: *userID,
- TLSMode: *tlsMode,
- SPIFFESocketPath: *spiffeSocket,
- TLSCertFile: *tlsCert,
- TLSKeyFile: *tlsKey,
- TLSCAFile: *tlsCA,
- Timeout: *timeout,
- }
-
- assetClient, err := client.New(ctx, config)
- if err != nil {
- log.Fatalf("Failed to create assetmanagerd client: %v", err)
- }
- defer assetClient.Close()
-
- // Execute command
- command := flag.Arg(0)
- switch command {
- case "list":
- handleList(ctx, assetClient, *jsonOutput)
- case "get":
- handleGet(ctx, assetClient, *jsonOutput)
- case "register":
- handleRegister(ctx, assetClient, *jsonOutput)
- case "query":
- handleQuery(ctx, assetClient, *jsonOutput)
- case "prepare":
- handlePrepare(ctx, assetClient, *jsonOutput)
- case "acquire":
- handleAcquire(ctx, assetClient, *jsonOutput)
- case "release":
- handleRelease(ctx, assetClient, *jsonOutput)
- case "delete":
- handleDelete(ctx, assetClient, *jsonOutput)
- case "gc":
- handleGarbageCollect(ctx, assetClient, *jsonOutput)
- default:
- fmt.Fprintf(os.Stderr, "Unknown command: %s\n", command)
- printUsage()
- os.Exit(1)
- }
-}
-
-func printUsage() {
- fmt.Printf(`assetmanagerd-cli - CLI tool for assetmanagerd operations
-
-Usage: %s [flags] [args...]
-
-Commands:
- list List all assets
- get Get detailed asset information
- register Register a new asset from JSON file
- query Query assets with auto-build
- prepare Prepare assets for deployment
- acquire Acquire asset reference
- release Release asset reference
- delete Delete an asset
- gc Trigger garbage collection
-
-Environment Variables:
- UNKEY_ASSETMANAGERD_SERVER_ADDRESS Server address (default: https://localhost:8083)
- UNKEY_ASSETMANAGERD_USER_ID User ID for authentication (default: cli-user)
- UNKEY_ASSETMANAGERD_TLS_MODE TLS mode (default: spiffe)
- UNKEY_ASSETMANAGERD_SPIFFE_SOCKET SPIFFE socket path (default: /var/lib/spire/agent/agent.sock)
-
-Examples:
- # List all assets with SPIFFE authentication
- %s -user=prod-user-123 list
-
- # Get detailed asset information
- %s get asset-12345
-
- # Query assets for a specific Docker image
- %s query -docker-image=nginx:alpine
-
- # Prepare assets for deployment
- %s prepare asset-123 asset-456
-
- # List assets with disabled TLS (development)
- %s -tls-mode=disabled -server=http://localhost:8083 list
-
- # Get asset info with JSON output
- %s get asset-12345 -json
-
-`, os.Args[0], os.Args[0], os.Args[0], os.Args[0], os.Args[0], os.Args[0], os.Args[0])
-}
-
-func handleList(ctx context.Context, assetClient *client.Client, jsonOutput bool) {
- req := &client.ListAssetsRequest{
- PageSize: 50,
- }
-
- resp, err := assetClient.ListAssets(ctx, req)
- if err != nil {
- log.Fatalf("Failed to list assets: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- for _, asset := range resp.Assets {
- fmt.Printf(" - %s: %s (%s, %d bytes)\n",
- asset.Id,
- asset.Name,
- asset.Type.String(),
- asset.SizeBytes,
- )
- }
- }
-}
-
-func handleGet(ctx context.Context, assetClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("Asset ID is required for get command")
- }
- assetID := flag.Arg(1)
-
- resp, err := assetClient.GetAsset(ctx, assetID)
- if err != nil {
- log.Fatalf("Failed to get asset: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- asset := resp.Asset
- fmt.Printf("Asset Information:\n")
- fmt.Printf(" ID: %s\n", asset.Id)
- fmt.Printf(" Name: %s\n", asset.Name)
- fmt.Printf(" Type: %s\n", asset.Type.String())
- fmt.Printf(" Backend: %s\n", asset.Backend.String())
- fmt.Printf(" Location: %s\n", asset.Location)
- fmt.Printf(" Size: %d bytes\n", asset.SizeBytes)
- fmt.Printf(" Created by: %s\n", asset.CreatedBy)
- fmt.Printf(" Created at: %d\n", asset.CreatedAt)
-
- if len(asset.Labels) > 0 {
- fmt.Printf(" Labels:\n")
- for k, v := range asset.Labels {
- fmt.Printf(" %s: %s\n", k, v)
- }
- }
- }
-}
-
-func handleRegister(ctx context.Context, assetClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("Asset file path is required for register command")
- }
- assetFile := flag.Arg(1)
-
- // Read asset from JSON file
- data, err := os.ReadFile(assetFile)
- if err != nil {
- log.Fatalf("Failed to read asset file: %v", err)
- }
-
- var asset assetv1.Asset
- if err := json.Unmarshal(data, &asset); err != nil {
- log.Fatalf("Failed to parse asset JSON: %v", err)
- }
-
- req := &client.RegisterAssetRequest{
- Name: asset.Name,
- Type: asset.Type,
- Backend: asset.Backend,
- Location: asset.Location,
- SizeBytes: asset.SizeBytes,
- Checksum: asset.Checksum,
- Labels: asset.Labels,
- CreatedBy: asset.CreatedBy,
- }
-
- resp, err := assetClient.RegisterAsset(ctx, req)
- if err != nil {
- log.Fatalf("Failed to register asset: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- fmt.Printf("Asset registered successfully:\n")
- fmt.Printf(" Asset ID: %s\n", resp.Asset.Id)
- fmt.Printf(" Asset Name: %s\n", resp.Asset.Name)
- }
-}
-
-func handleQuery(ctx context.Context, assetClient *client.Client, jsonOutput bool) {
- // Simple query example - in real usage, this would parse requirements from CLI args
- req := &client.QueryAssetsRequest{
- Type: assetv1.AssetType_ASSET_TYPE_ROOTFS,
- Labels: map[string]string{
- "docker_image": "nginx:alpine",
- },
- }
-
- resp, err := assetClient.QueryAssets(ctx, req)
- if err != nil {
- log.Fatalf("Failed to query assets: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- fmt.Printf("Query Results:\n")
- fmt.Printf(" Found assets: %d\n", len(resp.Assets))
- for _, asset := range resp.Assets {
- fmt.Printf(" - %s (%s)\n", asset.Id, asset.Name)
- }
- }
-}
-
-func handlePrepare(ctx context.Context, assetClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("At least one asset ID is required for prepare command")
- }
-
- assetIDs := flag.Args()[1:]
- req := &client.PrepareAssetsRequest{
- AssetIds: assetIDs,
- JailerId: "default",
- CacheDir: "/tmp/asset-cache",
- }
-
- resp, err := assetClient.PrepareAssets(ctx, req)
- if err != nil {
- log.Fatalf("Failed to prepare assets: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- fmt.Printf("Asset preparation:\n")
- fmt.Printf(" Success: %v\n", resp.Success)
- fmt.Printf(" Prepared paths: %d\n", len(resp.PreparedPaths))
- for i, path := range resp.PreparedPaths {
- fmt.Printf(" - %s: %s\n", assetIDs[i], path)
- }
- }
-}
-
-func handleAcquire(ctx context.Context, assetClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("Asset ID is required for acquire command")
- }
- assetID := flag.Arg(1)
-
- resp, err := assetClient.AcquireAsset(ctx, assetID)
- if err != nil {
- log.Fatalf("Failed to acquire asset: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- fmt.Printf("Asset acquisition:\n")
- fmt.Printf(" Asset ID: %s\n", assetID)
- fmt.Printf(" Success: %v\n", resp.Success)
- fmt.Printf(" Reference count: %d\n", resp.ReferenceCount)
- }
-}
-
-func handleRelease(ctx context.Context, assetClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("Asset ID is required for release command")
- }
- assetID := flag.Arg(1)
-
- resp, err := assetClient.ReleaseAsset(ctx, assetID)
- if err != nil {
- log.Fatalf("Failed to release asset: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- fmt.Printf("Asset release:\n")
- fmt.Printf(" Asset ID: %s\n", assetID)
- fmt.Printf(" Success: %v\n", resp.Success)
- fmt.Printf(" Reference count: %d\n", resp.ReferenceCount)
- }
-}
-
-func handleDelete(ctx context.Context, assetClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("Asset ID is required for delete command")
- }
- assetID := flag.Arg(1)
-
- resp, err := assetClient.DeleteAsset(ctx, assetID)
- if err != nil {
- log.Fatalf("Failed to delete asset: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- fmt.Printf("Asset deletion:\n")
- fmt.Printf(" Asset ID: %s\n", assetID)
- fmt.Printf(" Success: %v\n", resp.Success)
- }
-}
-
-func handleGarbageCollect(ctx context.Context, assetClient *client.Client, jsonOutput bool) {
- req := &client.GarbageCollectRequest{
- DryRun: true, // Default to dry run for safety
- MaxAgeHours: 24, // Clean up assets older than 24 hours
- }
-
- // Check for --force flag
- if flag.NArg() > 1 && flag.Arg(1) == "--force" {
- req.DryRun = false
- req.ForceCleanup = true
- }
-
- resp, err := assetClient.GarbageCollect(ctx, req)
- if err != nil {
- log.Fatalf("Failed to garbage collect: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- mode := "DRY RUN"
- if !req.DryRun {
- mode = "EXECUTED"
- }
- fmt.Printf("Garbage Collection (%s):\n", mode)
- fmt.Printf(" Success: %v\n", resp.Success)
- fmt.Printf(" Assets removed: %d\n", len(resp.RemovedAssets))
- fmt.Printf(" Bytes freed: %d\n", resp.FreedBytes)
- if len(resp.RemovedAssets) > 0 {
- fmt.Printf(" Removed asset IDs:\n")
- for _, assetID := range resp.RemovedAssets {
- fmt.Printf(" - %s\n", assetID)
- }
- }
- }
-}
-
-func outputJSON(data interface{}) {
- encoder := json.NewEncoder(os.Stdout)
- encoder.SetIndent("", " ")
- if err := encoder.Encode(data); err != nil {
- log.Fatalf("Failed to encode JSON: %v", err)
- }
-}
-
-func getEnvOrDefault(key, defaultValue string) string {
- if value := os.Getenv(key); value != "" {
- return value
- }
- return defaultValue
-}
diff --git a/go/apps/assetmanagerd/cmd/assetmanagerd/main.go b/go/apps/assetmanagerd/cmd/assetmanagerd/main.go
deleted file mode 100644
index 73549a0183..0000000000
--- a/go/apps/assetmanagerd/cmd/assetmanagerd/main.go
+++ /dev/null
@@ -1,403 +0,0 @@
-package main
-
-import (
- "context"
- "crypto/sha256"
- "flag"
- "fmt"
- "io"
- "log/slog"
- "net/http"
- "os"
- "os/signal"
- "runtime"
- "runtime/debug"
- "syscall"
- "time"
-
- "connectrpc.com/connect"
- "github.com/unkeyed/unkey/go/apps/assetmanagerd/internal/builderd"
- "github.com/unkeyed/unkey/go/apps/assetmanagerd/internal/config"
- "github.com/unkeyed/unkey/go/apps/assetmanagerd/internal/observability"
- "github.com/unkeyed/unkey/go/apps/assetmanagerd/internal/registry"
- "github.com/unkeyed/unkey/go/apps/assetmanagerd/internal/service"
- "github.com/unkeyed/unkey/go/apps/assetmanagerd/internal/storage"
- healthpkg "github.com/unkeyed/unkey/go/deploy/pkg/health"
- "github.com/unkeyed/unkey/go/deploy/pkg/observability/interceptors"
- tlspkg "github.com/unkeyed/unkey/go/deploy/pkg/tls"
- assetv1 "github.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1"
- "github.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1/assetmanagerdv1connect"
- "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
- "golang.org/x/net/http2"
- "golang.org/x/net/http2/h2c"
-)
-
-var version = ""
-
-// getVersion returns the version, with fallback logic for development builds
-func getVersion() string {
- // AIDEV-NOTE: Unified version handling pattern across all services
- // Priority: ldflags > VCS revision > module version > "dev"
- if version != "" {
- return version
- }
-
- if info, ok := debug.ReadBuildInfo(); ok {
- // Check for VCS revision (git commit)
- for _, setting := range info.Settings {
- if setting.Key == "vcs.revision" {
- return setting.Value[:8] // First 8 chars of commit hash
- }
- }
- // Fall back to module version if available
- if info.Main.Version != "" && info.Main.Version != "(devel)" {
- return info.Main.Version
- }
- }
-
- return "dev"
-}
-
-func main() {
- // Track application start time for uptime calculations
- startTime := time.Now()
-
- var showVersion bool
- flag.BoolVar(&showVersion, "version", false, "Show version information")
- flag.Parse()
-
- version = getVersion()
-
- if showVersion {
- fmt.Printf("assetmanagerd version %s\n", version)
- fmt.Printf("Go version: %s\n", runtime.Version())
- os.Exit(0)
- }
-
- // Create root logger
- //nolint:exhaustruct // Only Level field is needed for handler options
- logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
- Level: slog.LevelInfo,
- }))
- slog.SetDefault(logger)
-
- logger.Info("starting assetmanagerd",
- slog.String("version", version),
- slog.String("go_version", runtime.Version()),
- )
-
- // Load configuration
- cfg, err := config.Load()
- if err != nil {
- logger.Error("failed to load configuration", slog.String("error", err.Error()))
- os.Exit(1)
- }
-
- // Create context that cancels on interrupt
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- // Handle shutdown gracefully
- sigChan := make(chan os.Signal, 1)
- signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
-
- // Initialize TLS provider (defaults to disabled)
- //nolint:exhaustruct // Only specified TLS fields are needed for this configuration
- tlsConfig := tlspkg.Config{
- Mode: tlspkg.Mode(cfg.TLSMode),
- CertFile: cfg.TLSCertFile,
- KeyFile: cfg.TLSKeyFile,
- CAFile: cfg.TLSCAFile,
- SPIFFESocketPath: cfg.TLSSPIFFESocketPath,
- }
- tlsProvider, err := tlspkg.NewProvider(ctx, tlsConfig)
- if err != nil {
- // AIDEV-NOTE: TLS/SPIFFE is now required - no fallback to disabled mode
- logger.Error("TLS initialization failed",
- "error", err,
- "mode", cfg.TLSMode)
- os.Exit(1)
- }
- defer tlsProvider.Close()
-
- logger.Info("TLS provider initialized",
- "mode", cfg.TLSMode,
- "spiffe_enabled", cfg.TLSMode == "spiffe")
-
- // Initialize OpenTelemetry
- var shutdown func(context.Context) error
- if cfg.OTELEnabled {
- shutdown, err = observability.InitProviders(ctx, cfg, version)
- if err != nil {
- logger.Error("failed to initialize observability", slog.String("error", err.Error()))
- os.Exit(1)
- }
- defer func() {
- shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer shutdownCancel()
- if shutdownErr := shutdown(shutdownCtx); shutdownErr != nil {
- logger.Error("failed to shutdown observability", slog.String("error", shutdownErr.Error()))
- }
- }()
- }
-
- // Initialize storage backend
- storageBackend, err := storage.NewBackend(cfg, logger)
- if err != nil {
- logger.Error("failed to initialize storage backend", slog.String("error", err.Error()))
- os.Exit(1)
- }
-
- // Initialize asset registry (SQLite database)
- assetRegistry, err := registry.New(cfg.DatabasePath, logger)
- if err != nil {
- logger.Error("failed to initialize asset registry", slog.String("error", err.Error()))
- os.Exit(1)
- }
- defer assetRegistry.Close()
-
- // Seed initial kernel assets if they don't exist
- if err := seedKernelAssets(assetRegistry, logger); err != nil {
- logger.Warn("failed to seed kernel assets", slog.String("error", err.Error()))
- // Don't exit - continue without kernel assets, they can be added later
- }
-
- // Initialize builderd client if enabled
- var builderdClient *builderd.Client
- if cfg.BuilderdEnabled {
- builderdCfg := &builderd.Config{
- Endpoint: cfg.BuilderdEndpoint,
- Timeout: cfg.BuilderdTimeout,
- MaxRetries: cfg.BuilderdMaxRetries,
- RetryDelay: cfg.BuilderdRetryDelay,
- TLSProvider: tlsProvider,
- }
-
- var err error
- builderdClient, err = builderd.NewClient(builderdCfg, logger)
- if err != nil {
- logger.Error("failed to create builderd client", slog.String("error", err.Error()))
- os.Exit(1)
- }
-
- logger.Info("builderd integration enabled",
- slog.String("endpoint", cfg.BuilderdEndpoint),
- slog.Bool("auto_register", cfg.BuilderdAutoRegister),
- )
- } else {
- logger.Info("builderd integration disabled")
- }
-
- // Create service
- assetService := service.New(cfg, logger, assetRegistry, storageBackend, builderdClient)
-
- // Start garbage collector if enabled
- if cfg.GCEnabled {
- go assetService.StartGarbageCollector(ctx)
- }
-
- // Configure shared interceptor options
- interceptorOpts := []interceptors.Option{
- interceptors.WithServiceName("assetmanagerd"),
- interceptors.WithLogger(logger),
- interceptors.WithActiveRequestsMetric(false), // Match existing behavior (no active requests metric)
- interceptors.WithRequestDurationMetric(true), // Match existing behavior
- interceptors.WithErrorResampling(true),
- interceptors.WithPanicStackTrace(true),
- }
-
- // Add meter if OpenTelemetry is enabled
- if cfg.OTELEnabled {
- interceptorOpts = append(interceptorOpts, interceptors.WithMeter(observability.GetMeter("assetmanagerd")))
- }
-
- // Get default interceptors (metrics, logging)
- sharedInterceptors := interceptors.NewDefaultInterceptors("assetmanagerd", interceptorOpts...)
-
- // Convert UnaryInterceptorFunc to Interceptor
- var interceptorList []connect.Interceptor
- for _, interceptor := range sharedInterceptors {
- interceptorList = append(interceptorList, connect.Interceptor(interceptor))
- }
-
- // Create ConnectRPC handler with shared interceptors
- path, handler := assetmanagerdv1connect.NewAssetManagerServiceHandler(
- assetService,
- connect.WithInterceptors(interceptorList...),
- )
-
- // Create HTTP server with OTEL instrumentation
- mux := http.NewServeMux()
- mux.Handle(path, handler)
-
- var httpHandler http.Handler = mux
- if cfg.OTELEnabled {
- httpHandler = otelhttp.NewHandler(mux, "assetmanagerd")
- }
-
- addr := fmt.Sprintf("%s:%d", cfg.Address, cfg.Port)
- server := &http.Server{
- Addr: addr,
- //nolint:exhaustruct // Default http2.Server configuration is sufficient
- Handler: h2c.NewHandler(httpHandler, &http2.Server{}),
- ReadTimeout: 30 * time.Second,
- WriteTimeout: 30 * time.Second,
- IdleTimeout: 120 * time.Second,
- }
-
- // Apply TLS configuration if enabled
- serverTLSConfig, _ := tlsProvider.ServerTLSConfig()
- if serverTLSConfig != nil {
- server.TLSConfig = serverTLSConfig
- }
-
- // Start Prometheus metrics server if enabled
- if cfg.OTELEnabled && cfg.OTELPrometheusEnabled {
- go func() {
- // AIDEV-NOTE: Use configured interface, defaulting to localhost for security
- metricsAddr := fmt.Sprintf("%s:%d", cfg.OTELPrometheusInterface, cfg.OTELPrometheusPort)
- healthHandler := healthpkg.Handler("assetmanagerd", getVersion(), startTime)
- metricsServer := observability.NewMetricsServer(metricsAddr, healthHandler)
- localhostOnly := cfg.OTELPrometheusInterface == "127.0.0.1" || cfg.OTELPrometheusInterface == "localhost"
- logger.Info("starting Prometheus metrics server",
- slog.String("addr", metricsAddr),
- slog.Bool("localhost_only", localhostOnly))
- if err := metricsServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
- logger.Error("metrics server error", slog.String("error", err.Error()))
- }
- }()
- }
-
- // Start server
- go func() {
- if serverTLSConfig != nil {
- // For TLS, we need to use regular handler, not h2c
- server.Handler = httpHandler
- logger.Info("starting HTTPS server with TLS",
- slog.String("addr", addr),
- slog.String("tls_mode", cfg.TLSMode),
- slog.String("storage_backend", cfg.StorageBackend),
- slog.String("database_path", cfg.DatabasePath),
- )
- // Empty strings for cert/key paths - SPIFFE provides them in memory
- if err := server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
- logger.Error("server error", slog.String("error", err.Error()))
- cancel()
- }
- } else {
- logger.Info("starting HTTP server without TLS",
- slog.String("addr", addr),
- slog.String("storage_backend", cfg.StorageBackend),
- slog.String("database_path", cfg.DatabasePath),
- )
- if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
- logger.Error("server error", slog.String("error", err.Error()))
- cancel()
- }
- }
- }()
-
- // Wait for shutdown signal
- select {
- case <-sigChan:
- logger.Info("received shutdown signal")
- case <-ctx.Done():
- logger.Info("context cancelled")
- }
-
- // Graceful shutdown
- shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
- defer shutdownCancel()
-
- logger.Info("shutting down server")
- if err := server.Shutdown(shutdownCtx); err != nil {
- logger.Error("server shutdown error", slog.String("error", err.Error()))
- }
-
- logger.Info("assetmanagerd stopped")
-}
-
-// seedKernelAssets automatically registers default kernel assets on startup
-func seedKernelAssets(assetRegistry *registry.Registry, logger *slog.Logger) error {
- kernelPath := "/opt/vm-assets/vmlinux"
-
- // Check if kernel file exists
- if _, err := os.Stat(kernelPath); os.IsNotExist(err) {
- logger.Info("kernel file not found, skipping kernel asset seeding",
- slog.String("path", kernelPath))
- return nil
- }
-
- // Check if kernel asset already exists
- filters := registry.ListFilters{
- Type: assetv1.AssetType_ASSET_TYPE_KERNEL,
- }
- existingAssets, err := assetRegistry.ListAssets(filters)
- if err != nil {
- return fmt.Errorf("failed to check existing kernel assets: %w", err)
- }
-
- if len(existingAssets) > 0 {
- logger.Info("kernel assets already exist, skipping seeding",
- slog.Int("existing_count", len(existingAssets)))
- return nil
- }
-
- // Calculate file info
- fileInfo, err := os.Stat(kernelPath)
- if err != nil {
- return fmt.Errorf("failed to stat kernel file: %w", err)
- }
-
- checksum, err := calculateChecksum(kernelPath)
- if err != nil {
- return fmt.Errorf("failed to calculate kernel checksum: %w", err)
- }
-
- // Create kernel asset
- asset := &assetv1.Asset{
- Name: "vmlinux",
- Type: assetv1.AssetType_ASSET_TYPE_KERNEL,
- Backend: assetv1.StorageBackend_STORAGE_BACKEND_LOCAL,
- Location: kernelPath,
- SizeBytes: fileInfo.Size(),
- Checksum: checksum,
- Status: assetv1.AssetStatus_ASSET_STATUS_AVAILABLE,
- Labels: map[string]string{
- "version": "5.10",
- "arch": "x86_64",
- "default": "true",
- },
- CreatedBy: "assetmanagerd-startup",
- }
-
- // Register the asset
- err = assetRegistry.CreateAsset(asset)
- if err != nil {
- return fmt.Errorf("failed to register kernel asset: %w", err)
- }
-
- logger.Info("seeded default kernel asset",
- slog.String("asset_id", asset.Id),
- slog.String("path", kernelPath),
- slog.Int64("size_bytes", fileInfo.Size()),
- slog.String("checksum", checksum))
-
- return nil
-}
-
-// calculateChecksum calculates SHA256 checksum of a file
-func calculateChecksum(path string) (string, error) {
- file, err := os.Open(path)
- if err != nil {
- return "", err
- }
- defer file.Close()
-
- hash := sha256.New()
- if _, err := io.Copy(hash, file); err != nil {
- return "", err
- }
-
- return fmt.Sprintf("%x", hash.Sum(nil)), nil
-}
diff --git a/go/apps/assetmanagerd/contrib/grafana-dashboards/.gitkeep b/go/apps/assetmanagerd/contrib/grafana-dashboards/.gitkeep
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/go/apps/assetmanagerd/contrib/systemd/assetmanagerd.service b/go/apps/assetmanagerd/contrib/systemd/assetmanagerd.service
deleted file mode 100644
index ade8aad3f6..0000000000
--- a/go/apps/assetmanagerd/contrib/systemd/assetmanagerd.service
+++ /dev/null
@@ -1,75 +0,0 @@
-[Unit]
-Description=AssetManagerd VM Asset Management Service
-Documentation=https://github.com/unkeyed/unkey/go/apps/assetmanagerd
-After=network.target spire-agent.service
-Wants=network.target
-Requires=spire-agent.service
-
-[Service]
-Type=simple
-# Running as root for cross-service file access
-User=root
-Group=root
-# AIDEV-NOTE: WorkingDirectory removed - not needed for assetmanagerd
-# Create required directories (+ prefix runs as root before dropping privileges)
-ExecStartPre=+/usr/bin/mkdir -p /opt/assetmanagerd/cache
-ExecStartPre=+/usr/bin/mkdir -p /opt/vm-assets
-ExecStartPre=+/usr/bin/mkdir -p /var/log/assetmanagerd
-# No ownership changes needed when running as root
-ExecStart=/usr/local/bin/assetmanagerd
-Restart=always
-RestartSec=5
-StandardOutput=journal
-StandardError=journal
-SyslogIdentifier=assetmanagerd
-
-# Environment variables
-Environment=UNKEY_ASSETMANAGERD_PORT=8083
-Environment=UNKEY_ASSETMANAGERD_ADDRESS=0.0.0.0
-
-# Storage configuration
-Environment=UNKEY_ASSETMANAGERD_STORAGE_BACKEND=local
-Environment=UNKEY_ASSETMANAGERD_LOCAL_STORAGE_PATH=/opt/builderd/rootfs
-Environment=UNKEY_ASSETMANAGERD_DATABASE_PATH=/opt/assetmanagerd/assets.db
-Environment=UNKEY_ASSETMANAGERD_CACHE_DIR=/opt/assetmanagerd/cache
-
-# Garbage collection
-Environment=UNKEY_ASSETMANAGERD_GC_ENABLED=true
-Environment=UNKEY_ASSETMANAGERD_GC_INTERVAL=1h
-Environment=UNKEY_ASSETMANAGERD_GC_MAX_AGE=168h
-
-# OpenTelemetry Configuration
-Environment=UNKEY_ASSETMANAGERD_OTEL_ENABLED=true
-Environment=UNKEY_ASSETMANAGERD_OTEL_SERVICE_NAME=assetmanagerd
-Environment=UNKEY_ASSETMANAGERD_OTEL_SERVICE_VERSION=0.2.0
-Environment=UNKEY_ASSETMANAGERD_OTEL_SAMPLING_RATE=1.0
-Environment=UNKEY_ASSETMANAGERD_OTEL_ENDPOINT=localhost:4318
-Environment=UNKEY_ASSETMANAGERD_OTEL_PROMETHEUS_ENABLED=true
-Environment=UNKEY_ASSETMANAGERD_OTEL_PROMETHEUS_PORT=9467
-
-# TLS/SPIFFE configuration (REQUIRED)
-# AIDEV-BUSINESS_RULE: mTLS is required for secure inter-service communication
-Environment=UNKEY_ASSETMANAGERD_TLS_MODE=spiffe
-Environment=UNKEY_ASSETMANAGERD_SPIFFE_SOCKET=/var/lib/spire/agent/agent.sock
-
-# Builderd integration configuration
-# AIDEV-NOTE: Enables automatic rootfs building when assets don't exist
-Environment=UNKEY_ASSETMANAGERD_BUILDERD_ENABLED=true
-Environment=UNKEY_ASSETMANAGERD_BUILDERD_ENDPOINT=https://localhost:8082
-Environment=UNKEY_ASSETMANAGERD_BUILDERD_TIMEOUT=30m
-Environment=UNKEY_ASSETMANAGERD_BUILDERD_AUTO_REGISTER=true
-Environment=UNKEY_ASSETMANAGERD_BUILDERD_MAX_RETRIES=3
-Environment=UNKEY_ASSETMANAGERD_BUILDERD_RETRY_DELAY=5s
-
-# Resource limits
-LimitNOFILE=65536
-LimitNPROC=4096
-
-# Basic security settings (removed strict namespace protection)
-# AIDEV-NOTE: Namespace protection removed to simplify deployment
-# The service runs as root for filesystem operations
-NoNewPrivileges=true
-PrivateTmp=true
-
-[Install]
-WantedBy=multi-user.target
diff --git a/go/apps/assetmanagerd/environment.example b/go/apps/assetmanagerd/environment.example
deleted file mode 100644
index 7158d95244..0000000000
--- a/go/apps/assetmanagerd/environment.example
+++ /dev/null
@@ -1,63 +0,0 @@
-# AssetManagerd Environment Variables Template
-# NOTE: This service does NOT load .env files automatically
-# Set these variables in your system environment or process manager
-#
-# Usage examples:
-# systemd: EnvironmentFile=/etc/assetmanagerd/environment
-# Docker: docker run --env-file environment assetmanagerd
-# Shell: set -a; source environment; set +a; ./assetmanagerd
-
-# Service Configuration
-UNKEY_ASSETMANAGERD_PORT=8083
-UNKEY_ASSETMANAGERD_ADDRESS=0.0.0.0
-
-# Storage Configuration
-UNKEY_ASSETMANAGERD_STORAGE_BACKEND=local
-UNKEY_ASSETMANAGERD_LOCAL_STORAGE_PATH=/opt/vm-assets
-UNKEY_ASSETMANAGERD_DATABASE_PATH=/opt/assetmanagerd/assets.db
-UNKEY_ASSETMANAGERD_CACHE_DIR=/opt/assetmanagerd/cache
-
-# S3 Storage Configuration (when STORAGE_BACKEND=s3)
-UNKEY_ASSETMANAGERD_S3_BUCKET=
-UNKEY_ASSETMANAGERD_S3_REGION=us-east-1
-UNKEY_ASSETMANAGERD_S3_ENDPOINT=
-UNKEY_ASSETMANAGERD_S3_ACCESS_KEY_ID=
-UNKEY_ASSETMANAGERD_S3_SECRET_ACCESS_KEY=
-
-# Asset Management
-UNKEY_ASSETMANAGERD_MAX_ASSET_SIZE=10737418240
-UNKEY_ASSETMANAGERD_MAX_CACHE_SIZE=107374182400
-UNKEY_ASSETMANAGERD_ASSET_TTL=0
-UNKEY_ASSETMANAGERD_DOWNLOAD_CONCURRENCY=4
-UNKEY_ASSETMANAGERD_DOWNLOAD_TIMEOUT=30m
-
-# Garbage Collection
-UNKEY_ASSETMANAGERD_GC_ENABLED=true
-UNKEY_ASSETMANAGERD_GC_INTERVAL=1h
-UNKEY_ASSETMANAGERD_GC_MAX_AGE=168h
-UNKEY_ASSETMANAGERD_GC_MIN_REFERENCES=0
-
-# Builderd Integration
-UNKEY_ASSETMANAGERD_BUILDERD_ENABLED=true
-UNKEY_ASSETMANAGERD_BUILDERD_ENDPOINT=https://localhost:8082
-UNKEY_ASSETMANAGERD_BUILDERD_TIMEOUT=30m
-UNKEY_ASSETMANAGERD_BUILDERD_AUTO_REGISTER=true
-UNKEY_ASSETMANAGERD_BUILDERD_MAX_RETRIES=3
-UNKEY_ASSETMANAGERD_BUILDERD_RETRY_DELAY=5s
-
-# TLS Configuration
-UNKEY_ASSETMANAGERD_TLS_MODE=spiffe
-UNKEY_ASSETMANAGERD_SPIFFE_SOCKET=/var/lib/spire/agent/agent.sock
-UNKEY_ASSETMANAGERD_TLS_CERT_FILE=
-UNKEY_ASSETMANAGERD_TLS_KEY_FILE=
-UNKEY_ASSETMANAGERD_TLS_CA_FILE=
-
-# OpenTelemetry Configuration
-UNKEY_ASSETMANAGERD_OTEL_ENABLED=true
-UNKEY_ASSETMANAGERD_OTEL_SERVICE_NAME=assetmanagerd
-UNKEY_ASSETMANAGERD_OTEL_SERVICE_VERSION=0.2.0
-UNKEY_ASSETMANAGERD_OTEL_SAMPLING_RATE=1.0
-UNKEY_ASSETMANAGERD_OTEL_ENDPOINT=localhost:4318
-UNKEY_ASSETMANAGERD_OTEL_PROMETHEUS_ENABLED=true
-UNKEY_ASSETMANAGERD_OTEL_PROMETHEUS_PORT=9467
-UNKEY_ASSETMANAGERD_OTEL_PROMETHEUS_INTERFACE=127.0.0.1
\ No newline at end of file
diff --git a/go/apps/assetmanagerd/internal/builderd/client.go b/go/apps/assetmanagerd/internal/builderd/client.go
deleted file mode 100644
index 697ccd622e..0000000000
--- a/go/apps/assetmanagerd/internal/builderd/client.go
+++ /dev/null
@@ -1,224 +0,0 @@
-package builderd
-
-import (
- "context"
- "fmt"
- "log/slog"
- "time"
-
- "connectrpc.com/connect"
- tlspkg "github.com/unkeyed/unkey/go/deploy/pkg/tls"
- builderv1 "github.com/unkeyed/unkey/go/gen/proto/builderd/v1"
- "github.com/unkeyed/unkey/go/gen/proto/builderd/v1/builderdv1connect"
- "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
-)
-
-// Config holds the configuration for the builderd client
-type Config struct {
- Endpoint string
- Timeout time.Duration
- MaxRetries int
- RetryDelay time.Duration
- TLSProvider tlspkg.Provider
-}
-
-// BuildState represents the state of a build
-type BuildState int
-
-const (
- BuildStatePending BuildState = iota
- BuildStateRunning
- BuildStateCompleted
- BuildStateFailed
-)
-
-func (s BuildState) String() string {
- switch s {
- case BuildStatePending:
- return "pending"
- case BuildStateRunning:
- return "running"
- case BuildStateCompleted:
- return "completed"
- case BuildStateFailed:
- return "failed"
- default:
- return "unknown"
- }
-}
-
-// Build represents a build
-type Build struct {
- BuildId string
- State BuildState
- RootfsPath string
-}
-
-// CompletedBuild represents a completed build
-type CompletedBuild struct {
- Build *Build
-}
-
-// Client is a client for the builderd service
-type Client struct {
- cfg *Config
- logger *slog.Logger
- builderClient builderdv1connect.BuilderServiceClient
-}
-
-// NewClient creates a new builderd client
-func NewClient(cfg *Config, logger *slog.Logger) (*Client, error) {
- // Get HTTP client with TLS configuration
- httpClient := cfg.TLSProvider.HTTPClient()
-
- // Wrap with OpenTelemetry instrumentation for trace propagation
- httpClient.Transport = otelhttp.NewTransport(httpClient.Transport)
-
- // Create Connect client
- builderClient := builderdv1connect.NewBuilderServiceClient(
- httpClient,
- cfg.Endpoint,
- )
-
- logger.Info("initialized builderd client",
- slog.String("endpoint", cfg.Endpoint),
- )
-
- return &Client{
- cfg: cfg,
- logger: logger.With("component", "builderd-client"),
- builderClient: builderClient,
- }, nil
-}
-
-// BuildDockerRootfs triggers a docker rootfs build with options
-func (c *Client) BuildDockerRootfs(ctx context.Context, dockerImage string, labels map[string]string) (string, error) {
- // AIDEV-NOTE: Implemented builderd client method for automatic builds
- c.logger.InfoContext(ctx, "triggering docker rootfs build",
- slog.String("docker_image", dockerImage))
-
- // Create build request
- req := &builderv1.CreateBuildRequest{
- Config: &builderv1.BuildConfig{
- Source: &builderv1.BuildSource{
- SourceType: &builderv1.BuildSource_DockerImage{
- DockerImage: &builderv1.DockerImageSource{
- ImageUri: dockerImage,
- },
- },
- },
- Target: &builderv1.BuildTarget{
- TargetType: &builderv1.BuildTarget_MicrovmRootfs{
- MicrovmRootfs: &builderv1.MicroVMRootfs{
- InitStrategy: builderv1.InitStrategy_INIT_STRATEGY_TINI,
- },
- },
- },
- Strategy: &builderv1.BuildStrategy{
- StrategyType: &builderv1.BuildStrategy_DockerExtract{
- DockerExtract: &builderv1.DockerExtractStrategy{
- FlattenFilesystem: true,
- },
- },
- },
- Labels: labels,
- },
- }
-
- // Make the request with timeout context
- ctxWithTimeout, cancel := context.WithTimeout(ctx, c.cfg.Timeout)
- defer cancel()
-
- connectReq := connect.NewRequest(req)
-
- resp, err := c.builderClient.CreateBuild(ctxWithTimeout, connectReq)
- if err != nil {
- c.logger.ErrorContext(ctx, "failed to create build",
- slog.String("docker_image", dockerImage),
- slog.String("error", err.Error()),
- )
- return "", fmt.Errorf("failed to create build: %w", err)
- }
-
- buildID := resp.Msg.GetBuildId()
- c.logger.InfoContext(ctx, "build created successfully",
- slog.String("build_id", buildID),
- slog.String("docker_image", dockerImage),
- slog.String("state", resp.Msg.GetState().String()),
- )
-
- return buildID, nil
-}
-
-// WaitForBuild waits for a build to complete
-func (c *Client) WaitForBuild(ctx context.Context, buildID string, timeout time.Duration) (*CompletedBuild, error) {
- c.logger.InfoContext(ctx, "waiting for build to complete",
- slog.String("build_id", buildID),
- slog.Duration("timeout", timeout),
- )
-
- // Create context with timeout
- ctxWithTimeout, cancel := context.WithTimeout(ctx, timeout)
- defer cancel()
-
- // Poll for completion
- ticker := time.NewTicker(5 * time.Second)
- defer ticker.Stop()
-
- for {
- select {
- case <-ctxWithTimeout.Done():
- return nil, fmt.Errorf("timeout waiting for build %s to complete: %w", buildID, ctx.Err())
- case <-ticker.C:
- // Check build status
- req := &builderv1.GetBuildRequest{
- BuildId: buildID,
- }
-
- connectReq := connect.NewRequest(req)
- resp, err := c.builderClient.GetBuild(ctxWithTimeout, connectReq)
- if err != nil {
- c.logger.WarnContext(ctx, "failed to get build status",
- slog.String("build_id", buildID),
- slog.String("error", err.Error()),
- )
- continue
- }
-
- build := resp.Msg.GetBuild()
- c.logger.DebugContext(ctx, "build status update",
- slog.String("build_id", buildID),
- slog.String("state", build.GetState().String()),
- )
-
- switch build.GetState() {
- case builderv1.BuildState_BUILD_STATE_COMPLETED:
- c.logger.InfoContext(ctx, "build completed successfully",
- slog.String("build_id", buildID),
- slog.String("rootfs_path", build.GetRootfsPath()),
- )
- return &CompletedBuild{
- Build: &Build{
- BuildId: buildID,
- State: BuildStateCompleted,
- RootfsPath: build.GetRootfsPath(),
- },
- }, nil
- case builderv1.BuildState_BUILD_STATE_FAILED:
- c.logger.ErrorContext(ctx, "build failed",
- slog.String("build_id", buildID),
- slog.String("error", build.GetErrorMessage()),
- )
- return nil, fmt.Errorf("build %s failed: %s", buildID, build.GetErrorMessage())
- case builderv1.BuildState_BUILD_STATE_CANCELLED:
- c.logger.WarnContext(ctx, "build was cancelled",
- slog.String("build_id", buildID),
- )
- return nil, fmt.Errorf("build %s was cancelled", buildID)
- default:
- // Build still in progress, continue polling
- continue
- }
- }
- }
-}
diff --git a/go/apps/assetmanagerd/internal/config/config.go b/go/apps/assetmanagerd/internal/config/config.go
deleted file mode 100644
index 9fb3ded4d1..0000000000
--- a/go/apps/assetmanagerd/internal/config/config.go
+++ /dev/null
@@ -1,152 +0,0 @@
-package config
-
-import (
- "fmt"
- "time"
-
- "github.com/caarlos0/env/v11"
-)
-
-// Config represents the complete configuration for assetmanagerd
-type Config struct {
- // Service configuration
- Port int `env:"UNKEY_ASSETMANAGERD_PORT" envDefault:"8083"`
- Address string `env:"UNKEY_ASSETMANAGERD_ADDRESS" envDefault:"0.0.0.0"`
-
- // Storage configuration
- StorageBackend string `env:"UNKEY_ASSETMANAGERD_STORAGE_BACKEND" envDefault:"local"` // local, s3, nfs
- LocalStoragePath string `env:"UNKEY_ASSETMANAGERD_LOCAL_STORAGE_PATH" envDefault:"/opt/vm-assets"`
- DatabasePath string `env:"UNKEY_ASSETMANAGERD_DATABASE_PATH" envDefault:"/opt/assetmanagerd/assets.db"`
- CacheDir string `env:"UNKEY_ASSETMANAGERD_CACHE_DIR" envDefault:"/opt/assetmanagerd/cache"`
-
- // S3 configuration (if backend is s3)
- S3Bucket string `env:"UNKEY_ASSETMANAGERD_S3_BUCKET"`
- S3Region string `env:"UNKEY_ASSETMANAGERD_S3_REGION" envDefault:"us-east-1"`
- S3Endpoint string `env:"UNKEY_ASSETMANAGERD_S3_ENDPOINT"` // For S3-compatible services
- S3AccessKeyID string `env:"UNKEY_ASSETMANAGERD_S3_ACCESS_KEY_ID"`
- S3SecretAccessKey string `env:"UNKEY_ASSETMANAGERD_S3_SECRET_ACCESS_KEY"`
-
- // Garbage collection configuration
- GCEnabled bool `env:"UNKEY_ASSETMANAGERD_GC_ENABLED" envDefault:"true"`
- GCInterval time.Duration `env:"UNKEY_ASSETMANAGERD_GC_INTERVAL" envDefault:"1h"`
- GCMaxAge time.Duration `env:"UNKEY_ASSETMANAGERD_GC_MAX_AGE" envDefault:"168h"` // 7 days
- GCMinReferences int `env:"UNKEY_ASSETMANAGERD_GC_MIN_REFERENCES" envDefault:"0"`
-
- // Asset limits
- MaxAssetSize int64 `env:"UNKEY_ASSETMANAGERD_MAX_ASSET_SIZE" envDefault:"10737418240"` // 10GB
- MaxCacheSize int64 `env:"UNKEY_ASSETMANAGERD_MAX_CACHE_SIZE" envDefault:"107374182400"` // 100GB
- AssetTTL time.Duration `env:"UNKEY_ASSETMANAGERD_ASSET_TTL" envDefault:"0"` // 0 = no TTL
-
- // Performance tuning
- DownloadConcurrency int `env:"UNKEY_ASSETMANAGERD_DOWNLOAD_CONCURRENCY" envDefault:"4"`
- DownloadTimeout time.Duration `env:"UNKEY_ASSETMANAGERD_DOWNLOAD_TIMEOUT" envDefault:"30m"`
-
- // OpenTelemetry configuration
- OTELEnabled bool `env:"UNKEY_ASSETMANAGERD_OTEL_ENABLED" envDefault:"true"`
- OTELServiceName string `env:"UNKEY_ASSETMANAGERD_OTEL_SERVICE_NAME" envDefault:"assetmanagerd"`
- OTELServiceVersion string `env:"UNKEY_ASSETMANAGERD_OTEL_SERVICE_VERSION" envDefault:"0.2.0"`
- OTELEndpoint string `env:"UNKEY_ASSETMANAGERD_OTEL_ENDPOINT" envDefault:"localhost:4318"`
- OTELSamplingRate float64 `env:"UNKEY_ASSETMANAGERD_OTEL_SAMPLING_RATE" envDefault:"1.0"`
- OTELPrometheusPort int `env:"UNKEY_ASSETMANAGERD_OTEL_PROMETHEUS_PORT" envDefault:"9467"`
- OTELPrometheusEnabled bool `env:"UNKEY_ASSETMANAGERD_OTEL_PROMETHEUS_ENABLED" envDefault:"true"`
- OTELPrometheusInterface string `env:"UNKEY_ASSETMANAGERD_OTEL_PROMETHEUS_INTERFACE" envDefault:"127.0.0.1"`
-
- // TLS configuration
- // AIDEV-BUSINESS_RULE: SPIFFE/mTLS is required by default for security - no fallback to disabled mode
- TLSMode string `env:"UNKEY_ASSETMANAGERD_TLS_MODE" envDefault:"spiffe"`
- TLSCertFile string `env:"UNKEY_ASSETMANAGERD_TLS_CERT_FILE"`
- TLSKeyFile string `env:"UNKEY_ASSETMANAGERD_TLS_KEY_FILE"`
- TLSCAFile string `env:"UNKEY_ASSETMANAGERD_TLS_CA_FILE"`
- TLSSPIFFESocketPath string `env:"UNKEY_ASSETMANAGERD_SPIFFE_SOCKET" envDefault:"/var/lib/spire/agent/agent.sock"`
-
- // Builderd integration configuration
- // AIDEV-NOTE: When enabled, assetmanagerd will automatically trigger builderd to create missing assets
- BuilderdEnabled bool `env:"UNKEY_ASSETMANAGERD_BUILDERD_ENABLED" envDefault:"true"`
- BuilderdEndpoint string `env:"UNKEY_ASSETMANAGERD_BUILDERD_ENDPOINT" envDefault:"https://localhost:8082"`
- BuilderdTimeout time.Duration `env:"UNKEY_ASSETMANAGERD_BUILDERD_TIMEOUT" envDefault:"30m"`
- BuilderdAutoRegister bool `env:"UNKEY_ASSETMANAGERD_BUILDERD_AUTO_REGISTER" envDefault:"true"`
- BuilderdMaxRetries int `env:"UNKEY_ASSETMANAGERD_BUILDERD_MAX_RETRIES" envDefault:"3"`
- BuilderdRetryDelay time.Duration `env:"UNKEY_ASSETMANAGERD_BUILDERD_RETRY_DELAY" envDefault:"5s"`
-}
-
-// Load loads configuration from environment variables
-func Load() (*Config, error) {
- //nolint:exhaustruct // Config fields will be populated by environment variables
- cfg := &Config{}
- if err := env.Parse(cfg); err != nil {
- return nil, fmt.Errorf("failed to parse config: %w", err)
- }
-
- // Validate configuration
- if err := cfg.Validate(); err != nil {
- return nil, fmt.Errorf("invalid configuration: %w", err)
- }
-
- return cfg, nil
-}
-
-// Validate validates the configuration
-func (c *Config) Validate() error {
- // AIDEV-NOTE: Comprehensive validation ensures service reliability from startup
- if c.Port < 1 || c.Port > 65535 {
- return fmt.Errorf("invalid port: %d", c.Port)
- }
-
- if c.OTELPrometheusPort < 1 || c.OTELPrometheusPort > 65535 {
- return fmt.Errorf("invalid prometheus port: %d", c.OTELPrometheusPort)
- }
-
- // Validate storage backend
- switch c.StorageBackend {
- case "local":
- if c.LocalStoragePath == "" {
- return fmt.Errorf("local storage path is required for local backend")
- }
- case "s3":
- if c.S3Bucket == "" {
- return fmt.Errorf("S3 bucket is required for s3 backend")
- }
- if c.S3AccessKeyID == "" || c.S3SecretAccessKey == "" {
- return fmt.Errorf("S3 credentials are required for s3 backend")
- }
- case "nfs":
- // NFS validation would go here
- return fmt.Errorf("NFS backend not yet implemented")
- default:
- return fmt.Errorf("unsupported storage backend: %s", c.StorageBackend)
- }
-
- // Validate GC settings
- if c.GCEnabled && c.GCInterval < time.Minute {
- return fmt.Errorf("GC interval must be at least 1 minute")
- }
-
- // Validate size limits
- if c.MaxAssetSize <= 0 {
- return fmt.Errorf("max asset size must be positive")
- }
-
- if c.MaxCacheSize < c.MaxAssetSize {
- return fmt.Errorf("max cache size must be at least as large as max asset size")
- }
-
- // Validate OTEL settings
- if c.OTELEnabled && c.OTELSamplingRate < 0 || c.OTELSamplingRate > 1 {
- return fmt.Errorf("OTEL sampling rate must be between 0 and 1")
- }
-
- // Validate builderd configuration
- if c.BuilderdEnabled {
- if c.BuilderdEndpoint == "" {
- return fmt.Errorf("builderd endpoint is required when builderd integration is enabled")
- }
- if c.BuilderdTimeout < time.Minute {
- return fmt.Errorf("builderd timeout must be at least 1 minute")
- }
- if c.BuilderdMaxRetries < 0 {
- return fmt.Errorf("builderd max retries must be non-negative")
- }
- }
-
- return nil
-}
diff --git a/go/apps/assetmanagerd/internal/observability/otel.go b/go/apps/assetmanagerd/internal/observability/otel.go
deleted file mode 100644
index f558096d5c..0000000000
--- a/go/apps/assetmanagerd/internal/observability/otel.go
+++ /dev/null
@@ -1,169 +0,0 @@
-package observability
-
-import (
- "context"
- "fmt"
- "net/http"
- "time"
-
- "github.com/unkeyed/unkey/go/apps/assetmanagerd/internal/config"
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
- "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
- "go.opentelemetry.io/otel/exporters/prometheus"
- "go.opentelemetry.io/otel/metric"
- "go.opentelemetry.io/otel/propagation"
- sdkmetric "go.opentelemetry.io/otel/sdk/metric"
- "go.opentelemetry.io/otel/sdk/resource"
- sdktrace "go.opentelemetry.io/otel/sdk/trace"
- semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
- "go.opentelemetry.io/otel/trace"
-)
-
-// InitProviders initializes OpenTelemetry providers
-func InitProviders(ctx context.Context, cfg *config.Config, version string) (func(context.Context) error, error) {
- // AIDEV-NOTE: Dynamic version injection for unified telemetry
- // Schema conflict fix - Using semconv v1.26.0
- res, err := resource.New(ctx,
- resource.WithAttributes(
- ServiceAttributes(cfg.OTELServiceName, version)...,
- ),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create resource: %w", err)
- }
-
- // Initialize trace provider
- traceProvider, err := initTraceProvider(ctx, cfg, res)
- if err != nil {
- return nil, fmt.Errorf("failed to initialize trace provider: %w", err)
- }
-
- // Initialize metric provider
- metricProvider, err := initMetricProvider(ctx, cfg, res)
- if err != nil {
- _ = traceProvider.Shutdown(ctx)
- return nil, fmt.Errorf("failed to initialize metric provider: %w", err)
- }
-
- // Set global providers
- otel.SetTracerProvider(traceProvider)
- otel.SetMeterProvider(metricProvider)
- otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
- propagation.TraceContext{},
- propagation.Baggage{},
- ))
-
- // Return shutdown function
- return func(ctx context.Context) error {
- err := traceProvider.Shutdown(ctx)
- if err != nil {
- return fmt.Errorf("failed to shutdown trace provider: %w", err)
- }
-
- err = metricProvider.Shutdown(ctx)
- if err != nil {
- return fmt.Errorf("failed to shutdown metric provider: %w", err)
- }
-
- return nil
- }, nil
-}
-
-// initTraceProvider initializes the trace provider
-func initTraceProvider(ctx context.Context, cfg *config.Config, res *resource.Resource) (*sdktrace.TracerProvider, error) {
- exporter, err := otlptracehttp.New(ctx,
- otlptracehttp.WithEndpoint(cfg.OTELEndpoint),
- otlptracehttp.WithInsecure(),
- otlptracehttp.WithTimeout(30*time.Second),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create trace exporter: %w", err)
- }
-
- provider := sdktrace.NewTracerProvider(
- sdktrace.WithBatcher(exporter),
- sdktrace.WithResource(res),
- sdktrace.WithSampler(sdktrace.TraceIDRatioBased(cfg.OTELSamplingRate)),
- )
-
- return provider, nil
-}
-
-// initMetricProvider initializes the metric provider
-func initMetricProvider(ctx context.Context, cfg *config.Config, res *resource.Resource) (*sdkmetric.MeterProvider, error) {
- var readers []sdkmetric.Reader
-
- // OTLP metric exporter
- exporter, err := otlpmetrichttp.New(ctx,
- otlpmetrichttp.WithEndpoint(cfg.OTELEndpoint),
- otlpmetrichttp.WithInsecure(),
- otlpmetrichttp.WithTimeout(30*time.Second),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create metric exporter: %w", err)
- }
-
- readers = append(readers, sdkmetric.NewPeriodicReader(exporter,
- sdkmetric.WithInterval(10*time.Second),
- ))
-
- // Prometheus exporter
- if cfg.OTELPrometheusEnabled {
- promExporter, err := prometheus.New()
- if err != nil {
- return nil, fmt.Errorf("failed to create prometheus exporter: %w", err)
- }
- readers = append(readers, promExporter)
- }
-
- opts := []sdkmetric.Option{
- sdkmetric.WithResource(res),
- }
- for _, reader := range readers {
- opts = append(opts, sdkmetric.WithReader(reader))
- }
-
- provider := sdkmetric.NewMeterProvider(opts...)
-
- return provider, nil
-}
-
-// ServiceAttributes returns OTEL resource attributes for the service
-func ServiceAttributes(serviceName, version string) []attribute.KeyValue {
- // AIDEV-NOTE: Dynamic version parameter for unified telemetry
- return []attribute.KeyValue{
- semconv.ServiceName(serviceName),
- semconv.ServiceVersion(version),
- attribute.String("service.namespace", "unkey"),
- attribute.String("service.instance.id", serviceName),
- }
-}
-
-// NewMetricsServer creates a new HTTP server for Prometheus metrics
-func NewMetricsServer(addr string, healthHandler http.HandlerFunc) *http.Server {
- mux := http.NewServeMux()
- mux.Handle("/metrics", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- // The prometheus handler is registered globally
- http.DefaultServeMux.ServeHTTP(w, r)
- }))
- mux.HandleFunc("/health", healthHandler)
-
- return &http.Server{
- Addr: addr,
- Handler: mux,
- ReadTimeout: 10 * time.Second,
- WriteTimeout: 10 * time.Second,
- }
-}
-
-// GetTracer returns a tracer for the given name
-func GetTracer(name string) trace.Tracer {
- return otel.Tracer(name)
-}
-
-// GetMeter returns a meter for the given name
-func GetMeter(name string) metric.Meter {
- return otel.Meter(name)
-}
diff --git a/go/apps/assetmanagerd/internal/registry/registry.go b/go/apps/assetmanagerd/internal/registry/registry.go
deleted file mode 100644
index 82bec5e08e..0000000000
--- a/go/apps/assetmanagerd/internal/registry/registry.go
+++ /dev/null
@@ -1,521 +0,0 @@
-package registry
-
-import (
- "database/sql"
- "fmt"
- "log/slog"
- "os"
- "path/filepath"
- "time"
-
- "github.com/oklog/ulid/v2"
- assetv1 "github.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1"
- _ "modernc.org/sqlite"
-)
-
-// Registry manages asset metadata in SQLite
-type Registry struct {
- db *sql.DB
- logger *slog.Logger
-}
-
-// New creates a new asset registry
-func New(dbPath string, logger *slog.Logger) (*Registry, error) {
- // Ensure directory exists
- dir := filepath.Dir(dbPath)
- if err := os.MkdirAll(dir, 0755); err != nil {
- return nil, fmt.Errorf("failed to create database directory: %w", err)
- }
-
- // Open database
- db, err := sql.Open("sqlite", dbPath+"?_journal_mode=WAL&_synchronous=NORMAL")
- if err != nil {
- return nil, fmt.Errorf("failed to open database: %w", err)
- }
-
- // Set connection pool settings
- db.SetMaxOpenConns(10)
- db.SetMaxIdleConns(5)
- db.SetConnMaxLifetime(time.Hour)
-
- r := &Registry{
- db: db,
- logger: logger.With("component", "registry"),
- }
-
- // Initialize schema
- if err := r.initSchema(); err != nil {
- db.Close()
- return nil, fmt.Errorf("failed to initialize schema: %w", err)
- }
-
- return r, nil
-}
-
-// Close closes the registry
-func (r *Registry) Close() error {
- return r.db.Close()
-}
-
-// initSchema creates the database schema
-func (r *Registry) initSchema() error {
- schema := `
- CREATE TABLE IF NOT EXISTS assets (
- id TEXT PRIMARY KEY,
- name TEXT NOT NULL,
- type INTEGER NOT NULL,
- status INTEGER NOT NULL,
- backend INTEGER NOT NULL,
- location TEXT NOT NULL,
- size_bytes INTEGER NOT NULL,
- checksum TEXT NOT NULL,
- created_by TEXT NOT NULL,
- created_at INTEGER NOT NULL,
- last_accessed_at INTEGER NOT NULL,
- reference_count INTEGER NOT NULL DEFAULT 0,
- build_id TEXT,
- source_image TEXT
- );
-
- CREATE INDEX IF NOT EXISTS idx_assets_type ON assets(type);
- CREATE INDEX IF NOT EXISTS idx_assets_status ON assets(status);
- CREATE INDEX IF NOT EXISTS idx_assets_created_at ON assets(created_at);
- CREATE INDEX IF NOT EXISTS idx_assets_last_accessed_at ON assets(last_accessed_at);
- CREATE INDEX IF NOT EXISTS idx_assets_reference_count ON assets(reference_count);
- CREATE INDEX IF NOT EXISTS idx_assets_build_id ON assets(build_id);
-
- CREATE TABLE IF NOT EXISTS asset_labels (
- asset_id TEXT NOT NULL,
- key TEXT NOT NULL,
- value TEXT NOT NULL,
- PRIMARY KEY (asset_id, key),
- FOREIGN KEY (asset_id) REFERENCES assets(id) ON DELETE CASCADE
- );
-
- CREATE INDEX IF NOT EXISTS idx_asset_labels_key_value ON asset_labels(key, value);
-
- CREATE TABLE IF NOT EXISTS asset_leases (
- id TEXT PRIMARY KEY,
- asset_id TEXT NOT NULL,
- acquired_by TEXT NOT NULL,
- acquired_at INTEGER NOT NULL,
- expires_at INTEGER,
- FOREIGN KEY (asset_id) REFERENCES assets(id) ON DELETE CASCADE
- );
-
- CREATE INDEX IF NOT EXISTS idx_asset_leases_asset_id ON asset_leases(asset_id);
- CREATE INDEX IF NOT EXISTS idx_asset_leases_expires_at ON asset_leases(expires_at);
- `
-
- if _, err := r.db.Exec(schema); err != nil {
- return fmt.Errorf("failed to create schema: %w", err)
- }
-
- return nil
-}
-
-// CreateAsset creates a new asset record
-func (r *Registry) CreateAsset(asset *assetv1.Asset) error {
- // Generate ID if not provided
- if asset.GetId() == "" {
- asset.Id = ulid.Make().String()
- }
-
- tx, err := r.db.Begin()
- if err != nil {
- return fmt.Errorf("failed to begin transaction: %w", err)
- }
- defer func() { _ = tx.Rollback() }()
-
- // Insert asset
- query := `
- INSERT INTO assets (
- id, name, type, status, backend, location, size_bytes, checksum,
- created_by, created_at, last_accessed_at, reference_count,
- build_id, source_image
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- `
-
- _, err = tx.Exec(query,
- asset.GetId(), asset.GetName(), asset.GetType(), asset.GetStatus(), asset.GetBackend(),
- asset.GetLocation(), asset.GetSizeBytes(), asset.GetChecksum(),
- asset.GetCreatedBy(), asset.GetCreatedAt(), asset.GetLastAccessedAt(), asset.GetReferenceCount(),
- asset.GetBuildId(), asset.GetSourceImage(),
- )
- if err != nil {
- return fmt.Errorf("failed to insert asset: %w", err)
- }
-
- // Insert labels
- for key, value := range asset.GetLabels() {
- _, err = tx.Exec(
- "INSERT INTO asset_labels (asset_id, key, value) VALUES (?, ?, ?)",
- asset.GetId(), key, value,
- )
- if err != nil {
- return fmt.Errorf("failed to insert label %s=%s: %w", key, value, err)
- }
- }
-
- if err := tx.Commit(); err != nil {
- return fmt.Errorf("failed to commit transaction: %w", err)
- }
-
- r.logger.Info("created asset",
- slog.String("id", asset.GetId()),
- slog.String("name", asset.GetName()),
- slog.String("type", asset.GetType().String()),
- )
-
- return nil
-}
-
-// GetAsset retrieves an asset by ID
-func (r *Registry) GetAsset(id string) (*assetv1.Asset, error) {
- //nolint:exhaustruct // Asset fields will be populated from database
- asset := &assetv1.Asset{
- Labels: make(map[string]string),
- }
-
- // Get asset
- query := `
- SELECT name, type, status, backend, location, size_bytes, checksum,
- created_by, created_at, last_accessed_at, reference_count,
- build_id, source_image
- FROM assets WHERE id = ?
- `
-
- err := r.db.QueryRow(query, id).Scan(
- &asset.Name, &asset.Type, &asset.Status, &asset.Backend,
- &asset.Location, &asset.SizeBytes, &asset.Checksum,
- &asset.CreatedBy, &asset.CreatedAt, &asset.LastAccessedAt, &asset.ReferenceCount,
- &asset.BuildId, &asset.SourceImage,
- )
- if err != nil {
- if err == sql.ErrNoRows {
- return nil, fmt.Errorf("asset not found: %s", id)
- }
- return nil, fmt.Errorf("failed to get asset: %w", err)
- }
-
- asset.Id = id
-
- // Get labels
- rows, err := r.db.Query("SELECT key, value FROM asset_labels WHERE asset_id = ?", id)
- if err != nil {
- return nil, fmt.Errorf("failed to get labels: %w", err)
- }
- defer rows.Close()
-
- for rows.Next() {
- var key, value string
- if err := rows.Scan(&key, &value); err != nil {
- return nil, fmt.Errorf("failed to scan label: %w", err)
- }
- asset.Labels[key] = value
- }
- if err := rows.Err(); err != nil {
- return nil, fmt.Errorf("error iterating labels: %w", err)
- }
-
- // Update last accessed time
- go r.updateLastAccessed(id)
-
- return asset, nil
-}
-
-// UpdateAsset updates an asset record
-func (r *Registry) UpdateAsset(asset *assetv1.Asset) error {
- tx, err := r.db.Begin()
- if err != nil {
- return fmt.Errorf("failed to begin transaction: %w", err)
- }
- defer func() { _ = tx.Rollback() }()
-
- // Update asset
- query := `
- UPDATE assets SET
- name = ?, type = ?, status = ?, backend = ?, location = ?,
- size_bytes = ?, checksum = ?, last_accessed_at = ?,
- reference_count = ?, build_id = ?, source_image = ?
- WHERE id = ?
- `
-
- _, err = tx.Exec(query,
- asset.GetName(), asset.GetType(), asset.GetStatus(), asset.GetBackend(), asset.GetLocation(),
- asset.GetSizeBytes(), asset.GetChecksum(), asset.GetLastAccessedAt(),
- asset.GetReferenceCount(), asset.GetBuildId(), asset.GetSourceImage(),
- asset.GetId(),
- )
- if err != nil {
- return fmt.Errorf("failed to update asset: %w", err)
- }
-
- // Update labels (delete and re-insert)
- if _, err := tx.Exec("DELETE FROM asset_labels WHERE asset_id = ?", asset.GetId()); err != nil {
- return fmt.Errorf("failed to delete labels: %w", err)
- }
-
- for key, value := range asset.GetLabels() {
- _, labelErr := tx.Exec(
- "INSERT INTO asset_labels (asset_id, key, value) VALUES (?, ?, ?)",
- asset.GetId(), key, value,
- )
- if labelErr != nil {
- return fmt.Errorf("failed to insert label %s=%s: %w", key, value, labelErr)
- }
- }
-
- if err := tx.Commit(); err != nil {
- return fmt.Errorf("failed to commit transaction: %w", err)
- }
-
- return nil
-}
-
-// DeleteAsset deletes an asset record
-func (r *Registry) DeleteAsset(id string) error {
- // AIDEV-NOTE: CASCADE constraints handle cleanup of labels and leases
- _, err := r.db.Exec("DELETE FROM assets WHERE id = ?", id)
- if err != nil {
- return fmt.Errorf("failed to delete asset: %w", err)
- }
-
- r.logger.Info("deleted asset", slog.String("id", id))
- return nil
-}
-
-// ListAssets lists assets with optional filters
-func (r *Registry) ListAssets(filters ListFilters) ([]*assetv1.Asset, error) {
- query := "SELECT id FROM assets WHERE 1=1"
- args := []interface{}{}
-
- // Add filters
- if filters.Type != assetv1.AssetType_ASSET_TYPE_UNSPECIFIED {
- query += " AND type = ?"
- args = append(args, filters.Type)
- }
-
- if filters.Status != assetv1.AssetStatus_ASSET_STATUS_UNSPECIFIED {
- query += " AND status = ?"
- args = append(args, filters.Status)
- }
-
- // Label filters require a subquery
- for key, value := range filters.Labels {
- query += " AND id IN (SELECT asset_id FROM asset_labels WHERE key = ? AND value = ?)"
- args = append(args, key, value)
- }
-
- // Add ordering and pagination
- query += " ORDER BY created_at DESC"
-
- if filters.Limit > 0 {
- query += " LIMIT ?"
- args = append(args, filters.Limit)
- }
-
- if filters.Offset > 0 {
- query += " OFFSET ?"
- args = append(args, filters.Offset)
- }
-
- rows, err := r.db.Query(query, args...)
- if err != nil {
- return nil, fmt.Errorf("failed to list assets: %w", err)
- }
- defer rows.Close()
-
- var assets []*assetv1.Asset
- for rows.Next() {
- var id string
- if err := rows.Scan(&id); err != nil {
- return nil, fmt.Errorf("failed to scan asset ID: %w", err)
- }
-
- asset, err := r.GetAsset(id)
- if err != nil {
- return nil, fmt.Errorf("failed to get asset %s: %w", id, err)
- }
-
- assets = append(assets, asset)
- }
- if err := rows.Err(); err != nil {
- return nil, fmt.Errorf("error iterating rows: %w", err)
- }
-
- return assets, nil
-}
-
-// CreateLease creates a new asset lease
-func (r *Registry) CreateLease(assetID, acquiredBy string, ttl time.Duration) (string, error) {
- leaseID := ulid.Make().String()
- acquiredAt := time.Now().Unix()
-
- var expiresAt *int64
- if ttl > 0 {
- exp := time.Now().Add(ttl).Unix()
- expiresAt = &exp
- }
-
- tx, err := r.db.Begin()
- if err != nil {
- return "", fmt.Errorf("failed to begin transaction: %w", err)
- }
- defer func() { _ = tx.Rollback() }()
-
- // Insert lease
- _, err = tx.Exec(
- "INSERT INTO asset_leases (id, asset_id, acquired_by, acquired_at, expires_at) VALUES (?, ?, ?, ?, ?)",
- leaseID, assetID, acquiredBy, acquiredAt, expiresAt,
- )
- if err != nil {
- return "", fmt.Errorf("failed to create lease: %w", err)
- }
-
- // Increment reference count
- _, err = tx.Exec("UPDATE assets SET reference_count = reference_count + 1 WHERE id = ?", assetID)
- if err != nil {
- return "", fmt.Errorf("failed to increment reference count: %w", err)
- }
-
- if err := tx.Commit(); err != nil {
- return "", fmt.Errorf("failed to commit transaction: %w", err)
- }
-
- r.logger.Info("created lease",
- slog.String("lease_id", leaseID),
- slog.String("asset_id", assetID),
- slog.String("acquired_by", acquiredBy),
- )
-
- return leaseID, nil
-}
-
-// ReleaseLease releases an asset lease
-func (r *Registry) ReleaseLease(leaseID string) error {
- tx, err := r.db.Begin()
- if err != nil {
- return fmt.Errorf("failed to begin transaction: %w", err)
- }
- defer func() { _ = tx.Rollback() }()
-
- // Get asset ID from lease
- var assetID string
- err = tx.QueryRow("SELECT asset_id FROM asset_leases WHERE id = ?", leaseID).Scan(&assetID)
- if err != nil {
- if err == sql.ErrNoRows {
- return fmt.Errorf("lease not found: %s", leaseID)
- }
- return fmt.Errorf("failed to get lease: %w", err)
- }
-
- // Delete lease
- _, err = tx.Exec("DELETE FROM asset_leases WHERE id = ?", leaseID)
- if err != nil {
- return fmt.Errorf("failed to delete lease: %w", err)
- }
-
- // Decrement reference count
- _, err = tx.Exec("UPDATE assets SET reference_count = reference_count - 1 WHERE id = ?", assetID)
- if err != nil {
- return fmt.Errorf("failed to decrement reference count: %w", err)
- }
-
- if err := tx.Commit(); err != nil {
- return fmt.Errorf("failed to commit transaction: %w", err)
- }
-
- r.logger.Info("released lease",
- slog.String("lease_id", leaseID),
- slog.String("asset_id", assetID),
- )
-
- return nil
-}
-
-// GetExpiredLeases returns leases that have expired
-func (r *Registry) GetExpiredLeases() ([]string, error) {
- query := "SELECT id FROM asset_leases WHERE expires_at IS NOT NULL AND expires_at < ?"
-
- rows, err := r.db.Query(query, time.Now().Unix())
- if err != nil {
- return nil, fmt.Errorf("failed to query expired leases: %w", err)
- }
- defer rows.Close()
-
- var leaseIDs []string
- for rows.Next() {
- var id string
- if err := rows.Scan(&id); err != nil {
- return nil, fmt.Errorf("failed to scan lease ID: %w", err)
- }
- leaseIDs = append(leaseIDs, id)
- }
- if err := rows.Err(); err != nil {
- return nil, fmt.Errorf("error iterating rows: %w", err)
- }
-
- return leaseIDs, nil
-}
-
-// GetUnreferencedAssets returns assets with zero references
-func (r *Registry) GetUnreferencedAssets(olderThan time.Duration) ([]*assetv1.Asset, error) {
- cutoff := time.Now().Add(-olderThan).Unix()
-
- query := `
- SELECT id FROM assets
- WHERE reference_count = 0
- AND last_accessed_at < ?
- ORDER BY last_accessed_at ASC
- `
-
- rows, err := r.db.Query(query, cutoff)
- if err != nil {
- return nil, fmt.Errorf("failed to query unreferenced assets: %w", err)
- }
- defer rows.Close()
-
- var assets []*assetv1.Asset
- for rows.Next() {
- var id string
- if err := rows.Scan(&id); err != nil {
- return nil, fmt.Errorf("failed to scan asset ID: %w", err)
- }
-
- asset, err := r.GetAsset(id)
- if err != nil {
- return nil, fmt.Errorf("failed to get asset %s: %w", id, err)
- }
-
- assets = append(assets, asset)
- }
- if err := rows.Err(); err != nil {
- return nil, fmt.Errorf("error iterating rows: %w", err)
- }
-
- return assets, nil
-}
-
-// updateLastAccessed updates the last accessed timestamp
-func (r *Registry) updateLastAccessed(id string) {
- _, err := r.db.Exec(
- "UPDATE assets SET last_accessed_at = ? WHERE id = ?",
- time.Now().Unix(), id,
- )
- if err != nil {
- r.logger.Warn("failed to update last accessed time",
- slog.String("asset_id", id),
- slog.String("error", err.Error()),
- )
- }
-}
-
-// ListFilters defines filters for listing assets
-type ListFilters struct {
- Type assetv1.AssetType
- Status assetv1.AssetStatus
- Labels map[string]string
- Limit int
- Offset int
-}
diff --git a/go/apps/assetmanagerd/internal/service/service.go b/go/apps/assetmanagerd/internal/service/service.go
deleted file mode 100644
index 627cd9d274..0000000000
--- a/go/apps/assetmanagerd/internal/service/service.go
+++ /dev/null
@@ -1,1128 +0,0 @@
-package service
-
-import (
- "context"
- "fmt"
- "io"
- "log/slog"
- "os"
- "path/filepath"
- "strings"
- "time"
-
- "connectrpc.com/connect"
- "github.com/unkeyed/unkey/go/apps/assetmanagerd/internal/builderd"
- "github.com/unkeyed/unkey/go/apps/assetmanagerd/internal/config"
- "github.com/unkeyed/unkey/go/apps/assetmanagerd/internal/registry"
- "github.com/unkeyed/unkey/go/apps/assetmanagerd/internal/storage"
- assetv1 "github.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1"
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/codes"
- "go.opentelemetry.io/otel/trace"
-)
-
-// Service implements the AssetManagerService
-type Service struct {
- cfg *config.Config
- logger *slog.Logger
- registry *registry.Registry
- storage storage.Backend
- builderdClient *builderd.Client
-}
-
-// New creates a new asset service
-func New(cfg *config.Config, logger *slog.Logger, registry *registry.Registry, storage storage.Backend, builderdClient *builderd.Client) *Service {
- return &Service{
- cfg: cfg,
- logger: logger.With("component", "service"),
- registry: registry,
- storage: storage,
- builderdClient: builderdClient,
- }
-}
-
-// RegisterAsset registers a new asset
-func (s *Service) RegisterAsset(
- ctx context.Context,
- req *connect.Request[assetv1.RegisterAssetRequest],
-) (*connect.Response[assetv1.RegisterAssetResponse], error) {
- // AIDEV-NOTE: Assets are pre-stored before registration, this just adds metadata
- // This allows builderd to upload directly to storage then register
-
- // Validate request
- if req.Msg.GetName() == "" {
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("name is required"))
- }
-
- if req.Msg.GetLocation() == "" {
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("location is required"))
- }
-
- // Verify asset exists in storage
- exists, err := s.storage.Exists(ctx, req.Msg.GetLocation())
- if err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to check asset existence",
- slog.String("location", req.Msg.GetLocation()),
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to verify asset"))
- }
-
- if !exists {
- return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("asset not found at location: %s", req.Msg.GetLocation()))
- }
-
- // Get actual size and checksum from storage
- size := req.Msg.GetSizeBytes()
- if size == 0 {
- size, err = s.storage.GetSize(ctx, req.Msg.GetLocation())
- if err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to get asset size",
- slog.String("location", req.Msg.GetLocation()),
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to get asset size"))
- }
- }
-
- checksum := req.Msg.GetChecksum()
- if checksum == "" {
- checksum, err = s.storage.GetChecksum(ctx, req.Msg.GetLocation())
- if err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to get asset checksum",
- slog.String("location", req.Msg.GetLocation()),
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to get asset checksum"))
- }
- }
-
- // Create asset record
- //nolint:exhaustruct // Some fields may be auto-generated
- asset := &assetv1.Asset{
- Id: req.Msg.GetId(), // Use provided ID if available
- Name: req.Msg.GetName(),
- Type: req.Msg.GetType(),
- Status: assetv1.AssetStatus_ASSET_STATUS_AVAILABLE,
- Backend: req.Msg.GetBackend(),
- Location: req.Msg.GetLocation(),
- SizeBytes: size,
- Checksum: checksum,
- Labels: req.Msg.GetLabels(),
- CreatedBy: req.Msg.GetCreatedBy(),
- CreatedAt: time.Now().Unix(),
- LastAccessedAt: time.Now().Unix(),
- ReferenceCount: 0,
- BuildId: req.Msg.GetBuildId(),
- SourceImage: req.Msg.GetSourceImage(),
- }
-
- // Save to registry
- if err := s.registry.CreateAsset(asset); err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to create asset record",
- slog.String("name", req.Msg.GetName()),
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to register asset"))
- }
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "registered asset",
- slog.String("id", asset.GetId()),
- slog.String("name", asset.GetName()),
- slog.String("type", asset.GetType().String()),
- slog.Int64("size", asset.GetSizeBytes()),
- )
-
- return connect.NewResponse(&assetv1.RegisterAssetResponse{
- Asset: asset,
- }), nil
-}
-
-// GetAsset retrieves asset information
-func (s *Service) GetAsset(
- ctx context.Context,
- req *connect.Request[assetv1.GetAssetRequest],
-) (*connect.Response[assetv1.GetAssetResponse], error) {
- if req.Msg.GetId() == "" {
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("id is required"))
- }
-
- // Get asset from registry
- asset, err := s.registry.GetAsset(req.Msg.GetId())
- if err != nil {
- if strings.Contains(err.Error(), "not found") {
- return nil, connect.NewError(connect.CodeNotFound, err)
- }
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to get asset",
- slog.String("id", req.Msg.GetId()),
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to get asset"))
- }
-
- //nolint:exhaustruct // LocalPath field is optional and set below if needed
- resp := &assetv1.GetAssetResponse{
- Asset: asset,
- }
-
- // Ensure local if requested
- if req.Msg.GetEnsureLocal() {
- localPath, err := s.storage.EnsureLocal(ctx, asset.GetLocation(), s.cfg.CacheDir)
- if err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to ensure asset is local",
- slog.String("id", req.Msg.GetId()),
- slog.String("location", asset.GetLocation()),
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to ensure asset is local"))
- }
- resp.LocalPath = localPath
- }
-
- return connect.NewResponse(resp), nil
-}
-
-// ListAssets lists available assets
-func (s *Service) ListAssets(
- ctx context.Context,
- req *connect.Request[assetv1.ListAssetsRequest],
-) (*connect.Response[assetv1.ListAssetsResponse], error) {
- // Convert request to registry filters
- //nolint:exhaustruct // Limit and Offset are set below
- filters := registry.ListFilters{
- Type: req.Msg.GetType(),
- Status: req.Msg.GetStatus(),
- Labels: req.Msg.GetLabelSelector(),
- }
-
- // Handle pagination
- pageSize := int(req.Msg.GetPageSize())
- if pageSize == 0 {
- pageSize = 100
- }
- if pageSize > 1000 {
- pageSize = 1000
- }
- filters.Limit = pageSize
-
- // Parse page token (simple offset-based pagination)
- if req.Msg.GetPageToken() != "" {
- var offset int
- if _, err := fmt.Sscanf(req.Msg.GetPageToken(), "offset:%d", &offset); err == nil {
- filters.Offset = offset
- }
- }
-
- // Get assets
- assets, err := s.registry.ListAssets(filters)
- if err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to list assets",
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to list assets"))
- }
-
- // AIDEV-NOTE: Automatic asset building - if no rootfs found with docker_image label, trigger builderd
- if len(assets) == 0 && s.cfg.BuilderdEnabled && s.builderdClient != nil {
- // Check if this is a request for rootfs with docker_image label
- if req.Msg.GetType() == assetv1.AssetType_ASSET_TYPE_ROOTFS || req.Msg.GetType() == assetv1.AssetType_ASSET_TYPE_UNSPECIFIED {
- if dockerImage, ok := req.Msg.GetLabelSelector()["docker_image"]; ok && dockerImage != "" {
- s.logger.InfoContext(ctx, "no rootfs found, triggering automatic build",
- "docker_image", dockerImage,
- )
-
- // Trigger build and wait for completion
- if err := s.triggerAndWaitForBuild(ctx, dockerImage, req.Msg.GetLabelSelector()); err != nil {
- s.logger.ErrorContext(ctx, "failed to build rootfs automatically",
- "docker_image", dockerImage,
- "error", err,
- )
- // Return empty results but log the build failure
- // This allows the caller to handle the missing asset gracefully
- } else {
- // Re-query for assets after successful build
- assets, err = s.registry.ListAssets(filters)
- if err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to list assets after build",
- slog.String("error", err.Error()),
- )
- }
- }
- }
- }
- }
-
- //nolint:exhaustruct // NextPageToken is optional and set below if needed
- resp := &assetv1.ListAssetsResponse{
- Assets: assets,
- }
-
- // Set next page token if we hit the limit
- if len(assets) == pageSize {
- resp.NextPageToken = fmt.Sprintf("offset:%d", filters.Offset+pageSize)
- }
-
- return connect.NewResponse(resp), nil
-}
-
-// AcquireAsset acquires a reference to an asset
-func (s *Service) AcquireAsset(
- ctx context.Context,
- req *connect.Request[assetv1.AcquireAssetRequest],
-) (*connect.Response[assetv1.AcquireAssetResponse], error) {
- if req.Msg.GetAssetId() == "" {
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("asset_id is required"))
- }
-
- if req.Msg.GetAcquiredBy() == "" {
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("acquired_by is required"))
- }
-
- // Verify asset exists
- _, err := s.registry.GetAsset(req.Msg.GetAssetId())
- if err != nil {
- if strings.Contains(err.Error(), "not found") {
- return nil, connect.NewError(connect.CodeNotFound, err)
- }
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to get asset"))
- }
-
- // Create lease
- ttl := time.Duration(req.Msg.GetTtlSeconds()) * time.Second
- leaseID, err := s.registry.CreateLease(req.Msg.GetAssetId(), req.Msg.GetAcquiredBy(), ttl)
- if err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to create lease",
- slog.String("asset_id", req.Msg.GetAssetId()),
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to acquire asset"))
- }
-
- // Get updated asset with incremented ref count
- asset, _ := s.registry.GetAsset(req.Msg.GetAssetId())
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "acquired asset",
- slog.String("asset_id", req.Msg.GetAssetId()),
- slog.String("lease_id", leaseID),
- slog.String("acquired_by", req.Msg.GetAcquiredBy()),
- slog.Int("ref_count", int(asset.GetReferenceCount())),
- )
-
- return connect.NewResponse(&assetv1.AcquireAssetResponse{
- Asset: asset,
- LeaseId: leaseID,
- }), nil
-}
-
-// ReleaseAsset releases an asset reference
-func (s *Service) ReleaseAsset(
- ctx context.Context,
- req *connect.Request[assetv1.ReleaseAssetRequest],
-) (*connect.Response[assetv1.ReleaseAssetResponse], error) {
- if req.Msg.GetLeaseId() == "" {
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("lease_id is required"))
- }
-
- // Release lease
- if err := s.registry.ReleaseLease(req.Msg.GetLeaseId()); err != nil {
- if strings.Contains(err.Error(), "not found") {
- return nil, connect.NewError(connect.CodeNotFound, err)
- }
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to release lease",
- slog.String("lease_id", req.Msg.GetLeaseId()),
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to release asset"))
- }
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "released asset",
- slog.String("lease_id", req.Msg.GetLeaseId()),
- )
-
- // Return empty asset for now (could fetch if needed)
- return connect.NewResponse(&assetv1.ReleaseAssetResponse{
- //nolint:exhaustruct // Empty asset is intentional - could fetch if needed in future
- Asset: &assetv1.Asset{},
- }), nil
-}
-
-// DeleteAsset deletes an asset
-func (s *Service) DeleteAsset(
- ctx context.Context,
- req *connect.Request[assetv1.DeleteAssetRequest],
-) (*connect.Response[assetv1.DeleteAssetResponse], error) {
- if req.Msg.GetId() == "" {
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("id is required"))
- }
-
- // Get asset
- asset, err := s.registry.GetAsset(req.Msg.GetId())
- if err != nil {
- if strings.Contains(err.Error(), "not found") {
- return nil, connect.NewError(connect.CodeNotFound, err)
- }
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to get asset"))
- }
-
- // Check reference count
- if asset.GetReferenceCount() > 0 && !req.Msg.GetForce() {
- return connect.NewResponse(&assetv1.DeleteAssetResponse{
- Deleted: false,
- Message: fmt.Sprintf("asset has %d active references", asset.GetReferenceCount()),
- }), nil
- }
-
- // Delete from storage
- if err := s.storage.Delete(ctx, asset.GetLocation()); err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to delete from storage",
- slog.String("id", req.Msg.GetId()),
- slog.String("location", asset.GetLocation()),
- slog.String("error", err.Error()),
- )
- // Continue with registry deletion even if storage deletion fails
- }
-
- // Delete from registry
- if err := s.registry.DeleteAsset(req.Msg.GetId()); err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to delete from registry",
- slog.String("id", req.Msg.GetId()),
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to delete asset"))
- }
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "deleted asset",
- slog.String("id", req.Msg.GetId()),
- slog.String("name", asset.GetName()),
- )
-
- return connect.NewResponse(&assetv1.DeleteAssetResponse{
- Deleted: true,
- Message: "asset deleted successfully",
- }), nil
-}
-
-// GarbageCollect performs garbage collection
-func (s *Service) GarbageCollect(
- ctx context.Context,
- req *connect.Request[assetv1.GarbageCollectRequest],
-) (*connect.Response[assetv1.GarbageCollectResponse], error) {
- // AIDEV-NOTE: GC is critical for managing storage costs and disk space
- // This method handles both expired leases and unreferenced assets
-
- var deletedAssets []*assetv1.Asset
- var bytesFreed int64
-
- // Clean up expired leases first
- expiredLeases, err := s.registry.GetExpiredLeases()
- if err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to get expired leases",
- slog.String("error", err.Error()),
- )
- } else {
- for _, leaseID := range expiredLeases {
- if err := s.registry.ReleaseLease(leaseID); err != nil {
- s.logger.LogAttrs(ctx, slog.LevelWarn, "failed to release expired lease",
- slog.String("lease_id", leaseID),
- slog.String("error", err.Error()),
- )
- }
- }
- s.logger.LogAttrs(ctx, slog.LevelInfo, "cleaned up expired leases",
- slog.Int("count", len(expiredLeases)),
- )
- }
-
- // Get unreferenced assets
- //nolint:nestif // Nested conditions are clear and logical for GC operation
- if req.Msg.GetDeleteUnreferenced() {
- maxAge := time.Duration(req.Msg.GetMaxAgeSeconds()) * time.Second
- if maxAge == 0 {
- maxAge = s.cfg.GCMaxAge
- }
-
- unreferencedAssets, err := s.registry.GetUnreferencedAssets(maxAge)
- if err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to get unreferenced assets",
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to get unreferenced assets"))
- }
-
- for _, asset := range unreferencedAssets {
- if req.Msg.GetDryRun() {
- deletedAssets = append(deletedAssets, asset)
- bytesFreed += asset.GetSizeBytes()
- continue
- }
-
- // Delete from storage
- if err := s.storage.Delete(ctx, asset.GetLocation()); err != nil {
- s.logger.LogAttrs(ctx, slog.LevelWarn, "failed to delete asset from storage",
- slog.String("id", asset.GetId()),
- slog.String("location", asset.GetLocation()),
- slog.String("error", err.Error()),
- )
- continue
- }
-
- // Delete from registry
- if err := s.registry.DeleteAsset(asset.GetId()); err != nil {
- s.logger.LogAttrs(ctx, slog.LevelWarn, "failed to delete asset from registry",
- slog.String("id", asset.GetId()),
- slog.String("error", err.Error()),
- )
- continue
- }
-
- deletedAssets = append(deletedAssets, asset)
- bytesFreed += asset.GetSizeBytes()
- }
- }
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "garbage collection completed",
- slog.Bool("dry_run", req.Msg.GetDryRun()),
- slog.Int("deleted_count", len(deletedAssets)),
- slog.Int64("bytes_freed", bytesFreed),
- )
-
- return connect.NewResponse(&assetv1.GarbageCollectResponse{
- DeletedAssets: deletedAssets,
- BytesFreed: bytesFreed,
- }), nil
-}
-
-// PrepareAssets prepares assets for use (e.g., in jailer chroot)
-func (s *Service) PrepareAssets(
- ctx context.Context,
- req *connect.Request[assetv1.PrepareAssetsRequest],
-) (*connect.Response[assetv1.PrepareAssetsResponse], error) {
- if len(req.Msg.GetAssetIds()) == 0 {
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("asset_ids is required"))
- }
-
- if req.Msg.GetTargetPath() == "" {
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("target_path is required"))
- }
-
- assetPaths := make(map[string]string)
-
- for _, assetID := range req.Msg.GetAssetIds() {
- // Get asset
- asset, err := s.registry.GetAsset(assetID)
- if err != nil {
- if strings.Contains(err.Error(), "not found") {
- return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("asset %s not found", assetID))
- }
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to get asset %s", assetID))
- }
-
- // Ensure asset is available locally
- localPath, err := s.storage.EnsureLocal(ctx, asset.GetLocation(), s.cfg.CacheDir)
- if err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to ensure asset is local",
- slog.String("id", assetID),
- slog.String("location", asset.GetLocation()),
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to prepare asset %s", assetID))
- }
-
- // Prepare the target file path with standardized names
- // AIDEV-NOTE: Use standardized names that Firecracker expects
- var filename string
- switch asset.GetType() {
- case assetv1.AssetType_ASSET_TYPE_KERNEL:
- filename = "vmlinux"
- case assetv1.AssetType_ASSET_TYPE_ROOTFS:
- filename = "rootfs.ext4"
- default:
- filename = filepath.Base(localPath)
- }
- targetFile := filepath.Join(req.Msg.GetTargetPath(), filename)
-
- // Create the target directory if it doesn't exist
- if err := os.MkdirAll(req.Msg.GetTargetPath(), 0755); err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to create target directory",
- slog.String("path", req.Msg.GetTargetPath()),
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to create target directory: %w", err))
- }
-
- // Try to create a hard link first (most efficient)
- if err := os.Link(localPath, targetFile); err != nil {
- // If hard link fails (e.g., different filesystems), copy the file
- s.logger.LogAttrs(ctx, slog.LevelDebug, "hard link failed, copying file",
- slog.String("source", localPath),
- slog.String("target", targetFile),
- slog.String("error", err.Error()),
- )
-
- // Copy the file
- if err := copyFile(localPath, targetFile); err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to copy asset to target",
- slog.String("source", localPath),
- slog.String("target", targetFile),
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to prepare asset %s: %w", assetID, err))
- }
- }
-
- // AIDEV-NOTE: For rootfs assets, also copy associated metadata file if it exists
- // This is needed for container initialization in microVMs
- if asset.GetType() == assetv1.AssetType_ASSET_TYPE_ROOTFS {
- // Look for metadata file alongside the rootfs asset
- metadataFileName := strings.TrimSuffix(filepath.Base(localPath), filepath.Ext(localPath)) + ".metadata.json"
- metadataSourcePath := filepath.Join(filepath.Dir(localPath), metadataFileName)
-
- if _, err := os.Stat(metadataSourcePath); err == nil {
- // Metadata file exists, copy it
- metadataTargetPath := filepath.Join(req.Msg.GetTargetPath(), "metadata.json")
-
- if err := os.Link(metadataSourcePath, metadataTargetPath); err != nil {
- // If hard link fails, copy the file
- if err := copyFile(metadataSourcePath, metadataTargetPath); err != nil {
- s.logger.LogAttrs(ctx, slog.LevelWarn, "failed to copy metadata file",
- slog.String("source", metadataSourcePath),
- slog.String("target", metadataTargetPath),
- slog.String("error", err.Error()),
- )
- } else {
- s.logger.LogAttrs(ctx, slog.LevelDebug, "copied metadata file for rootfs asset",
- slog.String("metadata_file", metadataTargetPath),
- slog.String("asset_id", assetID),
- )
- }
- } else {
- s.logger.LogAttrs(ctx, slog.LevelDebug, "linked metadata file for rootfs asset",
- slog.String("metadata_file", metadataTargetPath),
- slog.String("asset_id", assetID),
- )
- }
- } else {
- s.logger.LogAttrs(ctx, slog.LevelDebug, "no metadata file found for rootfs asset",
- slog.String("expected_path", metadataSourcePath),
- slog.String("asset_id", assetID),
- )
- }
- }
-
- assetPaths[assetID] = targetFile
- }
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "prepared assets",
- slog.Int("count", len(assetPaths)),
- slog.String("target_path", req.Msg.GetTargetPath()),
- slog.String("prepared_for", req.Msg.GetPreparedFor()),
- )
-
- return connect.NewResponse(&assetv1.PrepareAssetsResponse{
- AssetPaths: assetPaths,
- }), nil
-}
-
-// StartGarbageCollector starts the background garbage collector
-func (s *Service) StartGarbageCollector(ctx context.Context) {
- ticker := time.NewTicker(s.cfg.GCInterval)
- defer ticker.Stop()
-
- s.logger.InfoContext(ctx, "started garbage collector",
- slog.Duration("interval", s.cfg.GCInterval),
- slog.Duration("max_age", s.cfg.GCMaxAge),
- )
-
- for {
- select {
- case <-ctx.Done():
- s.logger.InfoContext(ctx, "stopping garbage collector")
- return
- case <-ticker.C:
- // Run GC
- req := &assetv1.GarbageCollectRequest{
- MaxAgeSeconds: int64(s.cfg.GCMaxAge.Seconds()),
- DeleteUnreferenced: true,
- DryRun: false,
- }
-
- resp, err := s.GarbageCollect(ctx, connect.NewRequest(req))
- if err != nil {
- s.logger.ErrorContext(ctx, "garbage collection failed",
- slog.String("error", err.Error()),
- )
- } else {
- if len(resp.Msg.GetDeletedAssets()) > 0 {
- s.logger.InfoContext(ctx, "garbage collection completed",
- slog.Int("deleted_count", len(resp.Msg.GetDeletedAssets())),
- slog.Int64("bytes_freed", resp.Msg.GetBytesFreed()),
- )
- }
- }
- }
- }
-}
-
-// uploadAssetHelper handles direct asset uploads (helper method)
-func (s *Service) uploadAssetHelper(ctx context.Context, name string, assetType assetv1.AssetType, reader io.Reader, size int64) (*assetv1.Asset, error) {
- // AIDEV-NOTE: This is a helper method for direct uploads
- // Currently, builderd uploads to storage directly then calls RegisterAsset
- // This method would be used for manual uploads or future integrations
-
- // Store asset
- id := fmt.Sprintf("%s-%d", name, time.Now().UnixNano())
- location, err := s.storage.Store(ctx, id, reader, size)
- if err != nil {
- return nil, fmt.Errorf("failed to store asset: %w", err)
- }
-
- // Get checksum
- checksum, err := s.storage.GetChecksum(ctx, location)
- if err != nil {
- // Clean up
- _ = s.storage.Delete(ctx, location)
- return nil, fmt.Errorf("failed to get checksum: %w", err)
- }
-
- // Register asset
- //nolint:exhaustruct // Optional fields not needed for manual upload
- req := &assetv1.RegisterAssetRequest{
- Name: name,
- Type: assetType,
- Backend: assetv1.StorageBackend_STORAGE_BACKEND_LOCAL,
- Location: location,
- SizeBytes: size,
- Checksum: checksum,
- CreatedBy: "manual",
- }
-
- resp, err := s.RegisterAsset(ctx, connect.NewRequest(req))
- if err != nil {
- // Clean up
- _ = s.storage.Delete(ctx, location)
- return nil, err
- }
-
- return resp.Msg.GetAsset(), nil
-}
-
-// UploadAsset handles streaming asset uploads via gRPC
-func (s *Service) UploadAsset(
- ctx context.Context,
- stream *connect.ClientStream[assetv1.UploadAssetRequest],
-) (*connect.Response[assetv1.UploadAssetResponse], error) {
- // AIDEV-NOTE: Streaming upload RPC for builderd to upload assets before registering
- // First message should contain metadata, subsequent messages contain chunks
-
- // Read first message (metadata)
- if !stream.Receive() {
- if stream.Err() != nil {
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("failed to receive metadata: %w", stream.Err()))
- }
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("no metadata received"))
- }
-
- firstMsg := stream.Msg()
- metadata := firstMsg.GetMetadata()
- if metadata == nil {
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("first message must contain metadata"))
- }
-
- // Validate metadata
- if metadata.GetName() == "" {
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("name is required"))
- }
-
- // Generate asset ID if not provided
- assetID := metadata.GetId()
- if assetID == "" {
- assetID = fmt.Sprintf("%s-%d", metadata.GetName(), time.Now().UnixNano())
- }
-
- // Create a pipe for streaming data to storage
- pipeReader, pipeWriter := io.Pipe()
- defer pipeReader.Close()
-
- // Start storing in background
- storeCh := make(chan struct {
- location string
- err error
- }, 1)
-
- go func() {
- defer pipeWriter.Close()
- location, err := s.storage.Store(ctx, assetID, pipeReader, metadata.GetSizeBytes())
- storeCh <- struct {
- location string
- err error
- }{location, err}
- }()
-
- // Stream data chunks
- var totalBytes int64
- for stream.Receive() {
- chunk := stream.Msg().GetChunk()
- if chunk == nil {
- continue
- }
-
- if _, err := pipeWriter.Write(chunk); err != nil {
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to write chunk: %w", err))
- }
- totalBytes += int64(len(chunk))
- }
-
- if stream.Err() != nil {
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("stream error: %w", stream.Err()))
- }
-
- // Close writer to signal end of data
- pipeWriter.Close()
-
- // Wait for storage to complete
- storeResult := <-storeCh
- if storeResult.err != nil {
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to store asset: %w", storeResult.err))
- }
-
- // Get checksum
- checksum, err := s.storage.GetChecksum(ctx, storeResult.location)
- if err != nil {
- _ = s.storage.Delete(ctx, storeResult.location)
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to get checksum: %w", err))
- }
-
- // Register asset
- req := &assetv1.RegisterAssetRequest{
- Name: metadata.GetName(),
- Type: metadata.GetType(),
- Backend: assetv1.StorageBackend_STORAGE_BACKEND_LOCAL,
- Location: storeResult.location,
- SizeBytes: totalBytes,
- Checksum: checksum,
- Labels: metadata.GetLabels(),
- CreatedBy: metadata.GetCreatedBy(),
- BuildId: metadata.GetBuildId(),
- SourceImage: metadata.GetSourceImage(),
- Id: assetID,
- }
-
- resp, err := s.RegisterAsset(ctx, connect.NewRequest(req))
- if err != nil {
- _ = s.storage.Delete(ctx, storeResult.location)
- return nil, err
- }
-
- // Return response
- return connect.NewResponse(&assetv1.UploadAssetResponse{
- Asset: resp.Msg.GetAsset(),
- }), nil
-}
-
-// copyFile copies a file from source to destination
-func copyFile(src, dst string) error {
- sourceFile, err := os.Open(src)
- if err != nil {
- return fmt.Errorf("failed to open source file: %w", err)
- }
- defer sourceFile.Close()
-
- destFile, err := os.Create(dst)
- if err != nil {
- return fmt.Errorf("failed to create destination file: %w", err)
- }
- defer destFile.Close()
-
- // Copy the file contents
- if _, copyErr := io.Copy(destFile, sourceFile); copyErr != nil {
- return fmt.Errorf("failed to copy file contents: %w", copyErr)
- }
-
- // Sync to ensure all data is written to disk
- if syncErr := destFile.Sync(); syncErr != nil {
- return fmt.Errorf("failed to sync destination file: %w", syncErr)
- }
-
- // Copy file permissions
- sourceInfo, err := os.Stat(src)
- if err != nil {
- return fmt.Errorf("failed to stat source file: %w", err)
- }
-
- if chmodErr := os.Chmod(dst, sourceInfo.Mode()); chmodErr != nil {
- return fmt.Errorf("failed to set destination file permissions: %w", chmodErr)
- }
-
- return nil
-}
-
-// triggerAndWaitForBuild triggers builderd to create a rootfs and waits for completion
-// AIDEV-NOTE: This implements the automatic asset creation workflow
-func (s *Service) triggerAndWaitForBuild(ctx context.Context, dockerImage string, labels map[string]string) error {
- tracer := otel.Tracer("assetmanagerd")
-
- // Create build request
- ctx, buildSpan := tracer.Start(ctx, "assetmanagerd.service.trigger_build",
- trace.WithAttributes(
- attribute.String("docker.image", dockerImage),
- attribute.StringSlice("build.labels", func() []string {
- var labelPairs []string
- for k, v := range labels {
- labelPairs = append(labelPairs, fmt.Sprintf("%s=%s", k, v))
- }
- return labelPairs
- }()),
- ),
- )
- buildID, err := s.builderdClient.BuildDockerRootfs(ctx, dockerImage, labels)
- if err != nil {
- buildSpan.RecordError(err)
- buildSpan.SetStatus(codes.Error, err.Error())
- buildSpan.End()
- return fmt.Errorf("failed to trigger build: %w", err)
- }
- buildSpan.SetAttributes(attribute.String("build.id", buildID))
- buildSpan.End()
-
- s.logger.InfoContext(ctx, "build triggered",
- "build_id", buildID,
- "docker_image", dockerImage,
- )
-
- // Wait for build completion with polling
- pollInterval := 5 * time.Second
- ctx, waitSpan := tracer.Start(ctx, "assetmanagerd.service.wait_for_build",
- trace.WithAttributes(
- attribute.String("build.id", buildID),
- attribute.String("docker.image", dockerImage),
- attribute.String("poll.interval", pollInterval.String()),
- ),
- )
- completedBuild, err := s.builderdClient.WaitForBuild(ctx, buildID, pollInterval)
- if err != nil {
- waitSpan.RecordError(err)
- waitSpan.SetStatus(codes.Error, err.Error())
- } else {
- waitSpan.SetAttributes(
- attribute.String("build.rootfs_path", completedBuild.Build.RootfsPath),
- attribute.String("build.status", completedBuild.Build.State.String()),
- )
- }
- waitSpan.End()
- if err != nil {
- return fmt.Errorf("build failed or timed out: %w", err)
- }
-
- s.logger.InfoContext(ctx, "build completed successfully",
- "build_id", completedBuild.Build.BuildId,
- "docker_image", dockerImage,
- "rootfs_path", completedBuild.Build.RootfsPath,
- )
-
- // If auto-register is enabled, the build should have been registered automatically
- // by builderd's post-build hook. If not, we'd need to register it here.
- if !s.cfg.BuilderdAutoRegister {
- // Manual registration would go here if needed
- // For now, we assume builderd handles registration
- s.logger.WarnContext(ctx, "auto-registration disabled, asset may need manual registration",
- "build_id", completedBuild.Build.BuildId,
- )
- }
-
- return nil
-}
-
-// QueryAssets queries assets with automatic build triggering if not found
-// AIDEV-NOTE: This is the enhanced version of ListAssets that implements the complete
-// asset query + automatic build workflow for metald
-func (s *Service) QueryAssets(
- ctx context.Context,
- req *connect.Request[assetv1.QueryAssetsRequest],
-) (*connect.Response[assetv1.QueryAssetsResponse], error) {
- // Convert request to registry filters
- //nolint:exhaustruct // Limit and Offset are set below
- filters := registry.ListFilters{
- Type: req.Msg.GetType(),
- Status: req.Msg.GetStatus(),
- Labels: req.Msg.GetLabelSelector(),
- }
-
- // Handle pagination
- pageSize := int(req.Msg.GetPageSize())
- if pageSize == 0 {
- pageSize = 100
- }
- if pageSize > 1000 {
- pageSize = 1000
- }
- filters.Limit = pageSize
-
- // Parse page token (simple offset-based pagination)
- if req.Msg.GetPageToken() != "" {
- var offset int
- if _, err := fmt.Sscanf(req.Msg.GetPageToken(), "offset:%d", &offset); err == nil {
- filters.Offset = offset
- }
- }
-
- // Get assets
- assets, err := s.registry.ListAssets(filters)
- if err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to list assets",
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to list assets"))
- }
-
- var triggeredBuilds []*assetv1.BuildInfo
-
- // Check if we should trigger automatic builds
- buildOpts := req.Msg.GetBuildOptions()
- if len(assets) == 0 && buildOpts != nil && buildOpts.GetEnableAutoBuild() && s.cfg.BuilderdEnabled && s.builderdClient != nil {
- // Check if this is a request for rootfs with docker_image label
- if req.Msg.GetType() == assetv1.AssetType_ASSET_TYPE_ROOTFS || req.Msg.GetType() == assetv1.AssetType_ASSET_TYPE_UNSPECIFIED {
- if dockerImage, ok := req.Msg.GetLabelSelector()["docker_image"]; ok && dockerImage != "" {
- s.logger.InfoContext(ctx, "no rootfs found, triggering automatic build",
- "docker_image", dockerImage,
- )
-
- // Merge labels for the build
- buildLabels := make(map[string]string)
- for k, v := range req.Msg.GetLabelSelector() {
- buildLabels[k] = v
- }
- for k, v := range buildOpts.GetBuildLabels() {
- buildLabels[k] = v
- }
-
- s.logger.InfoContext(ctx, "triggering build with labels and asset ID",
- "build_labels", buildLabels,
- "suggested_asset_id", buildOpts.GetSuggestedAssetId(),
- )
-
- // Create build info
- buildInfo := &assetv1.BuildInfo{
- DockerImage: dockerImage,
- Status: "pending",
- }
-
- // Set timeout
- timeout := time.Duration(buildOpts.GetBuildTimeoutSeconds()) * time.Second
- if timeout == 0 {
- timeout = 30 * time.Minute // Default timeout
- }
-
- // Create context with timeout
- buildCtx, cancel := context.WithTimeout(ctx, timeout)
- defer cancel()
-
- // Trigger build
- tracer := otel.Tracer("assetmanagerd")
- buildCtx, buildSpan := tracer.Start(buildCtx, "assetmanagerd.service.trigger_build",
- trace.WithAttributes(
- attribute.String("docker.image", dockerImage),
- attribute.StringSlice("build.labels", func() []string {
- var labelPairs []string
- for k, v := range buildLabels {
- labelPairs = append(labelPairs, fmt.Sprintf("%s=%s", k, v))
- }
- return labelPairs
- }()),
- ),
- )
-
- buildID, err := s.builderdClient.BuildDockerRootfs(buildCtx, dockerImage, buildLabels)
- if err != nil {
- buildSpan.RecordError(err)
- buildSpan.SetStatus(codes.Error, err.Error())
- } else {
- buildSpan.SetAttributes(attribute.String("build.id", buildID))
- }
- buildSpan.End()
- if err != nil {
- s.logger.ErrorContext(ctx, "failed to trigger build",
- "docker_image", dockerImage,
- "error", err,
- )
- buildInfo.Status = "failed"
- buildInfo.ErrorMessage = fmt.Sprintf("failed to trigger build: %v", err)
- triggeredBuilds = append(triggeredBuilds, buildInfo)
- } else {
- buildInfo.BuildId = buildID
- buildInfo.Status = "building"
-
- // Wait for completion if requested
- if buildOpts.GetWaitForCompletion() {
- // AIDEV-NOTE: Use proper build timeout instead of poll interval
- buildTimeout := time.Duration(buildOpts.GetBuildTimeoutSeconds()) * time.Second
- if buildTimeout == 0 {
- buildTimeout = 30 * time.Minute // Default timeout
- }
-
- buildCtx, waitSpan := tracer.Start(buildCtx, "assetmanagerd.service.wait_for_build",
- trace.WithAttributes(
- attribute.String("build.id", buildID),
- attribute.String("docker.image", dockerImage),
- attribute.String("build.timeout", buildTimeout.String()),
- ),
- )
- completedBuild, err := s.builderdClient.WaitForBuild(buildCtx, buildID, buildTimeout)
- if err != nil {
- waitSpan.RecordError(err)
- waitSpan.SetStatus(codes.Error, err.Error())
- } else {
- waitSpan.SetAttributes(
- attribute.String("build.rootfs_path", completedBuild.Build.RootfsPath),
- attribute.String("build.status", completedBuild.Build.State.String()),
- )
- }
- waitSpan.End()
- if err != nil {
- s.logger.ErrorContext(ctx, "build failed or timed out",
- "build_id", buildID,
- "docker_image", dockerImage,
- "error", err,
- )
- buildInfo.Status = "failed"
- buildInfo.ErrorMessage = fmt.Sprintf("build failed: %v", err)
- } else {
- s.logger.InfoContext(ctx, "build completed successfully",
- "build_id", buildID,
- "docker_image", dockerImage,
- "rootfs_path", completedBuild.Build.RootfsPath,
- )
- buildInfo.Status = "completed"
-
- // Re-query for assets after successful build
- assets, err = s.registry.ListAssets(filters)
- if err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to list assets after build",
- slog.String("error", err.Error()),
- )
- } else if len(assets) > 0 {
- // Find the newly created asset
- buildInfo.AssetId = assets[0].GetId()
- }
- }
- }
-
- triggeredBuilds = append(triggeredBuilds, buildInfo)
- }
- }
- }
- }
-
- //nolint:exhaustruct // NextPageToken is optional and set below if needed
- resp := &assetv1.QueryAssetsResponse{
- Assets: assets,
- TriggeredBuilds: triggeredBuilds,
- }
-
- // Set next page token if we hit the limit
- if len(assets) == pageSize {
- resp.NextPageToken = fmt.Sprintf("offset:%d", filters.Offset+pageSize)
- }
-
- return connect.NewResponse(resp), nil
-}
diff --git a/go/apps/assetmanagerd/internal/storage/local.go b/go/apps/assetmanagerd/internal/storage/local.go
deleted file mode 100644
index 00883e2991..0000000000
--- a/go/apps/assetmanagerd/internal/storage/local.go
+++ /dev/null
@@ -1,184 +0,0 @@
-package storage
-
-import (
- "context"
- "crypto/sha256"
- "encoding/hex"
- "fmt"
- "io"
- "log/slog"
- "os"
- "path/filepath"
-)
-
-// LocalBackend implements Backend for local filesystem storage
-type LocalBackend struct {
- basePath string
- logger *slog.Logger
-}
-
-// NewLocalBackend creates a new local storage backend
-func NewLocalBackend(basePath string, logger *slog.Logger) (*LocalBackend, error) {
- // Ensure base path exists
- if err := os.MkdirAll(basePath, 0755); err != nil {
- return nil, fmt.Errorf("failed to create storage directory: %w", err)
- }
-
- return &LocalBackend{
- basePath: basePath,
- logger: logger.With("backend", "local"),
- }, nil
-}
-
-// Store stores an asset locally
-func (b *LocalBackend) Store(ctx context.Context, id string, reader io.Reader, size int64) (string, error) {
- // Create subdirectory based on first 2 chars of ID for better filesystem performance
- // AIDEV-NOTE: Sharding prevents too many files in a single directory
- subdir := id[:2]
- dirPath := filepath.Join(b.basePath, subdir)
- if err := os.MkdirAll(dirPath, 0755); err != nil {
- return "", fmt.Errorf("failed to create directory: %w", err)
- }
-
- filePath := filepath.Join(dirPath, id)
-
- // Create temporary file first
- tmpPath := filePath + ".tmp"
- tmpFile, err := os.Create(tmpPath)
- if err != nil {
- return "", fmt.Errorf("failed to create temporary file: %w", err)
- }
- defer os.Remove(tmpPath) // Clean up on any error
-
- // Copy data
- written, err := io.Copy(tmpFile, reader)
- if err != nil {
- tmpFile.Close()
- return "", fmt.Errorf("failed to write asset: %w", err)
- }
- tmpFile.Close()
-
- // Verify size if provided
- if size > 0 && written != size {
- return "", fmt.Errorf("size mismatch: expected %d, got %d", size, written)
- }
-
- // Atomic rename
- if err := os.Rename(tmpPath, filePath); err != nil {
- return "", fmt.Errorf("failed to finalize asset: %w", err)
- }
-
- b.logger.LogAttrs(ctx, slog.LevelInfo, "stored asset",
- slog.String("id", id),
- slog.String("path", filePath),
- slog.Int64("size", written),
- )
-
- // Return relative path from base
- return filepath.Join(subdir, id), nil
-}
-
-// Retrieve retrieves an asset from local storage
-func (b *LocalBackend) Retrieve(ctx context.Context, location string) (io.ReadCloser, error) {
- fullPath := filepath.Join(b.basePath, location)
-
- file, err := os.Open(fullPath)
- if err != nil {
- if os.IsNotExist(err) {
- return nil, fmt.Errorf("asset not found: %s", location)
- }
- return nil, fmt.Errorf("failed to open asset: %w", err)
- }
-
- return file, nil
-}
-
-// Delete deletes an asset from local storage
-func (b *LocalBackend) Delete(ctx context.Context, location string) error {
- fullPath := filepath.Join(b.basePath, location)
-
- if err := os.Remove(fullPath); err != nil {
- if os.IsNotExist(err) {
- return nil // Already deleted
- }
- return fmt.Errorf("failed to delete asset: %w", err)
- }
-
- b.logger.LogAttrs(ctx, slog.LevelInfo, "deleted asset",
- slog.String("location", location),
- slog.String("path", fullPath),
- )
-
- return nil
-}
-
-// Exists checks if an asset exists
-func (b *LocalBackend) Exists(ctx context.Context, location string) (bool, error) {
- fullPath := filepath.Join(b.basePath, location)
-
- _, err := os.Stat(fullPath)
- if err != nil {
- if os.IsNotExist(err) {
- return false, nil
- }
- return false, fmt.Errorf("failed to stat asset: %w", err)
- }
-
- return true, nil
-}
-
-// GetSize returns the size of an asset
-func (b *LocalBackend) GetSize(ctx context.Context, location string) (int64, error) {
- fullPath := filepath.Join(b.basePath, location)
-
- info, err := os.Stat(fullPath)
- if err != nil {
- if os.IsNotExist(err) {
- return 0, fmt.Errorf("asset not found: %s", location)
- }
- return 0, fmt.Errorf("failed to stat asset: %w", err)
- }
-
- return info.Size(), nil
-}
-
-// GetChecksum calculates and returns the SHA256 checksum
-func (b *LocalBackend) GetChecksum(ctx context.Context, location string) (string, error) {
- fullPath := filepath.Join(b.basePath, location)
-
- file, err := os.Open(fullPath)
- if err != nil {
- if os.IsNotExist(err) {
- return "", fmt.Errorf("asset not found: %s", location)
- }
- return "", fmt.Errorf("failed to open asset: %w", err)
- }
- defer file.Close()
-
- hasher := sha256.New()
- if _, err := io.Copy(hasher, file); err != nil {
- return "", fmt.Errorf("failed to calculate checksum: %w", err)
- }
-
- return hex.EncodeToString(hasher.Sum(nil)), nil
-}
-
-// EnsureLocal returns the full path for local assets
-func (b *LocalBackend) EnsureLocal(ctx context.Context, location string, cacheDir string) (string, error) {
- fullPath := filepath.Join(b.basePath, location)
-
- // Verify it exists
- if _, err := os.Stat(fullPath); err != nil {
- if os.IsNotExist(err) {
- return "", fmt.Errorf("asset not found: %s", location)
- }
- return "", fmt.Errorf("failed to stat asset: %w", err)
- }
-
- return fullPath, nil
-}
-
-// Type returns the backend type
-func (b *LocalBackend) Type() string {
- return "local"
-}
diff --git a/go/apps/assetmanagerd/internal/storage/storage.go b/go/apps/assetmanagerd/internal/storage/storage.go
deleted file mode 100644
index 47ccb24b5a..0000000000
--- a/go/apps/assetmanagerd/internal/storage/storage.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package storage
-
-import (
- "context"
- "fmt"
- "io"
- "log/slog"
-
- "github.com/unkeyed/unkey/go/apps/assetmanagerd/internal/config"
-)
-
-// Backend defines the interface for asset storage backends
-type Backend interface {
- // Store stores an asset and returns its location
- Store(ctx context.Context, id string, reader io.Reader, size int64) (string, error)
-
- // Retrieve retrieves an asset by its location
- Retrieve(ctx context.Context, location string) (io.ReadCloser, error)
-
- // Delete deletes an asset by its location
- Delete(ctx context.Context, location string) error
-
- // Exists checks if an asset exists at the given location
- Exists(ctx context.Context, location string) (bool, error)
-
- // GetSize returns the size of an asset in bytes
- GetSize(ctx context.Context, location string) (int64, error)
-
- // GetChecksum returns the SHA256 checksum of an asset
- GetChecksum(ctx context.Context, location string) (string, error)
-
- // EnsureLocal ensures an asset is available locally and returns the local path
- // For local backend, this just returns the location
- // For remote backends, this downloads to cache if needed
- EnsureLocal(ctx context.Context, location string, cacheDir string) (string, error)
-
- // Type returns the backend type
- Type() string
-}
-
-// NewBackend creates a new storage backend based on configuration
-func NewBackend(cfg *config.Config, logger *slog.Logger) (Backend, error) {
- switch cfg.StorageBackend {
- case "local":
- return NewLocalBackend(cfg.LocalStoragePath, logger)
- case "s3":
- return nil, fmt.Errorf("S3 backend not yet implemented")
- case "nfs":
- return nil, fmt.Errorf("NFS backend not yet implemented")
- default:
- return nil, fmt.Errorf("unsupported storage backend: %s", cfg.StorageBackend)
- }
-}
diff --git a/go/apps/billaged/.gitignore b/go/apps/billaged/.gitignore
deleted file mode 100644
index 81b5230d36..0000000000
--- a/go/apps/billaged/.gitignore
+++ /dev/null
@@ -1,87 +0,0 @@
-# Compiled binaries (SECURITY: Never commit compiled binaries)
-build/
-*.exe
-*.dll
-*.so
-*.dylib
-
-# Test binaries, built with `go test -c`
-*.test
-
-# Output of the go coverage tool
-*.out
-
-# Dependency directories (remove the comment below to include it)
-vendor/
-
-# Go workspace file
-go.work
-go.work.sum
-
-# IDE files
-.vscode/
-.idea/
-*.swp
-*.swo
-*~
-
-# OS files
-.DS_Store
-Thumbs.db
-
-# Local development files
-.env
-.env.local
-.env.development
-.env.test
-.env.production
-
-# Temporary files
-tmp/
-temp/
-*.tmp
-
-# Logs
-*.log
-logs/
-
-# Database files
-*.db
-*.sqlite
-*.sqlite3
-
-# Build artifacts and cache
-dist/
-cache/
-.cache/
-
-# Coverage reports
-coverage.html
-coverage.out
-profile.out
-
-# Backup files
-*.bak
-*.backup
-
-# Docker build context (if using dockerignore isn't sufficient)
-.dockerignore
-
-# Certificate files (never commit certificates or keys)
-*.pem
-*.key
-*.crt
-*.p12
-*.pfx
-
-# Secret files
-secrets.yaml
-secrets.json
-.secrets
-
-# Local storage directories for development
-data/
-storage/
-scratch/
-rootfs/
-workspace/
diff --git a/go/apps/billaged/CHANGELOG.md b/go/apps/billaged/CHANGELOG.md
deleted file mode 100644
index d8c1a37650..0000000000
--- a/go/apps/billaged/CHANGELOG.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# Changelog
-
-All notable changes to billaged will be documented in this file.
-
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-
-## [0.5.1] - 2025-07-02
-
-### Changed
-- Update client.go,types.go,main.go,
-
diff --git a/go/apps/billaged/DOCUMENTATION_REPORT.md b/go/apps/billaged/DOCUMENTATION_REPORT.md
deleted file mode 100644
index 94ee60ec78..0000000000
--- a/go/apps/billaged/DOCUMENTATION_REPORT.md
+++ /dev/null
@@ -1,172 +0,0 @@
-# Billaged Documentation Generation Report
-
-**Service**: billaged
-**Generated**: 2025-07-03
-**Total Documentation**: 4 comprehensive documents + 1 main overview
-
-## Service Analysis Summary
-
-### Discovered Service Structure
-
-**Go Files**: 11 source files
-- Main service: `cmd/billaged/main.go`
-- CLI tool: `cmd/billaged-cli/main.go`
-- Core service: `internal/service/billing.go`
-- Aggregation engine: `internal/aggregator/aggregator.go`
-- Configuration: `internal/config/config.go`
-- Observability: `internal/observability/metrics.go`, `internal/observability/otel.go`
-- Client library: `client/client.go`, `client/types.go`
-
-**Protocol Buffer Files**: 1 definition
-- Service API: `proto/billing/v1/billing.proto`
-
-**Module**: `github.com/unkeyed/unkey/go/deploy/billaged`
-
-### Service Dependencies Identified
-
-#### Core Dependencies
-- **metald** - Primary source of VM usage metrics and lifecycle events
-- **SPIFFE/Spire** - mTLS authentication and service authorization
-- **OpenTelemetry** - Observability, metrics export, and distributed tracing
-
-#### Integration Patterns
-- **ConnectRPC** - HTTP/2-based service communication
-- **Real-time Aggregation** - In-memory usage data processing
-- **Resource Scoring** - Weighted algorithms for billing calculations
-- **Multi-tenant Isolation** - Customer-scoped data aggregation
-
-## Documentation Structure Generated
-
-### 1. Main Documentation (docs/README.md)
-**Size**: 254 lines
-**Content**: Service overview, architecture diagram, quick start guide, and navigation
-
-**Key Sections**:
-- Service role and dependencies
-- Architecture overview with flow diagram
-- API highlights and examples
-- Production deployment guidance
-- Monitoring and development setup
-
-### 2. API Documentation (docs/api/README.md)
-**Size**: 367 lines
-**Content**: Complete ConnectRPC API reference with examples
-
-**Key Sections**:
-- All 5 RPC endpoints with schemas
-- Authentication and authorization patterns
-- Client library usage examples
-- Error handling and rate limits
-- Integration patterns and best practices
-
-### 3. Architecture Guide (docs/architecture/README.md)
-**Size**: 441 lines
-**Content**: Deep dive into service design and implementation
-
-**Key Sections**:
-- Core component architecture
-- Data flow patterns and processing pipelines
-- Resource scoring algorithm with business rules
-- Multi-tenant isolation strategies
-- Performance characteristics and optimization
-
-### 4. Operations Manual (docs/operations/README.md)
-**Size**: 512 lines
-**Content**: Production deployment and management
-
-**Key Sections**:
-- Installation and system requirements
-- Configuration management and templates
-- Monitoring setup with Prometheus/Grafana
-- Troubleshooting guides and diagnostic commands
-- Security operations and capacity planning
-
-### 5. Development Setup (docs/development/README.md)
-**Size**: 496 lines
-**Content**: Local development and testing
-
-**Key Sections**:
-- Build system and dependencies
-- Local development configuration
-- Testing strategies and frameworks
-- Debugging tools and techniques
-- Code quality and contribution guidelines
-
-## Key Technical Findings
-
-### Resource Scoring Algorithm
-**Implementation**: `aggregator.go:282-305`
-
-Billaged uses a sophisticated weighted scoring system:
-```
-resourceScore = (cpuSeconds × 1.0) + (memoryGB × 0.5) + (diskMB × 0.3)
-```
-
-### Real-time Aggregation
-**Performance**: 10,000+ metrics/second processing capability
-**Memory**: ~1MB per 1000 active VMs
-**Architecture**: Thread-safe in-memory data structures with delta calculations
-
-### Multi-tenant Security
-**Authentication**: SPIFFE workload identity verification
-**Isolation**: Customer-scoped data aggregation with tenant boundaries
-**Authorization**: Service-to-service mTLS communication
-
-## Integration Documentation
-
-### metald Integration
-**Status**: ✅ Documented with complete interaction patterns
-**Details**: Real-time metrics push, lifecycle events, heartbeat monitoring
-
-### SPIFFE/Spire Integration
-**Status**: ✅ Documented with security configuration
-**Details**: Workload identity, certificate management, transport security
-
-### OpenTelemetry Integration
-**Status**: ✅ Documented with monitoring setup
-**Details**: Metrics export, distributed tracing, performance monitoring
-
-## Source Code References
-
-All documentation includes direct source code references in the format `[concept](file_path:line_number)`:
-
-- **145 source code references** across all documentation
-- **Line-specific links** for implementation details
-- **Cross-references** between related concepts and dependencies
-
-## Completeness Checklist
-
-- ✅ All public APIs documented with examples
-- ✅ All discovered dependencies documented with interaction patterns
-- ✅ Service interaction patterns clearly described with SPIFFE & Spire
-- ✅ Configuration options explained with validation rules
-- ✅ Error scenarios documented with response codes
-- ✅ Monitoring and observability fully covered
-- ✅ Development workflow clearly explained
-- ✅ All claims linked to source code references
-
-## Quality Standards Met
-
-- **Accuracy**: 145 direct source code references ensure documentation accuracy
-- **Ecosystem Awareness**: Documented role in 4-pillar service architecture
-- **Dynamic Learning**: Enhanced understanding through dependency documentation analysis
-- **Operational Focus**: Comprehensive production deployment and troubleshooting guides
-- **Code-First Approach**: Every documented behavior traceable to implementation
-
-## Generated Files Summary
-
-1. `docs/README.md` - Service overview and navigation (254 lines)
-2. `docs/api/README.md` - Complete API reference (367 lines)
-3. `docs/architecture/README.md` - System design deep dive (441 lines)
-4. `docs/operations/README.md` - Production operations manual (512 lines)
-5. `docs/development/README.md` - Development setup guide (496 lines)
-
-**Total Documentation**: 2,070 lines of comprehensive technical documentation
-
-## Notes
-
-- No QUESTIONS.md file existed for billaged service
-- Documentation organized in subdirectories due to size (>200 lines)
-- All AIDEV anchor comments preserved and referenced appropriately
-- Integration with existing service documentation (metald, assetmanagerd, builderd)
-- Production-ready configuration examples and security best practices included
\ No newline at end of file
diff --git a/go/apps/billaged/Makefile b/go/apps/billaged/Makefile
deleted file mode 100644
index 1b86c813a3..0000000000
--- a/go/apps/billaged/Makefile
+++ /dev/null
@@ -1,130 +0,0 @@
-# Billaged VM Usage Billing Service Makefile
-
-.DEFAULT_GOAL := help
-
-# Variables
-BINARY_NAME=billaged
-BUILD_DIR=build
-VERSION ?= 0.5.1
-COMMIT=$(shell git rev-parse --short HEAD || echo "unknown")
-BUILD_TIME=$(shell date -u '+%Y-%m-%d_%H:%M:%S')
-LDFLAGS=-ldflags "-s -w -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.buildTime=$(BUILD_TIME)"
-
-# Targets (alphabetically ordered)
-.PHONY: build check clean clean-gen create-user deps dev env-example fmt generate health help install lint lint-proto run service-logs service-logs-tail service-restart service-start service-status service-stop setup test test-coverage uninstall version vet
-
-build: generate deps ## Build the billaged service binary
- @mkdir -p $(BUILD_DIR)
- @go build $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME) ./cmd/billaged
-
-check: fmt vet lint test ## Run all checks (fmt, vet, lint with proto, test)
-
-clean: ## Clean build artifacts
- @rm -rf $(BUILD_DIR)
- @rm -f coverage.out coverage.html
-
-clean-gen: ## Clean generated protobuf code
- rm -rf ./gen
-
-create-user: ## Create service user
- @sudo useradd -r -s /bin/false -d /opt/billaged -c "Billaged VM Usage Billing Service" billaged 2>/dev/null || true
-
-deps: ## Download and tidy dependencies
- @go mod download
- @go mod tidy
-
-dev: ## Run the service in development mode
- @go run ./cmd/billaged
-
-env-example: ## Generate example environment file
- @echo "# Billaged Environment Variables" > .env.example
- @echo "UNKEY_BILLAGED_PORT=8081" >> .env.example
- @echo "UNKEY_BILLAGED_AGGREGATION_INTERVAL=60s" >> .env.example
- @echo "UNKEY_BILLAGED_ASSET_MANAGER_ADDR=localhost:50052" >> .env.example
- @echo "UNKEY_BILLAGED_SPIFFE_ENABLED=false" >> .env.example
- @echo "✓ .env.example created"
-
-fmt: ## Format Go code
- @goimports -w .
-
-generate: ## Generate protobuf code
- @buf generate
- @buf lint
-
-health: ## Check service health
- @curl -s http://localhost:8081/health | jq . || echo "Health check failed"
-
-help: ## Show this help message
- @echo ""
- @echo "Billaged VM Usage Billing Service - Available targets:"
- @echo ""
- @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
- @echo ""
-
-install: build create-user ## Install billaged binary and systemd service
- @sudo systemctl stop billaged 2>/dev/null || true
- @sudo cp $(BUILD_DIR)/$(BINARY_NAME) /usr/local/bin/$(BINARY_NAME)
- @sudo chmod +x /usr/local/bin/$(BINARY_NAME)
- @sudo cp contrib/systemd/billaged.service /etc/systemd/system/billaged.service
- @sudo systemctl daemon-reload
- @sudo systemctl start billaged 2>/dev/null || true
- @echo "✓ billaged installed and started"
-
-lint: lint-proto ## Run linting tools (includes protobuf linting)
- @which golangci-lint >/dev/null || (echo "golangci-lint not found, install from https://golangci-lint.run/usage/install/" && exit 1)
- @golangci-lint run --disable=godox
-
-lint-proto: ## Run protobuf linter
- @buf lint
-
-run: build ## Build and run the billaged service
- @./$(BUILD_DIR)/$(BINARY_NAME)
-
-service-logs: ## Show all billaged service logs
- @sudo journalctl -u billaged --no-pager
-
-service-logs-tail: ## Follow billaged service logs
- @sudo journalctl -u billaged -f
-
-service-restart: ## Restart billaged service
- @sudo systemctl restart billaged
- @echo "✓ billaged restarted"
-
-service-start: ## Start billaged service
- @sudo systemctl start billaged
- @echo "✓ billaged started"
-
-service-status: ## Show billaged service status
- @sudo systemctl status billaged
-
-service-stop: ## Stop billaged service
- @sudo systemctl stop billaged
- @echo "✓ billaged stopped"
-
-setup: deps generate ## Complete development setup
-
-test: ## Run all tests
- @go test ./... -v
-
-test-coverage: ## Run tests with coverage report
- @go test -coverprofile=coverage.out ./...
- @go tool cover -html=coverage.out -o coverage.html
- @echo "✓ Coverage report: coverage.html"
-
-uninstall: ## Uninstall billaged service and binary
- @sudo systemctl stop billaged 2>/dev/null || true
- @sudo systemctl disable billaged 2>/dev/null || true
- @sudo rm -f /etc/systemd/system/billaged.service
- @sudo rm -f /usr/local/bin/$(BINARY_NAME)
- @sudo systemctl daemon-reload
- @echo "✓ billaged uninstalled"
-
-version: ## Show version information
- @echo "Billaged Version Information:"
- @echo " Version: $(VERSION)"
- @echo " Commit: $(COMMIT)"
- @echo " Build Time: $(BUILD_TIME)"
-
-vet: ## Run go vet
- @go vet ./...
-
diff --git a/go/apps/billaged/README.md b/go/apps/billaged/README.md
deleted file mode 100644
index 3a1dc98ccd..0000000000
--- a/go/apps/billaged/README.md
+++ /dev/null
@@ -1,193 +0,0 @@
-# Billaged - VM Usage Billing Aggregation Service
-
-Billaged is a lightweight, stateless service that collects and aggregates virtual machine usage metrics from [metald](../metald/docs/README.md) instances for billing purposes in the Unkey Deploy platform.
-
-## Quick Links
-
-- [API Documentation](./docs/api/README.md) - Complete API reference with examples
-- [Architecture & Dependencies](./docs/architecture/README.md) - Service interactions and data flow
-- [Operations Guide](./docs/operations/README.md) - Production deployment and monitoring
-- [Development Setup](./docs/development/README.md) - Build and test instructions
-
-## Service Overview
-
-**Purpose**: Real-time aggregation of VM resource usage metrics for billing calculations.
-
-### Key Features
-
-- **High-frequency Ingestion**: Processes metrics batches from multiple metald instances
-- **In-memory Aggregation**: Configurable aggregation intervals (default: 60s)
-- **Composite Billing Score**: Weighted resource usage calculation
-- **Stateless Design**: No database dependencies, all data in-memory
-- **Gap Detection**: Handles metric collection interruptions gracefully
-- **Observable**: OpenTelemetry tracing, Prometheus metrics, structured logging
-- **Secure Communication**: SPIFFE/mTLS support for service authentication
-
-### Dependencies
-
-- [metald](../metald/docs/README.md) - Sends VM usage metrics and lifecycle events
-
-## Quick Start
-
-### Installation
-
-```bash
-# Build from source
-cd billaged
-make build
-
-# Install with systemd
-sudo make install
-```
-
-### Basic Configuration
-
-```bash
-# Minimal configuration for development
-export UNKEY_BILLAGED_PORT=8081
-export UNKEY_BILLAGED_ADDRESS=0.0.0.0
-export UNKEY_BILLAGED_AGGREGATION_INTERVAL=60s
-export UNKEY_BILLAGED_TLS_MODE=disabled
-
-./billaged
-```
-
-### Testing the Service
-
-```bash
-# Check health
-curl http://localhost:8081/health
-
-# View aggregation stats
-curl http://localhost:8081/stats
-
-# Send test metrics (see API docs for full examples)
-curl -X POST http://localhost:8081/billing.v1.BillingService/SendMetricsBatch \
- -H "Content-Type: application/json" \
- -d '{
- "vm_id": "test-vm-123",
- "customer_id": "customer-456",
- "metrics": [{
- "timestamp": "2024-01-01T12:00:00Z",
- "cpu_time_nanos": 1000000000,
- "memory_usage_bytes": 1073741824
- }]
- }'
-```
-
-## Architecture Overview
-
-```mermaid
-graph TB
- subgraph "Metald Instances"
- M1[Metald 1]
- M2[Metald 2]
- MN[Metald N]
- end
-
- subgraph "Billaged Service"
- API[ConnectRPC API
:8081]
- AGG[Aggregator]
- MEM[(In-Memory Store)]
- end
-
- subgraph "Outputs"
- LOGS[JSON Logs]
- PROM[Prometheus
:9465]
- OTLP[OTLP Collector
:4318]
- end
-
- M1 -->|Metrics Batch| API
- M2 -->|Lifecycle Events| API
- MN -->|Heartbeats| API
-
- API --> AGG
- AGG --> MEM
-
- AGG -->|Summaries| LOGS
- AGG -->|Metrics| PROM
- AGG -->|Traces| OTLP
-```
-
-## Billing Score Calculation
-
-The service calculates a composite billing score based on weighted resource usage:
-
-```go
-resourceScore = (cpuSeconds * 1.0) + (memoryGB * 0.5) + (diskMB * 0.3)
-```
-
-[View implementation](internal/aggregator/aggregator.go:282-326)
-
-## API Highlights
-
-The service exposes a ConnectRPC API with five main operations:
-
-- `SendMetricsBatch` - Ingest VM usage metrics from metald
-- `SendHeartbeat` - Receive active VM lists from metald instances
-- `NotifyVmStarted` - Handle VM lifecycle start events
-- `NotifyVmStopped` - Handle VM lifecycle stop events
-- `NotifyPossibleGap` - Handle data gap notifications
-
-See [API Documentation](./docs/api/README.md) for complete reference.
-
-## Production Deployment
-
-### System Requirements
-
-- **OS**: Linux (any distribution)
-- **CPU**: 2+ cores recommended
-- **Memory**: 2GB+ for typical workloads
-- **Network**: Low latency connection to metald instances
-
-### Configuration
-
-Key environment variables:
-
-- `UNKEY_BILLAGED_PORT` - Service port (default: 8081)
-- `UNKEY_BILLAGED_AGGREGATION_INTERVAL` - Summary interval (default: 60s)
-- `UNKEY_BILLAGED_TLS_MODE` - TLS mode: disabled/file/spiffe (default: spiffe)
-- `UNKEY_BILLAGED_ENABLE_OTEL` - Enable OpenTelemetry (default: false)
-
-See [Operations Guide](./docs/operations/README.md) for complete configuration.
-
-## Monitoring
-
-Key metrics to monitor:
-
-- `billaged_usage_records_processed_total` - Usage records processed
-- `billaged_aggregation_duration_seconds` - Aggregation latency
-- `billaged_active_vms` - Currently tracked VMs
-- `billaged_billing_errors_total` - Processing errors
-
-## Development
-
-### Building from Source
-
-```bash
-git clone https://github.com/unkeyed/unkey
-cd go/billaged
-make test
-make build
-```
-
-### Running Tests
-
-```bash
-# Unit tests
-make test
-
-# Lint checks
-make lint
-
-# All checks
-make ci
-```
-
-See [Development Setup](./docs/development/README.md) for detailed instructions.
-
-## Support
-
-- **Issues**: [GitHub Issues](https://github.com/unkeyed/unkey/issues)
-- **Documentation**: [Full Documentation](./docs/README.md)
-- **Version**: v0.1.0
diff --git a/go/apps/billaged/TODO.md b/go/apps/billaged/TODO.md
deleted file mode 100644
index 5ab8512727..0000000000
--- a/go/apps/billaged/TODO.md
+++ /dev/null
@@ -1,59 +0,0 @@
-# Billaged TODO
-
-## High Priority
-
-- [ ] Implement proper ClickHouse schema migrations
- - Version tracking for schema changes
- - Rollback capabilities
- - Migration testing framework
-
-- [ ] Add rate limiting for billing events
- - Per-tenant rate limits
- - Circuit breaker for ClickHouse writes
- - Backpressure handling
-
-## Medium Priority
-
-- [ ] Implement billing event deduplication
- - Idempotency keys for events
- - Duplicate detection window
- - Metrics for duplicate events
-
-- [ ] Add billing aggregation optimizations
- - Pre-aggregated materialized views
- - Configurable aggregation windows
- - Real-time vs batch aggregation modes
-
-- [ ] Implement data retention policies
- - Configurable retention per event type
- - Automated data archival
- - Compliance with data regulations
-
-## Low Priority
-
-- [ ] Add support for multiple ClickHouse clusters
- - Read/write splitting
- - Cluster health monitoring
- - Automatic failover
-
-- [ ] Implement billing event replay
- - Event sourcing capabilities
- - Point-in-time recovery
- - Audit trail for billing changes
-
-- [ ] Add billing analytics endpoints
- - Cost breakdown by resource
- - Usage trends and forecasting
- - Anomaly detection
-
-## Completed
-
-- [x] Basic service implementation
-- [x] ClickHouse integration
-- [x] ConnectRPC API
-- [x] Event aggregation
-- [x] Prometheus metrics
-- [x] SPIFFE/mTLS support
-- [x] Grafana dashboards
-- [x] Unified health endpoint
-- [x] Unified Makefile structure
\ No newline at end of file
diff --git a/go/apps/billaged/client/Makefile b/go/apps/billaged/client/Makefile
deleted file mode 100644
index b48196a138..0000000000
--- a/go/apps/billaged/client/Makefile
+++ /dev/null
@@ -1,38 +0,0 @@
-# Makefile for billaged CLI client
-
-# Variables
-BINARY_NAME := billaged-cli
-BUILD_DIR := build
-VERSION ?= 0.5.1
-
-# Default target
-.DEFAULT_GOAL := help
-
-# Targets (alphabetically ordered)
-
-.PHONY: build
-build: ## Build the billaged CLI client
- @echo "Building $(BINARY_NAME)..."
- @mkdir -p $(BUILD_DIR)
- @go build -o $(BUILD_DIR)/$(BINARY_NAME) ../cmd/billaged-cli/main.go
- @echo "Build complete: $(BUILD_DIR)/$(BINARY_NAME)"
-
-.PHONY: clean
-clean: ## Clean build artifacts
- @echo "Cleaning..."
- @rm -rf $(BUILD_DIR)
-
-.PHONY: help
-help: ## Show this help message
- @echo "Available targets:"
- @echo " build - Build the billaged CLI client"
- @echo " clean - Clean build artifacts"
- @echo " install - Install the CLI client to /usr/local/bin"
- @echo " help - Show this help message"
-
-.PHONY: install
-install: build ## Install the CLI client to /usr/local/bin
- @echo "Installing $(BINARY_NAME) to /usr/local/bin..."
- @sudo mv $(BUILD_DIR)/$(BINARY_NAME) /usr/local/bin/$(BINARY_NAME)
- @sudo chmod +x /usr/local/bin/$(BINARY_NAME)
- @echo "Installation complete"
\ No newline at end of file
diff --git a/go/apps/billaged/client/client.go b/go/apps/billaged/client/client.go
deleted file mode 100644
index 70bb8c3974..0000000000
--- a/go/apps/billaged/client/client.go
+++ /dev/null
@@ -1,244 +0,0 @@
-package client
-
-import (
- "context"
- "fmt"
- "net/http"
- "time"
-
- "connectrpc.com/connect"
- "github.com/unkeyed/unkey/go/deploy/pkg/tls"
- billingv1 "github.com/unkeyed/unkey/go/gen/proto/billaged/v1"
- "github.com/unkeyed/unkey/go/gen/proto/billaged/v1/billagedv1connect"
-)
-
-// AIDEV-NOTE: Billaged client with SPIFFE/SPIRE socket integration
-// This client provides a high-level interface for billaged operations with proper authentication
-
-// Config holds the configuration for the billaged client
-type Config struct {
- // ServerAddress is the billaged server endpoint (e.g., "https://billaged:8081")
- ServerAddress string
-
- // UserID is the user identifier for authentication
- UserID string
-
- // TenantID is the tenant identifier for data scoping
- TenantID string
-
- // TLS configuration
- TLSMode string // "disabled", "file", or "spiffe"
- SPIFFESocketPath string // Path to SPIFFE agent socket
- TLSCertFile string // TLS certificate file (for file mode)
- TLSKeyFile string // TLS key file (for file mode)
- TLSCAFile string // TLS CA file (for file mode)
- EnableCertCaching bool // Enable certificate caching
- CertCacheTTL time.Duration // Certificate cache TTL
-
- // Optional HTTP client timeout
- Timeout time.Duration
-}
-
-// Client provides a high-level interface to billaged services
-type Client struct {
- billingService billagedv1connect.BillingServiceClient
- tlsProvider tls.Provider
- userID string
- tenantID string
- serverAddr string
-}
-
-// New creates a new billaged client with SPIFFE/SPIRE integration
-func New(ctx context.Context, config Config) (*Client, error) {
- // Set defaults
- if config.SPIFFESocketPath == "" {
- config.SPIFFESocketPath = "/var/lib/spire/agent/agent.sock"
- }
- if config.TLSMode == "" {
- config.TLSMode = "spiffe"
- }
- if config.Timeout == 0 {
- config.Timeout = 30 * time.Second
- }
- if config.CertCacheTTL == 0 {
- config.CertCacheTTL = 5 * time.Second
- }
-
- // Create TLS provider
- tlsConfig := tls.Config{
- Mode: tls.Mode(config.TLSMode),
- CertFile: config.TLSCertFile,
- KeyFile: config.TLSKeyFile,
- CAFile: config.TLSCAFile,
- SPIFFESocketPath: config.SPIFFESocketPath,
- EnableCertCaching: config.EnableCertCaching,
- CertCacheTTL: config.CertCacheTTL,
- }
-
- tlsProvider, err := tls.NewProvider(ctx, tlsConfig)
- if err != nil {
- return nil, fmt.Errorf("failed to create TLS provider: %w", err)
- }
-
- // Get HTTP client with SPIFFE mTLS
- httpClient := tlsProvider.HTTPClient()
- httpClient.Timeout = config.Timeout
-
- // Add authentication and tenant isolation transport
- httpClient.Transport = &tenantTransport{
- Base: httpClient.Transport,
- UserID: config.UserID,
- TenantID: config.TenantID,
- }
-
- // Create ConnectRPC client
- billingService := billagedv1connect.NewBillingServiceClient(
- httpClient,
- config.ServerAddress,
- )
-
- return &Client{
- billingService: billingService,
- tlsProvider: tlsProvider,
- userID: config.UserID,
- tenantID: config.TenantID,
- serverAddr: config.ServerAddress,
- }, nil
-}
-
-// Close closes the client and cleans up resources
-func (c *Client) Close() error {
- if c.tlsProvider != nil {
- return c.tlsProvider.Close()
- }
- return nil
-}
-
-// SendMetricsBatch sends a batch of VM metrics to the billing service
-func (c *Client) SendMetricsBatch(ctx context.Context, req *SendMetricsBatchRequest) (*SendMetricsBatchResponse, error) {
- pbReq := &billingv1.SendMetricsBatchRequest{
- VmId: req.VmID,
- CustomerId: req.CustomerID,
- Metrics: req.Metrics,
- }
-
- resp, err := c.billingService.SendMetricsBatch(ctx, connect.NewRequest(pbReq))
- if err != nil {
- return nil, fmt.Errorf("failed to send metrics batch: %w", err)
- }
-
- return &SendMetricsBatchResponse{
- Success: resp.Msg.Success,
- Message: resp.Msg.Message,
- }, nil
-}
-
-// SendHeartbeat sends a heartbeat to indicate this instance is alive
-func (c *Client) SendHeartbeat(ctx context.Context, req *SendHeartbeatRequest) (*SendHeartbeatResponse, error) {
- pbReq := &billingv1.SendHeartbeatRequest{
- InstanceId: req.InstanceID,
- ActiveVms: req.ActiveVMs,
- }
-
- resp, err := c.billingService.SendHeartbeat(ctx, connect.NewRequest(pbReq))
- if err != nil {
- return nil, fmt.Errorf("failed to send heartbeat: %w", err)
- }
-
- return &SendHeartbeatResponse{
- Success: resp.Msg.Success,
- }, nil
-}
-
-// NotifyVmStarted notifies the billing service that a VM has started
-func (c *Client) NotifyVmStarted(ctx context.Context, req *NotifyVmStartedRequest) (*NotifyVmStartedResponse, error) {
- pbReq := &billingv1.NotifyVmStartedRequest{
- VmId: req.VmID,
- CustomerId: req.CustomerID,
- StartTime: req.StartTime,
- }
-
- resp, err := c.billingService.NotifyVmStarted(ctx, connect.NewRequest(pbReq))
- if err != nil {
- return nil, fmt.Errorf("failed to notify VM started: %w", err)
- }
-
- return &NotifyVmStartedResponse{
- Success: resp.Msg.Success,
- }, nil
-}
-
-// NotifyVmStopped notifies the billing service that a VM has stopped
-func (c *Client) NotifyVmStopped(ctx context.Context, req *NotifyVmStoppedRequest) (*NotifyVmStoppedResponse, error) {
- pbReq := &billingv1.NotifyVmStoppedRequest{
- VmId: req.VmID,
- StopTime: req.StopTime,
- }
-
- resp, err := c.billingService.NotifyVmStopped(ctx, connect.NewRequest(pbReq))
- if err != nil {
- return nil, fmt.Errorf("failed to notify VM stopped: %w", err)
- }
-
- return &NotifyVmStoppedResponse{
- Success: resp.Msg.Success,
- }, nil
-}
-
-// NotifyPossibleGap notifies about a possible gap in metrics reporting
-func (c *Client) NotifyPossibleGap(ctx context.Context, req *NotifyPossibleGapRequest) (*NotifyPossibleGapResponse, error) {
- pbReq := &billingv1.NotifyPossibleGapRequest{
- VmId: req.VmID,
- LastSent: req.LastSent,
- ResumeTime: req.ResumeTime,
- }
-
- resp, err := c.billingService.NotifyPossibleGap(ctx, connect.NewRequest(pbReq))
- if err != nil {
- return nil, fmt.Errorf("failed to notify possible gap: %w", err)
- }
-
- return &NotifyPossibleGapResponse{
- Success: resp.Msg.Success,
- }, nil
-}
-
-// GetTenantID returns the tenant ID associated with this client
-func (c *Client) GetTenantID() string {
- return c.tenantID
-}
-
-// GetServerAddress returns the server address this client is connected to
-func (c *Client) GetServerAddress() string {
- return c.serverAddr
-}
-
-// tenantTransport adds authentication and tenant isolation headers to all requests
-type tenantTransport struct {
- Base http.RoundTripper
- UserID string
- TenantID string
-}
-
-func (t *tenantTransport) RoundTrip(req *http.Request) (*http.Response, error) {
- // Clone the request to avoid modifying the original
- req2 := req.Clone(req.Context())
- if req2.Header == nil {
- req2.Header = make(http.Header)
- }
-
- // Set Authorization header with development token format
- // AIDEV-BUSINESS_RULE: In development, use "dev_user_" format
- // TODO: Update to proper JWT tokens in production
- req2.Header.Set("Authorization", fmt.Sprintf("Bearer dev_user_%s", t.UserID))
-
- // Also set X-Tenant-ID header for tenant identification
- req2.Header.Set("X-Tenant-ID", t.TenantID)
-
- // Use the base transport, or default if nil
- base := t.Base
- if base == nil {
- base = http.DefaultTransport
- }
- return base.RoundTrip(req2)
-}
diff --git a/go/apps/billaged/client/types.go b/go/apps/billaged/client/types.go
deleted file mode 100644
index 9d8919e2f2..0000000000
--- a/go/apps/billaged/client/types.go
+++ /dev/null
@@ -1,67 +0,0 @@
-package client
-
-import (
- billingv1 "github.com/unkeyed/unkey/go/gen/proto/billaged/v1"
-)
-
-// AIDEV-NOTE: Type definitions for billaged client requests and responses
-// These provide a clean interface that wraps the protobuf types
-
-// SendMetricsBatchRequest represents a request to send a batch of VM metrics
-type SendMetricsBatchRequest struct {
- VmID string
- CustomerID string
- Metrics []*billingv1.VMMetrics
-}
-
-// SendMetricsBatchResponse represents the response from sending metrics batch
-type SendMetricsBatchResponse struct {
- Success bool
- Message string
-}
-
-// SendHeartbeatRequest represents a request to send a heartbeat
-type SendHeartbeatRequest struct {
- InstanceID string
- ActiveVMs []string
-}
-
-// SendHeartbeatResponse represents the response from sending heartbeat
-type SendHeartbeatResponse struct {
- Success bool
-}
-
-// NotifyVmStartedRequest represents a request to notify that a VM has started
-type NotifyVmStartedRequest struct {
- VmID string
- CustomerID string
- StartTime int64
-}
-
-// NotifyVmStartedResponse represents the response from notifying VM started
-type NotifyVmStartedResponse struct {
- Success bool
-}
-
-// NotifyVmStoppedRequest represents a request to notify that a VM has stopped
-type NotifyVmStoppedRequest struct {
- VmID string
- StopTime int64
-}
-
-// NotifyVmStoppedResponse represents the response from notifying VM stopped
-type NotifyVmStoppedResponse struct {
- Success bool
-}
-
-// NotifyPossibleGapRequest represents a request to notify about a possible gap in metrics
-type NotifyPossibleGapRequest struct {
- VmID string
- LastSent int64
- ResumeTime int64
-}
-
-// NotifyPossibleGapResponse represents the response from notifying possible gap
-type NotifyPossibleGapResponse struct {
- Success bool
-}
diff --git a/go/apps/billaged/cmd/billaged-cli/main.go b/go/apps/billaged/cmd/billaged-cli/main.go
deleted file mode 100644
index 9a72a410c6..0000000000
--- a/go/apps/billaged/cmd/billaged-cli/main.go
+++ /dev/null
@@ -1,281 +0,0 @@
-package main
-
-import (
- "context"
- "encoding/json"
- "flag"
- "fmt"
- "log"
- "os"
- "strconv"
- "time"
-
- "github.com/unkeyed/unkey/go/apps/billaged/client"
- billingv1 "github.com/unkeyed/unkey/go/gen/proto/billaged/v1"
- "google.golang.org/protobuf/types/known/timestamppb"
-)
-
-// AIDEV-NOTE: CLI tool demonstrating billaged client usage with SPIFFE integration
-// This provides a command-line interface for billing operations with proper tenant isolation
-
-func main() {
- var (
- serverAddr = flag.String("server", getEnvOrDefault("UNKEY_BILLAGED_SERVER_ADDRESS", "https://localhost:8081"), "billaged server address")
- userID = flag.String("user", getEnvOrDefault("UNKEY_BILLAGED_USER_ID", "cli-user"), "user ID for authentication")
- tenantID = flag.String("tenant", getEnvOrDefault("UNKEY_BILLAGED_TENANT_ID", "cli-tenant"), "tenant ID for data scoping")
- tlsMode = flag.String("tls-mode", getEnvOrDefault("UNKEY_BILLAGED_TLS_MODE", "spiffe"), "TLS mode: disabled, file, or spiffe")
- spiffeSocket = flag.String("spiffe-socket", getEnvOrDefault("UNKEY_BILLAGED_SPIFFE_SOCKET", "/var/lib/spire/agent/agent.sock"), "SPIFFE agent socket path")
- tlsCert = flag.String("tls-cert", "", "TLS certificate file (for file mode)")
- tlsKey = flag.String("tls-key", "", "TLS key file (for file mode)")
- tlsCA = flag.String("tls-ca", "", "TLS CA file (for file mode)")
- timeout = flag.Duration("timeout", 30*time.Second, "request timeout")
- jsonOutput = flag.Bool("json", false, "output results as JSON")
- )
- flag.Parse()
-
- if flag.NArg() == 0 {
- printUsage()
- os.Exit(1)
- }
-
- ctx := context.Background()
-
- // Create billaged client
- config := client.Config{
- ServerAddress: *serverAddr,
- UserID: *userID,
- TenantID: *tenantID,
- TLSMode: *tlsMode,
- SPIFFESocketPath: *spiffeSocket,
- TLSCertFile: *tlsCert,
- TLSKeyFile: *tlsKey,
- TLSCAFile: *tlsCA,
- Timeout: *timeout,
- }
-
- billingClient, err := client.New(ctx, config)
- if err != nil {
- log.Fatalf("Failed to create billaged client: %v", err)
- }
- defer billingClient.Close()
-
- // Execute command
- command := flag.Arg(0)
- switch command {
- case "send-metrics":
- handleSendMetrics(ctx, billingClient, *jsonOutput)
- case "heartbeat":
- handleHeartbeat(ctx, billingClient, *jsonOutput)
- case "notify-started":
- handleNotifyVmStarted(ctx, billingClient, *jsonOutput)
- case "notify-stopped":
- handleNotifyVmStopped(ctx, billingClient, *jsonOutput)
- case "notify-gap":
- handleNotifyPossibleGap(ctx, billingClient, *jsonOutput)
- default:
- fmt.Fprintf(os.Stderr, "Unknown command: %s\n", command)
- printUsage()
- os.Exit(1)
- }
-}
-
-func printUsage() {
- fmt.Printf(`billaged-cli - CLI tool for billaged operations
-
-Usage: %s [flags] [args...]
-
-Commands:
- send-metrics Send VM metrics batch
- heartbeat Send heartbeat with active VMs
- notify-started Notify that a VM has started
- notify-stopped Notify that a VM has stopped
- notify-gap Notify about a possible gap in metrics
-
-Environment Variables:
- UNKEY_BILLAGED_SERVER_ADDRESS Server address (default: https://localhost:8081)
- UNKEY_BILLAGED_USER_ID User ID for authentication (default: cli-user)
- UNKEY_BILLAGED_TENANT_ID Tenant ID for data scoping (default: cli-tenant)
- UNKEY_BILLAGED_TLS_MODE TLS mode (default: spiffe)
- UNKEY_BILLAGED_SPIFFE_SOCKET SPIFFE socket path (default: /var/lib/spire/agent/agent.sock)
-
-Examples:
- # Send heartbeat with SPIFFE authentication
- %s -user=prod-user-123 -tenant=prod-tenant-456 heartbeat
-
- # Notify VM started
- %s notify-started vm-12345
-
- # Send metrics batch
- %s send-metrics
-
- # Get response with JSON output
- %s heartbeat -json
-
-`, os.Args[0], os.Args[0], os.Args[0], os.Args[0], os.Args[0])
-}
-
-func handleSendMetrics(ctx context.Context, billingClient *client.Client, jsonOutput bool) {
- // Example metrics batch - in real usage, this would be provided via CLI args or file
- metrics := []*billingv1.VMMetrics{
- {
- Timestamp: timestamppb.Now(),
- CpuTimeNanos: 1000000000, // 1 second of CPU time
- MemoryUsageBytes: 512 * 1024 * 1024, // 512MB
- DiskReadBytes: 1024 * 1024, // 1MB
- DiskWriteBytes: 512 * 1024, // 512KB
- NetworkRxBytes: 2048, // 2KB
- NetworkTxBytes: 1024, // 1KB
- },
- }
-
- req := &client.SendMetricsBatchRequest{
- VmID: "example-vm-123",
- CustomerID: billingClient.GetTenantID(),
- Metrics: metrics,
- }
-
- resp, err := billingClient.SendMetricsBatch(ctx, req)
- if err != nil {
- log.Fatalf("Failed to send metrics: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- fmt.Printf("Metrics sent:\n")
- fmt.Printf(" Success: %v\n", resp.Success)
- fmt.Printf(" Message: %s\n", resp.Message)
- fmt.Printf(" Metrics count: %d\n", len(metrics))
- }
-}
-
-func handleHeartbeat(ctx context.Context, billingClient *client.Client, jsonOutput bool) {
- // Example heartbeat - in real usage, this would get actual active VMs
- req := &client.SendHeartbeatRequest{
- InstanceID: "metald-instance-1",
- ActiveVMs: []string{"vm-123", "vm-456", "vm-789"},
- }
-
- resp, err := billingClient.SendHeartbeat(ctx, req)
- if err != nil {
- log.Fatalf("Failed to send heartbeat: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- fmt.Printf("Heartbeat sent:\n")
- fmt.Printf(" Success: %v\n", resp.Success)
- fmt.Printf(" Active VMs: %d\n", len(req.ActiveVMs))
- }
-}
-
-func handleNotifyVmStarted(ctx context.Context, billingClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("VM ID is required for notify-started command")
- }
- vmID := flag.Arg(1)
-
- req := &client.NotifyVmStartedRequest{
- VmID: vmID,
- CustomerID: billingClient.GetTenantID(),
- StartTime: time.Now().Unix(),
- }
-
- resp, err := billingClient.NotifyVmStarted(ctx, req)
- if err != nil {
- log.Fatalf("Failed to notify VM started: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- fmt.Printf("VM started notification:\n")
- fmt.Printf(" VM ID: %s\n", vmID)
- fmt.Printf(" Success: %v\n", resp.Success)
- fmt.Printf(" Start time: %d\n", req.StartTime)
- }
-}
-
-func handleNotifyVmStopped(ctx context.Context, billingClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("VM ID is required for notify-stopped command")
- }
- vmID := flag.Arg(1)
-
- req := &client.NotifyVmStoppedRequest{
- VmID: vmID,
- StopTime: time.Now().Unix(),
- }
-
- resp, err := billingClient.NotifyVmStopped(ctx, req)
- if err != nil {
- log.Fatalf("Failed to notify VM stopped: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- fmt.Printf("VM stopped notification:\n")
- fmt.Printf(" VM ID: %s\n", vmID)
- fmt.Printf(" Success: %v\n", resp.Success)
- fmt.Printf(" Stop time: %d\n", req.StopTime)
- }
-}
-
-func handleNotifyPossibleGap(ctx context.Context, billingClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("VM ID is required for notify-gap command")
- }
- vmID := flag.Arg(1)
-
- // Default to a 5-minute gap ending now
- gapDuration := 5 * time.Minute
- resumeTime := time.Now()
- lastSent := resumeTime.Add(-gapDuration)
-
- // Allow custom gap duration from CLI args
- if flag.NArg() > 2 {
- if minutes, err := strconv.Atoi(flag.Arg(2)); err == nil {
- gapDuration = time.Duration(minutes) * time.Minute
- lastSent = resumeTime.Add(-gapDuration)
- }
- }
-
- req := &client.NotifyPossibleGapRequest{
- VmID: vmID,
- LastSent: lastSent.Unix(),
- ResumeTime: resumeTime.Unix(),
- }
-
- resp, err := billingClient.NotifyPossibleGap(ctx, req)
- if err != nil {
- log.Fatalf("Failed to notify possible gap: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- fmt.Printf("Possible gap notification:\n")
- fmt.Printf(" VM ID: %s\n", vmID)
- fmt.Printf(" Success: %v\n", resp.Success)
- fmt.Printf(" Gap duration: %v\n", gapDuration)
- fmt.Printf(" Last sent: %s\n", lastSent.Format(time.RFC3339))
- fmt.Printf(" Resume time: %s\n", resumeTime.Format(time.RFC3339))
- }
-}
-
-func outputJSON(data interface{}) {
- encoder := json.NewEncoder(os.Stdout)
- encoder.SetIndent("", " ")
- if err := encoder.Encode(data); err != nil {
- log.Fatalf("Failed to encode JSON: %v", err)
- }
-}
-
-func getEnvOrDefault(key, defaultValue string) string {
- if value := os.Getenv(key); value != "" {
- return value
- }
- return defaultValue
-}
diff --git a/go/apps/billaged/cmd/billaged/main.go b/go/apps/billaged/cmd/billaged/main.go
deleted file mode 100644
index 67e64db83c..0000000000
--- a/go/apps/billaged/cmd/billaged/main.go
+++ /dev/null
@@ -1,479 +0,0 @@
-package main
-
-import (
- "context"
- "flag"
- "fmt"
- "log/slog"
- "net/http"
- "os"
- "os/signal"
- "runtime"
- "runtime/debug"
- "syscall"
- "time"
-
- "connectrpc.com/connect"
- "github.com/prometheus/client_golang/prometheus/promhttp"
- "github.com/unkeyed/unkey/go/apps/billaged/internal/aggregator"
- "github.com/unkeyed/unkey/go/apps/billaged/internal/config"
- "github.com/unkeyed/unkey/go/apps/billaged/internal/observability"
- "github.com/unkeyed/unkey/go/apps/billaged/internal/service"
- healthpkg "github.com/unkeyed/unkey/go/deploy/pkg/health"
- "github.com/unkeyed/unkey/go/deploy/pkg/observability/interceptors"
- tlspkg "github.com/unkeyed/unkey/go/deploy/pkg/tls"
- "github.com/unkeyed/unkey/go/gen/proto/billaged/v1/billagedv1connect"
- "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
- "go.opentelemetry.io/otel"
- "golang.org/x/net/http2"
- "golang.org/x/net/http2/h2c"
-)
-
-// version is set at build time via ldflags
-var version = ""
-
-// AIDEV-NOTE: Enhanced version management with debug.ReadBuildInfo fallback
-// Handles production builds (ldflags), development builds (git commit), and module builds
-// getVersion returns the version string, with fallback to debug.ReadBuildInfo
-func getVersion() string {
- // If version was set via ldflags (production builds), use it
- if version != "" {
- return version
- }
-
- // Fallback to debug.ReadBuildInfo for development/module builds
- if info, ok := debug.ReadBuildInfo(); ok {
- // Use the module version if available
- if info.Main.Version != "(devel)" && info.Main.Version != "" {
- return info.Main.Version
- }
-
- // Try to get version from VCS info
- for _, setting := range info.Settings {
- if setting.Key == "vcs.revision" && len(setting.Value) >= 7 {
- return "dev-" + setting.Value[:7] // First 7 chars of commit hash
- }
- }
-
- // Last resort: indicate it's a development build
- return "dev"
- }
-
- // Final fallback
- return version
-}
-
-func main() {
- // Track application start time for uptime calculations
- startTime := time.Now()
-
- // Parse command-line flags with environment variable fallbacks
- var (
- showHelp = flag.Bool("help", false, "Show help information")
- showVersion = flag.Bool("version", false, "Show version information")
- )
- flag.Parse()
-
- // Handle help and version flags
- if *showHelp {
- printUsage()
- os.Exit(0)
- }
-
- if *showVersion {
- printVersion()
- os.Exit(0)
- }
-
- // Initialize structured logger
- logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ //nolint:exhaustruct // AddSource and ReplaceAttr use appropriate default values
- Level: slog.LevelInfo,
- }))
- slog.SetDefault(logger)
-
- // Load configuration
- cfg, err := config.LoadConfigWithLogger(logger)
- if err != nil {
- logger.Error("failed to load configuration",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
-
- // Parse aggregation interval from config
- aggregationInterval, err := time.ParseDuration(cfg.Aggregation.Interval)
- if err != nil {
- logger.Error("invalid aggregation interval",
- slog.String("interval", cfg.Aggregation.Interval),
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
-
- // Log startup
- logger.Info("starting billaged service",
- slog.String("version", getVersion()),
- slog.String("go_version", runtime.Version()),
- slog.String("port", cfg.Server.Port),
- slog.String("address", cfg.Server.Address),
- slog.String("aggregation_interval", aggregationInterval.String()),
- slog.Bool("otel_enabled", cfg.OpenTelemetry.Enabled),
- )
-
- // Initialize TLS provider (defaults to disabled)
- ctx := context.Background()
- tlsConfig := tlspkg.Config{ //nolint:exhaustruct // Optional TLS fields use appropriate default values
- Mode: tlspkg.Mode(cfg.TLS.Mode),
- CertFile: cfg.TLS.CertFile,
- KeyFile: cfg.TLS.KeyFile,
- CAFile: cfg.TLS.CAFile,
- SPIFFESocketPath: cfg.TLS.SPIFFESocketPath,
- }
- tlsProvider, err := tlspkg.NewProvider(ctx, tlsConfig)
- if err != nil {
- // AIDEV-NOTE: TLS/SPIFFE is now required - no fallback to disabled mode
- logger.Error("TLS initialization failed",
- "error", err,
- "mode", cfg.TLS.Mode)
- os.Exit(1)
- }
- defer tlsProvider.Close()
-
- logger.Info("TLS provider initialized",
- "mode", cfg.TLS.Mode,
- "spiffe_enabled", cfg.TLS.Mode == "spiffe")
-
- // Initialize OpenTelemetry
- otelProviders, err := observability.InitProviders(ctx, cfg, getVersion())
- if err != nil {
- logger.Error("failed to initialize OpenTelemetry",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
- defer func() {
- shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
- if err := otelProviders.Shutdown(shutdownCtx); err != nil {
- logger.Error("failed to shutdown OpenTelemetry",
- slog.String("error", err.Error()),
- )
- }
- }()
-
- if cfg.OpenTelemetry.Enabled {
- logger.Info("OpenTelemetry initialized",
- slog.String("service_name", cfg.OpenTelemetry.ServiceName),
- slog.String("service_version", cfg.OpenTelemetry.ServiceVersion),
- slog.Float64("sampling_rate", cfg.OpenTelemetry.TracingSamplingRate),
- slog.String("otlp_endpoint", cfg.OpenTelemetry.OTLPEndpoint),
- slog.Bool("prometheus_enabled", cfg.OpenTelemetry.PrometheusEnabled),
- slog.Bool("high_cardinality_enabled", cfg.OpenTelemetry.HighCardinalityLabelsEnabled),
- )
- }
-
- // Initialize billing metrics if OpenTelemetry is enabled
- var billingMetrics *observability.BillingMetrics
- if cfg.OpenTelemetry.Enabled {
- var err error
- billingMetrics, err = observability.NewBillingMetrics(logger, cfg.OpenTelemetry.HighCardinalityLabelsEnabled)
- if err != nil {
- logger.Error("failed to initialize billing metrics",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
- logger.Info("billing metrics initialized",
- slog.Bool("high_cardinality_enabled", cfg.OpenTelemetry.HighCardinalityLabelsEnabled),
- )
- }
-
- // Create aggregator with usage summary callback
- agg := aggregator.NewAggregator(logger, aggregationInterval)
-
- // Set up usage summary callback to print results
- agg.SetUsageSummaryCallback(func(summary *aggregator.UsageSummary) {
- printUsageSummary(logger, summary)
- })
-
- // Create billing service
- billingService := service.NewBillingService(logger, agg, billingMetrics)
-
- // Configure shared interceptor options
- interceptorOpts := []interceptors.Option{
- interceptors.WithServiceName("billaged"),
- interceptors.WithLogger(logger),
- interceptors.WithActiveRequestsMetric(true),
- interceptors.WithRequestDurationMetric(false), // Match existing behavior
- interceptors.WithErrorResampling(true),
- interceptors.WithPanicStackTrace(true),
- }
-
- // Add meter if OpenTelemetry is enabled
- if cfg.OpenTelemetry.Enabled {
- interceptorOpts = append(interceptorOpts, interceptors.WithMeter(otel.Meter("billaged")))
- }
-
- // Get default interceptors (tenant auth, metrics, logging)
- sharedInterceptors := interceptors.NewDefaultInterceptors("billaged", interceptorOpts...)
-
- // Convert UnaryInterceptorFunc to Interceptor
- var interceptorList []connect.Interceptor
- for _, interceptor := range sharedInterceptors {
- interceptorList = append(interceptorList, connect.Interceptor(interceptor))
- }
-
- mux := http.NewServeMux()
- path, handler := billagedv1connect.NewBillingServiceHandler(billingService,
- connect.WithInterceptors(interceptorList...),
- )
- mux.Handle(path, handler)
-
- // Add stats endpoint
- mux.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) {
- activeVMs := agg.GetActiveVMCount()
-
- response := fmt.Sprintf(`{
- "active_vms": %d,
- "aggregation_interval": "%s"
- }`, activeVMs, aggregationInterval.String())
-
- w.Header().Set("Content-Type", "application/json")
- w.WriteHeader(http.StatusOK)
- _, _ = w.Write([]byte(response))
- })
-
- // Add Prometheus metrics endpoint if enabled
- if cfg.OpenTelemetry.Enabled && cfg.OpenTelemetry.PrometheusEnabled {
- mux.Handle("/metrics", otelProviders.PrometheusHTTP)
- logger.Info("Prometheus metrics endpoint enabled",
- slog.String("path", "/metrics"),
- )
- }
-
- // Create HTTP server with H2C support
- serverAddr := fmt.Sprintf("%s:%s", cfg.Server.Address, cfg.Server.Port)
-
- // Wrap handler with OTEL HTTP middleware if enabled
- var httpHandler http.Handler = mux
- if cfg.OpenTelemetry.Enabled {
- httpHandler = otelhttp.NewHandler(mux, "http",
- otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string {
- return fmt.Sprintf("%s %s", r.Method, r.URL.Path)
- }),
- )
- }
-
- // Configure server with optional TLS and security timeouts
- server := &http.Server{
- Addr: serverAddr,
- Handler: h2c.NewHandler(httpHandler, &http2.Server{}), //nolint:exhaustruct // Using default HTTP/2 server configuration
- // AIDEV-NOTE: Security timeouts to prevent slowloris attacks
- ReadTimeout: 30 * time.Second, // Time to read request headers
- WriteTimeout: 30 * time.Second, // Time to write response
- IdleTimeout: 120 * time.Second, // Keep-alive timeout
- MaxHeaderBytes: 1 << 20, // 1MB max header size
- }
-
- // Apply TLS configuration if enabled
- serverTLSConfig, _ := tlsProvider.ServerTLSConfig()
- if serverTLSConfig != nil {
- server.TLSConfig = serverTLSConfig
- // For TLS, we need to use regular handler, not h2c
- server.Handler = httpHandler
- }
-
- // Start periodic aggregation
- aggCtx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- go agg.StartPeriodicAggregation(aggCtx)
-
- // Start Prometheus server on separate port if enabled
- var promServer *http.Server
- if cfg.OpenTelemetry.Enabled && cfg.OpenTelemetry.PrometheusEnabled {
- // AIDEV-NOTE: Use configured interface, defaulting to localhost for security
- promAddr := fmt.Sprintf("%s:%s", cfg.OpenTelemetry.PrometheusInterface, cfg.OpenTelemetry.PrometheusPort)
- promMux := http.NewServeMux()
- promMux.Handle("/metrics", promhttp.Handler())
- promMux.HandleFunc("/health", healthpkg.Handler("billaged", getVersion(), startTime))
-
- promServer = &http.Server{
- Addr: promAddr,
- Handler: promMux,
- ReadTimeout: 30 * time.Second,
- WriteTimeout: 30 * time.Second,
- IdleTimeout: 120 * time.Second,
- }
-
- go func() {
- localhostOnly := cfg.OpenTelemetry.PrometheusInterface == "127.0.0.1" || cfg.OpenTelemetry.PrometheusInterface == "localhost"
- logger.Info("starting prometheus metrics server",
- slog.String("address", promAddr),
- slog.Bool("localhost_only", localhostOnly),
- )
- if err := promServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
- logger.Error("prometheus server failed",
- slog.String("error", err.Error()),
- )
- }
- }()
- }
-
- // Start main server in goroutine
- go func() {
- if serverTLSConfig != nil {
- logger.Info("starting HTTPS server with TLS",
- slog.String("address", serverAddr),
- slog.String("tls_mode", cfg.TLS.Mode),
- )
- // Empty strings for cert/key paths - SPIFFE provides them in memory
- if err := server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
- logger.Error("server failed",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
- } else {
- logger.Info("starting HTTP server without TLS",
- slog.String("address", serverAddr),
- )
- if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
- logger.Error("server failed",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
- }
- }()
-
- // Wait for interrupt signal
- sigChan := make(chan os.Signal, 1)
- signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
- <-sigChan
-
- logger.Info("shutting down billaged service")
-
- // Graceful shutdown
- shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer shutdownCancel()
-
- cancel() // Stop aggregation
-
- // Shutdown all servers
- var shutdownErrors []error
-
- if err := server.Shutdown(shutdownCtx); err != nil {
- shutdownErrors = append(shutdownErrors, fmt.Errorf("main server: %w", err))
- }
-
- if promServer != nil {
- if err := promServer.Shutdown(shutdownCtx); err != nil {
- shutdownErrors = append(shutdownErrors, fmt.Errorf("prometheus server: %w", err))
- }
- }
-
- if len(shutdownErrors) > 0 {
- logger.Error("failed to shutdown servers gracefully",
- slog.String("error", fmt.Sprintf("%v", shutdownErrors)),
- )
- os.Exit(1)
- }
-
- logger.Info("billaged service shutdown complete")
-}
-
-// printUsageSummary prints the aggregated usage summary every 60 seconds
-func printUsageSummary(logger *slog.Logger, summary *aggregator.UsageSummary) {
- logger.Info("=== BILLAGED USAGE SUMMARY ===",
- "vm_id", summary.VMID,
- "customer_id", summary.CustomerID,
- "period", summary.Period.String(),
- "start_time", summary.StartTime.Format("15:04:05"),
- "end_time", summary.EndTime.Format("15:04:05"),
- )
-
- logger.Info("CPU USAGE",
- "vm_id", summary.VMID,
- "cpu_time_used_ms", summary.CPUTimeUsedMs,
- "cpu_time_used_seconds", float64(summary.CPUTimeUsedMs)/1000.0,
- )
-
- logger.Info("MEMORY USAGE",
- "vm_id", summary.VMID,
- "avg_memory_usage_mb", summary.AvgMemoryUsageBytes/(1024*1024),
- "max_memory_usage_mb", summary.MaxMemoryUsageBytes/(1024*1024),
- )
-
- logger.Info("DISK I/O",
- "vm_id", summary.VMID,
- "disk_read_mb", summary.DiskReadBytes/(1024*1024),
- "disk_write_mb", summary.DiskWriteBytes/(1024*1024),
- "total_disk_io_mb", summary.TotalDiskIO/(1024*1024),
- )
-
- logger.Info("NETWORK I/O",
- "vm_id", summary.VMID,
- "network_rx_mb", summary.NetworkRxBytes/(1024*1024),
- "network_tx_mb", summary.NetworkTxBytes/(1024*1024),
- "total_network_io_mb", summary.TotalNetworkIO/(1024*1024),
- )
-
- logger.Info("BILLING METRICS",
- "vm_id", summary.VMID,
- "resource_score", fmt.Sprintf("%.2f", summary.ResourceScore),
- "sample_count", summary.SampleCount,
- )
-
- logger.Info("=== END USAGE SUMMARY ===",
- "vm_id", summary.VMID,
- )
-}
-
-// printVersion displays version information
-func printVersion() {
- fmt.Printf("Billaged - VM Usage Billing Aggregation Service\n")
- fmt.Printf("Version: %s\n", getVersion())
- fmt.Printf("Built with: %s\n", runtime.Version())
-}
-
-// printUsage displays help information
-func printUsage() {
- fmt.Printf("Billaged - VM Usage Billing Aggregation Service\n\n")
- fmt.Printf("Usage: %s [OPTIONS]\n\n", os.Args[0])
- fmt.Printf("Options:\n")
- flag.PrintDefaults()
- fmt.Printf("\nEnvironment Variables:\n")
- fmt.Printf(" UNKEY_BILLAGED_PORT Server port (default: 8081)\n")
- fmt.Printf(" UNKEY_BILLAGED_ADDRESS Bind address (default: 0.0.0.0)\n")
- fmt.Printf(" UNKEY_BILLAGED_AGGREGATION_INTERVAL Aggregation interval (default: 60s)\n")
- fmt.Printf("\nOpenTelemetry Configuration:\n")
- fmt.Printf(" UNKEY_BILLAGED_OTEL_ENABLED Enable OpenTelemetry (default: false)\n")
- fmt.Printf(" UNKEY_BILLAGED_OTEL_SERVICE_NAME Service name (default: billaged)\n")
- fmt.Printf(" UNKEY_BILLAGED_OTEL_SERVICE_VERSION Service version (default: 0.0.1)\n")
- fmt.Printf(" UNKEY_BILLAGED_OTEL_SAMPLING_RATE Trace sampling rate 0.0-1.0 (default: 1.0)\n")
- fmt.Printf(" UNKEY_BILLAGED_OTEL_ENDPOINT OTLP endpoint (default: localhost:4318)\n")
- fmt.Printf(" UNKEY_BILLAGED_OTEL_PROMETHEUS_ENABLED Enable Prometheus metrics (default: true)\n")
- fmt.Printf(" UNKEY_BILLAGED_OTEL_PROMETHEUS_PORT Prometheus metrics port (default: 9465)\n")
- fmt.Printf(" UNKEY_BILLAGED_OTEL_PROMETHEUS_INTERFACE Prometheus binding interface (default: 127.0.0.1)\n")
- fmt.Printf(" UNKEY_BILLAGED_OTEL_HIGH_CARDINALITY_ENABLED Enable high-cardinality labels (default: false)\n")
- fmt.Printf("\nTLS Configuration:\n")
- fmt.Printf(" UNKEY_BILLAGED_TLS_MODE TLS mode: disabled, file, spiffe (default: disabled)\n")
- fmt.Printf(" UNKEY_BILLAGED_TLS_CERT_FILE Path to certificate file (file mode)\n")
- fmt.Printf(" UNKEY_BILLAGED_TLS_KEY_FILE Path to private key file (file mode)\n")
- fmt.Printf(" UNKEY_BILLAGED_TLS_CA_FILE Path to CA bundle file (file mode)\n")
- fmt.Printf(" UNKEY_BILLAGED_SPIFFE_SOCKET SPIFFE workload API socket (default: /run/spire/sockets/agent.sock)\n")
- fmt.Printf("\nDescription:\n")
- fmt.Printf(" Billaged receives VM usage metrics from metald instances and aggregates\n")
- fmt.Printf(" them for billing purposes. It calculates usage summaries every 60 seconds\n")
- fmt.Printf(" (configurable) and can track multiple VMs across multiple customers.\n\n")
- fmt.Printf("Endpoints:\n")
- fmt.Printf(" /billing.v1.BillingService/* - ConnectRPC billing service\n")
- fmt.Printf(" /health - Health check endpoint\n")
- fmt.Printf(" /stats - Current statistics\n")
- fmt.Printf(" /metrics - Prometheus metrics (if enabled)\n\n")
- fmt.Printf("Examples:\n")
- fmt.Printf(" %s # Default settings (port 8081)\n", os.Args[0])
- fmt.Printf(" UNKEY_BILLAGED_OTEL_ENABLED=true %s # Enable telemetry\n", os.Args[0])
- fmt.Printf(" UNKEY_BILLAGED_AGGREGATION_INTERVAL=30s %s # 30-second summaries\n", os.Args[0])
-}
diff --git a/go/apps/billaged/contrib/grafana-dashboards/README.md b/go/apps/billaged/contrib/grafana-dashboards/README.md
deleted file mode 100644
index cb025c3840..0000000000
--- a/go/apps/billaged/contrib/grafana-dashboards/README.md
+++ /dev/null
@@ -1,79 +0,0 @@
-# Billaged Grafana Dashboards
-
-This directory contains Grafana dashboards for monitoring the billaged service.
-
-## Available Dashboards
-
-### billaged-overview.json
-A comprehensive overview dashboard for the billaged billing aggregation service that provides:
-
-#### Key Metrics
-- **Usage Records Rate**: Rate of usage records being processed per second
-- **Avg Aggregation Duration**: Average time spent aggregating usage metrics
-- **Active VMs**: Current number of VMs being tracked for billing
-- **Error Rate**: Rate of billing processing errors
-
-#### Detailed Views
-- **Usage Processing**: Time-series charts showing usage record processing rates and aggregation duration percentiles
-- **Active VMs & Errors**: VM count tracking and error breakdown by type
-- **System Health**: CPU usage, memory usage, and goroutine counts
-
-## Importing Dashboards
-
-1. **Manual Import**:
- ```bash
- # Open Grafana UI
- # Go to Dashboards -> Import
- # Upload the JSON file or paste its contents
- ```
-
-2. **Automated Import** (if you have Grafana API access):
- ```bash
- # Using curl to import via API
- curl -X POST \
- http://your-grafana-instance:3000/api/dashboards/db \
- -H 'Content-Type: application/json' \
- -H 'Authorization: Bearer YOUR_API_KEY' \
- -d @billaged-overview.json
- ```
-
-## Metrics Used
-
-The dashboards expect the following Prometheus metrics from billaged:
-
-### Core Billing Metrics
-- `billaged_usage_records_processed_total` - Counter of processed usage records
-- `billaged_aggregation_duration_seconds` - Histogram of aggregation durations
-- `billaged_active_vms` - Gauge of active VMs being tracked
-- `billaged_billing_errors_total` - Counter of billing errors by type
-
-### System Health Metrics (from Prometheus Go client)
-- `process_cpu_seconds_total` - Process CPU usage
-- `process_resident_memory_bytes` - Process memory usage
-- `go_goroutines` - Number of active goroutines
-
-## Configuration
-
-### High-Cardinality Labels
-The dashboards are designed to work with both high and low cardinality configurations:
-
-- **High-cardinality enabled**: Shows per-VM breakdowns when `BILLAGED_OTEL_HIGH_CARDINALITY_ENABLED=true`
-- **High-cardinality disabled**: Shows aggregate metrics only when `BILLAGED_OTEL_HIGH_CARDINALITY_ENABLED=false` (default)
-
-### Rate Intervals
-All rate calculations use `$__rate_interval` for optimal performance across different time ranges.
-
-## Troubleshooting
-
-1. **No Data Showing**:
- - Verify billaged is running with `BILLAGED_OTEL_ENABLED=true`
- - Check Prometheus is scraping billaged metrics endpoint
- - Confirm the job name in Prometheus matches "billaged"
-
-2. **Missing Metrics**:
- - Ensure billaged is processing usage records (metrics only appear after first usage)
- - Check billaged logs for initialization errors
-
-3. **Performance Issues**:
- - Consider disabling high-cardinality labels in production
- - Adjust dashboard refresh rates for heavy load scenarios
\ No newline at end of file
diff --git a/go/apps/billaged/contrib/systemd/README.md b/go/apps/billaged/contrib/systemd/README.md
deleted file mode 100644
index a6ce25a504..0000000000
--- a/go/apps/billaged/contrib/systemd/README.md
+++ /dev/null
@@ -1,99 +0,0 @@
-# Systemd Integration for Billaged
-
-This directory contains systemd service files and deployment scripts for billaged.
-
-## Files
-
-- `billaged.service` - Production-ready systemd service unit
-- `billaged.env.example` - Example environment configuration file
-
-## Quick Installation
-
-```bash
-# From the billaged root directory
-make service-install
-```
-
-## Manual Installation
-
-```bash
-# Copy service file
-sudo cp contrib/systemd/billaged.service /etc/systemd/system/
-
-# Copy environment file (optional)
-sudo mkdir -p /etc/billaged
-sudo cp contrib/systemd/billaged.env.example /etc/billaged/billaged.env
-
-# Edit configuration as needed
-sudo vim /etc/billaged/billaged.env
-
-# Install and start service
-sudo systemctl daemon-reload
-sudo systemctl enable billaged
-sudo systemctl start billaged
-```
-
-## Service Management
-
-```bash
-# Check status
-sudo systemctl status billaged
-
-# View logs
-sudo journalctl -u billaged -f
-
-# Restart service
-sudo systemctl restart billaged
-
-# Stop service
-sudo systemctl stop billaged
-```
-
-## Configuration
-
-The service supports configuration via:
-
-1. Environment variables in the service file
-2. Command-line arguments (modify `ExecStart` in service file)
-3. Optional environment file at `/etc/billaged/billaged.env`
-
-### Key Configuration Options
-
-- `BILLAGED_PORT` - Service port (default: 8081)
-- `BILLAGED_ADDRESS` - Bind address (default: 0.0.0.0)
-- `BILLAGED_AGGREGATION_INTERVAL` - How often to print usage summaries (default: 60s)
-
-## Integration with Metald
-
-Billaged is designed to receive VM usage data from metald instances. To enable the integration:
-
-1. Configure metald to use ConnectRPC billing client
-2. Point metald to billaged endpoint (http://localhost:8081)
-3. Billaged will automatically aggregate and print usage summaries
-
-## Endpoints
-
-- `/billing.v1.BillingService/*` - ConnectRPC billing service endpoints
-- `/health` - Health check endpoint
-- `/stats` - Current statistics and active VM count
-
-## Security
-
-The service runs as the `billaged` system user with minimal privileges. The installation process automatically:
-
-- Creates the `billaged` system user
-- Sets up secure directories with proper ownership
-- Configures resource limits
-
-## Troubleshooting
-
-```bash
-# Check service validation
-sudo systemd-analyze verify /etc/systemd/system/billaged.service
-
-# Debug service startup
-sudo systemctl show billaged
-
-# Check logs for errors
-sudo journalctl -u billaged --no-pager
-```
\ No newline at end of file
diff --git a/go/apps/billaged/contrib/systemd/billaged.env.example b/go/apps/billaged/contrib/systemd/billaged.env.example
deleted file mode 100644
index f040224130..0000000000
--- a/go/apps/billaged/contrib/systemd/billaged.env.example
+++ /dev/null
@@ -1,36 +0,0 @@
-# Billaged Environment Configuration
-# Copy this file to /etc/billaged/billaged.env and customize as needed
-
-# Server Configuration
-BILLAGED_PORT=8081
-BILLAGED_ADDRESS=0.0.0.0
-
-# Aggregation Configuration
-BILLAGED_AGGREGATION_INTERVAL=60s
-
-# OpenTelemetry Configuration
-# Enable telemetry for monitoring and observability
-BILLAGED_OTEL_ENABLED=false
-BILLAGED_OTEL_SERVICE_NAME=billaged
-BILLAGED_OTEL_SERVICE_VERSION=0.0.1
-BILLAGED_OTEL_SAMPLING_RATE=1.0
-BILLAGED_OTEL_ENDPOINT=http://localhost:4318
-BILLAGED_OTEL_PROMETHEUS_ENABLED=true
-BILLAGED_OTEL_PROMETHEUS_PORT=9465
-
-# Development Configuration
-# Uncomment for development/testing
-# BILLAGED_AGGREGATION_INTERVAL=10s
-# BILLAGED_OTEL_ENABLED=true
-
-# Production Configuration Examples
-# For high-frequency billing updates:
-# BILLAGED_AGGREGATION_INTERVAL=30s
-
-# For standard billing cycles:
-# BILLAGED_AGGREGATION_INTERVAL=300s # 5 minutes
-
-# Production Telemetry (with OTEL collector endpoint)
-# BILLAGED_OTEL_ENABLED=true
-# BILLAGED_OTEL_ENDPOINT=http://otel-collector:4318
-# BILLAGED_OTEL_SAMPLING_RATE=0.1
\ No newline at end of file
diff --git a/go/apps/billaged/contrib/systemd/billaged.service b/go/apps/billaged/contrib/systemd/billaged.service
deleted file mode 100644
index ef9d83e3f1..0000000000
--- a/go/apps/billaged/contrib/systemd/billaged.service
+++ /dev/null
@@ -1,50 +0,0 @@
-[Unit]
-Description=Billaged VM Usage Billing Service
-Documentation=https://github.com/unkeyed/unkey/go/apps/billaged
-After=network.target spire-agent.service assetmanagerd.service metald.service
-Wants=network.target
-Requires=spire-agent.service assetmanagerd.service metald.service
-
-[Service]
-Type=simple
-User=billaged
-Group=billaged
-# AIDEV-NOTE: WorkingDirectory removed - not needed for billaged
-# Create required directories (+ prefix runs as root)
-ExecStartPre=+/usr/bin/mkdir -p /opt/billaged
-ExecStartPre=+/usr/bin/mkdir -p /var/log/billaged
-# Set ownership for service directories
-ExecStartPre=+/usr/bin/chown -R billaged:billaged /opt/billaged
-ExecStartPre=+/usr/bin/chown -R billaged:billaged /var/log/billaged
-ExecStart=/usr/local/bin/billaged
-Restart=always
-RestartSec=5
-StandardOutput=journal
-StandardError=journal
-SyslogIdentifier=billaged
-
-# Environment variables
-Environment=UNKEY_BILLAGED_PORT=8081
-Environment=UNKEY_BILLAGED_ADDRESS=0.0.0.0
-Environment=UNKEY_BILLAGED_AGGREGATION_INTERVAL=60s
-
-# OpenTelemetry Configuration (enabled for production)
-Environment=UNKEY_BILLAGED_OTEL_ENABLED=true
-Environment=UNKEY_BILLAGED_OTEL_SERVICE_NAME=billaged
-Environment=UNKEY_BILLAGED_OTEL_SERVICE_VERSION=0.0.1
-Environment=UNKEY_BILLAGED_OTEL_SAMPLING_RATE=1.0
-Environment=UNKEY_BILLAGED_OTEL_ENDPOINT=localhost:4318
-Environment=UNKEY_BILLAGED_OTEL_PROMETHEUS_ENABLED=true
-Environment=UNKEY_BILLAGED_OTEL_PROMETHEUS_PORT=9465
-
-# TLS/SPIFFE configuration (REQUIRED)
-# AIDEV-BUSINESS_RULE: mTLS is required for secure inter-service communication
-Environment=UNKEY_BILLAGED_TLS_MODE=spiffe
-Environment=UNKEY_BILLAGED_SPIFFE_SOCKET=/var/lib/spire/agent/agent.sock
-
-# Resource limits
-LimitNOFILE=65536
-LimitNPROC=4096
-
-[Install]
-WantedBy=multi-user.target
diff --git a/go/apps/billaged/environment.example b/go/apps/billaged/environment.example
deleted file mode 100644
index af17b96bd1..0000000000
--- a/go/apps/billaged/environment.example
+++ /dev/null
@@ -1,31 +0,0 @@
-# Billaged Environment Variables Template
-# NOTE: This service does NOT load .env files automatically
-# Set these variables in your system environment or process manager
-#
-# Usage examples:
-# systemd: EnvironmentFile=/etc/billaged/environment
-# Docker: docker run --env-file environment billaged
-# Shell: set -a; source environment; set +a; ./billaged
-
-# Service Configuration
-UNKEY_BILLAGED_PORT=8081
-UNKEY_BILLAGED_ADDRESS=0.0.0.0
-UNKEY_BILLAGED_AGGREGATION_INTERVAL=60s
-
-# TLS Configuration
-UNKEY_BILLAGED_TLS_MODE=spiffe
-UNKEY_BILLAGED_SPIFFE_SOCKET=/var/lib/spire/agent/agent.sock
-UNKEY_BILLAGED_TLS_CERT_FILE=
-UNKEY_BILLAGED_TLS_KEY_FILE=
-UNKEY_BILLAGED_TLS_CA_FILE=
-
-# OpenTelemetry Configuration
-UNKEY_BILLAGED_OTEL_ENABLED=false
-UNKEY_BILLAGED_OTEL_SERVICE_NAME=billaged
-UNKEY_BILLAGED_OTEL_SERVICE_VERSION=0.1.0
-UNKEY_BILLAGED_OTEL_SAMPLING_RATE=1.0
-UNKEY_BILLAGED_OTEL_ENDPOINT=localhost:4318
-UNKEY_BILLAGED_OTEL_PROMETHEUS_ENABLED=true
-UNKEY_BILLAGED_OTEL_PROMETHEUS_PORT=9465
-UNKEY_BILLAGED_OTEL_PROMETHEUS_INTERFACE=127.0.0.1
-UNKEY_BILLAGED_OTEL_HIGH_CARDINALITY_ENABLED=false
\ No newline at end of file
diff --git a/go/apps/billaged/internal/aggregator/aggregator.go b/go/apps/billaged/internal/aggregator/aggregator.go
deleted file mode 100644
index 52aeeb90ce..0000000000
--- a/go/apps/billaged/internal/aggregator/aggregator.go
+++ /dev/null
@@ -1,365 +0,0 @@
-package aggregator
-
-import (
- "context"
- "log/slog"
- "sync"
- "time"
-
- billingv1 "github.com/unkeyed/unkey/go/gen/proto/billaged/v1"
-)
-
-// VMUsageData tracks usage for a single VM
-type VMUsageData struct {
- VMID string
- CustomerID string
- StartTime time.Time
- LastUpdate time.Time
- TotalCPUNanos int64
- TotalMemoryBytes int64
- TotalDiskReadBytes int64
- TotalDiskWriteBytes int64
- TotalNetworkRxBytes int64
- TotalNetworkTxBytes int64
- SampleCount int64
-
- // For calculating rates/deltas
- LastCPUNanos int64
- LastMemoryBytes int64
- LastDiskReadBytes int64
- LastDiskWriteBytes int64
- LastNetworkRxBytes int64
- LastNetworkTxBytes int64
-}
-
-// UsageSummary contains aggregated usage over a time period
-type UsageSummary struct {
- VMID string
- CustomerID string
- Period time.Duration
- StartTime time.Time
- EndTime time.Time
-
- // CPU time actually used (not just allocated)
- CPUTimeUsedNanos int64
- CPUTimeUsedMs int64
-
- // Memory usage statistics
- AvgMemoryUsageBytes int64
- MaxMemoryUsageBytes int64
-
- // Disk I/O totals
- DiskReadBytes int64
- DiskWriteBytes int64
- TotalDiskIO int64
-
- // Network I/O totals
- NetworkRxBytes int64
- NetworkTxBytes int64
- TotalNetworkIO int64
-
- // Overall resource usage score (for billing)
- ResourceScore float64
-
- SampleCount int64
-}
-
-// Aggregator collects and aggregates VM usage data for billing
-type Aggregator struct {
- logger *slog.Logger
- mu sync.RWMutex
- vmData map[string]*VMUsageData // vmID -> usage data
- customers map[string][]string // customerID -> []vmID
-
- // Aggregation interval (configurable)
- aggregationInterval time.Duration
-
- // Callbacks for reporting
- onUsageSummary func(*UsageSummary)
-}
-
-// NewAggregator creates a new billing aggregator
-func NewAggregator(logger *slog.Logger, aggregationInterval time.Duration) *Aggregator {
- return &Aggregator{ //nolint:exhaustruct // mu and onUsageSummary fields use appropriate zero values and are set later
- logger: logger.With("component", "billing_aggregator"),
- vmData: make(map[string]*VMUsageData),
- customers: make(map[string][]string),
- aggregationInterval: aggregationInterval,
- }
-}
-
-// SetUsageSummaryCallback sets the callback for when usage summaries are ready
-func (a *Aggregator) SetUsageSummaryCallback(callback func(*UsageSummary)) {
- a.onUsageSummary = callback
-}
-
-// ProcessMetricsBatch processes a batch of metrics from metald
-func (a *Aggregator) ProcessMetricsBatch(vmID, customerID string, metrics []*billingv1.VMMetrics) {
- a.mu.Lock()
- defer a.mu.Unlock()
-
- if len(metrics) == 0 {
- return
- }
-
- // Get or create VM usage data
- vmUsage, exists := a.vmData[vmID]
- if !exists {
- vmUsage = &VMUsageData{ //nolint:exhaustruct // Other usage tracking fields are initialized during metric processing
- VMID: vmID,
- CustomerID: customerID,
- StartTime: metrics[0].GetTimestamp().AsTime(),
- }
- a.vmData[vmID] = vmUsage
-
- // Track customer -> VM mapping
- a.customers[customerID] = append(a.customers[customerID], vmID)
- }
-
- // Process each metric in the batch
- for _, metric := range metrics {
- a.processMetric(vmUsage, metric)
- }
-
- vmUsage.LastUpdate = time.Now()
- vmUsage.SampleCount += int64(len(metrics))
-
- a.logger.Debug("processed metrics batch",
- "vm_id", vmID,
- "customer_id", customerID,
- "metrics_count", len(metrics),
- "total_samples", vmUsage.SampleCount,
- )
-}
-
-// processMetric processes a single metric and updates usage data
-func (a *Aggregator) processMetric(vmUsage *VMUsageData, metric *billingv1.VMMetrics) {
- // Calculate deltas (incremental usage)
- cpuDelta := metric.GetCpuTimeNanos() - vmUsage.LastCPUNanos
- diskReadDelta := metric.GetDiskReadBytes() - vmUsage.LastDiskReadBytes
- diskWriteDelta := metric.GetDiskWriteBytes() - vmUsage.LastDiskWriteBytes
- netRxDelta := metric.GetNetworkRxBytes() - vmUsage.LastNetworkRxBytes
- netTxDelta := metric.GetNetworkTxBytes() - vmUsage.LastNetworkTxBytes
-
- // Only add positive deltas (handle counter resets gracefully)
- if cpuDelta > 0 {
- vmUsage.TotalCPUNanos += cpuDelta
- }
- if diskReadDelta > 0 {
- vmUsage.TotalDiskReadBytes += diskReadDelta
- }
- if diskWriteDelta > 0 {
- vmUsage.TotalDiskWriteBytes += diskWriteDelta
- }
- if netRxDelta > 0 {
- vmUsage.TotalNetworkRxBytes += netRxDelta
- }
- if netTxDelta > 0 {
- vmUsage.TotalNetworkTxBytes += netTxDelta
- }
-
- // Memory is a point-in-time value, track max and average
- if metric.GetMemoryUsageBytes() > vmUsage.TotalMemoryBytes {
- vmUsage.TotalMemoryBytes = metric.GetMemoryUsageBytes()
- }
-
- // Update last values for next delta calculation
- vmUsage.LastCPUNanos = metric.GetCpuTimeNanos()
- vmUsage.LastMemoryBytes = metric.GetMemoryUsageBytes()
- vmUsage.LastDiskReadBytes = metric.GetDiskReadBytes()
- vmUsage.LastDiskWriteBytes = metric.GetDiskWriteBytes()
- vmUsage.LastNetworkRxBytes = metric.GetNetworkRxBytes()
- vmUsage.LastNetworkTxBytes = metric.GetNetworkTxBytes()
-}
-
-// NotifyVMStarted handles VM start notifications
-func (a *Aggregator) NotifyVMStarted(vmID, customerID string, startTime int64) {
- a.mu.Lock()
- defer a.mu.Unlock()
-
- // Initialize or reset VM usage data
- vmUsage := &VMUsageData{ //nolint:exhaustruct // Usage tracking fields are initialized as metrics are received
- VMID: vmID,
- CustomerID: customerID,
- StartTime: time.Unix(0, startTime),
- }
- a.vmData[vmID] = vmUsage
-
- // Track customer mapping
- vmIDs := a.customers[customerID]
- found := false
- for _, existingVMID := range vmIDs {
- if existingVMID == vmID {
- found = true
- break
- }
- }
- if !found {
- a.customers[customerID] = append(vmIDs, vmID)
- }
-
- a.logger.Info("VM started tracking",
- "vm_id", vmID,
- "customer_id", customerID,
- "start_time", time.Unix(0, startTime).Format(time.RFC3339),
- )
-}
-
-// NotifyVMStopped handles VM stop notifications and generates final usage summary
-func (a *Aggregator) NotifyVMStopped(vmID string, stopTime int64) {
- a.mu.Lock()
- defer a.mu.Unlock()
-
- vmUsage, exists := a.vmData[vmID]
- if !exists {
- a.logger.Warn("received stop notification for unknown VM", "vm_id", vmID)
- return
- }
-
- // Generate final usage summary
- endTime := time.Unix(0, stopTime)
- summary := a.generateUsageSummary(vmUsage, endTime)
-
- a.logger.Info("VM stopped, generating final usage summary",
- "vm_id", vmID,
- "customer_id", vmUsage.CustomerID,
- "stop_time", endTime.Format(time.RFC3339),
- "total_runtime", endTime.Sub(vmUsage.StartTime).String(),
- )
-
- // Send summary if callback is set
- if a.onUsageSummary != nil {
- a.onUsageSummary(summary)
- }
-
- // Clean up VM data
- delete(a.vmData, vmID)
-
- // Remove from customer mapping
- if vmIDs, exists := a.customers[vmUsage.CustomerID]; exists {
- for i, existingVMID := range vmIDs {
- if existingVMID == vmID {
- a.customers[vmUsage.CustomerID] = append(vmIDs[:i], vmIDs[i+1:]...)
- break
- }
- }
- }
-}
-
-// GeneratePeriodicSummaries generates usage summaries for all active VMs
-func (a *Aggregator) GeneratePeriodicSummaries() {
- a.mu.RLock()
- defer a.mu.RUnlock()
-
- now := time.Now()
-
- for vmID, vmUsage := range a.vmData {
- // Skip VMs with no recent activity
- if now.Sub(vmUsage.LastUpdate) > a.aggregationInterval*2 {
- continue
- }
-
- summary := a.generateUsageSummary(vmUsage, now)
-
- a.logger.Debug("generated periodic usage summary",
- "vm_id", vmID,
- "customer_id", vmUsage.CustomerID,
- "cpu_time_ms", summary.CPUTimeUsedMs,
- "avg_memory_mb", summary.AvgMemoryUsageBytes/(1024*1024),
- "resource_score", summary.ResourceScore,
- )
-
- if a.onUsageSummary != nil {
- a.onUsageSummary(summary)
- }
- }
-}
-
-// generateUsageSummary creates a usage summary for a VM
-func (a *Aggregator) generateUsageSummary(vmUsage *VMUsageData, endTime time.Time) *UsageSummary {
- period := endTime.Sub(vmUsage.StartTime)
-
- // AIDEV-BUSINESS_RULE: Resource Score Calculation for VM Billing
- // The resource score is a composite metric that combines CPU, memory, and I/O usage
- // into a single billing unit. This weighted formula reflects the relative cost impact
- // of each resource type on infrastructure expenses:
- //
- // 1. CPU Weight (1.0): Highest weight as CPU time directly correlates with compute costs
- // and represents actual work performed vs. allocated but unused resources
- // 2. Memory Weight (0.5): Medium weight as memory allocation has moderate cost impact
- // but is often over-provisioned relative to actual usage
- // 3. I/O Weight (0.3): Lower weight as disk I/O has less direct cost impact than CPU/memory
- // but still represents meaningful resource consumption
- //
- // Formula: resourceScore = (cpuSeconds * 1.0) + (memoryGB * 0.5) + (diskMB * 0.3)
- // These weights should be periodically reviewed against actual infrastructure costs
- // and may need adjustment based on provider pricing changes or workload patterns
- cpuWeight := 1.0
- memoryWeight := 0.5
- ioWeight := 0.3
-
- cpuScore := float64(vmUsage.TotalCPUNanos) / float64(time.Second) * cpuWeight
- memoryScore := float64(vmUsage.TotalMemoryBytes) / (1024 * 1024 * 1024) * memoryWeight // GB
- ioScore := float64(vmUsage.TotalDiskReadBytes+vmUsage.TotalDiskWriteBytes) / (1024 * 1024) * ioWeight // MB
-
- resourceScore := cpuScore + memoryScore + ioScore
-
- return &UsageSummary{
- VMID: vmUsage.VMID,
- CustomerID: vmUsage.CustomerID,
- Period: period,
- StartTime: vmUsage.StartTime,
- EndTime: endTime,
- CPUTimeUsedNanos: vmUsage.TotalCPUNanos,
- CPUTimeUsedMs: vmUsage.TotalCPUNanos / 1_000_000,
- AvgMemoryUsageBytes: vmUsage.TotalMemoryBytes,
- MaxMemoryUsageBytes: vmUsage.TotalMemoryBytes,
- DiskReadBytes: vmUsage.TotalDiskReadBytes,
- DiskWriteBytes: vmUsage.TotalDiskWriteBytes,
- TotalDiskIO: vmUsage.TotalDiskReadBytes + vmUsage.TotalDiskWriteBytes,
- NetworkRxBytes: vmUsage.TotalNetworkRxBytes,
- NetworkTxBytes: vmUsage.TotalNetworkTxBytes,
- TotalNetworkIO: vmUsage.TotalNetworkRxBytes + vmUsage.TotalNetworkTxBytes,
- ResourceScore: resourceScore,
- SampleCount: vmUsage.SampleCount,
- }
-}
-
-// GetCustomerStats returns usage statistics by customer
-func (a *Aggregator) GetCustomerStats() map[string]int {
- a.mu.RLock()
- defer a.mu.RUnlock()
-
- stats := make(map[string]int)
- for customerID, vmIDs := range a.customers {
- stats[customerID] = len(vmIDs)
- }
- return stats
-}
-
-// GetActiveVMCount returns the number of currently tracked VMs
-func (a *Aggregator) GetActiveVMCount() int {
- a.mu.RLock()
- defer a.mu.RUnlock()
- return len(a.vmData)
-}
-
-// StartPeriodicAggregation starts the periodic aggregation goroutine
-func (a *Aggregator) StartPeriodicAggregation(ctx context.Context) {
- ticker := time.NewTicker(a.aggregationInterval)
- defer ticker.Stop()
-
- a.logger.InfoContext(ctx, "started periodic aggregation",
- "interval", a.aggregationInterval.String(),
- )
-
- for {
- select {
- case <-ticker.C:
- a.GeneratePeriodicSummaries()
- case <-ctx.Done():
- a.logger.InfoContext(ctx, "stopping periodic aggregation")
- return
- }
- }
-}
diff --git a/go/apps/billaged/internal/config/config.go b/go/apps/billaged/internal/config/config.go
deleted file mode 100644
index b94df2a9ff..0000000000
--- a/go/apps/billaged/internal/config/config.go
+++ /dev/null
@@ -1,206 +0,0 @@
-package config
-
-import (
- "fmt"
- "log/slog"
- "os"
- "strconv"
-)
-
-// Config holds the application configuration
-type Config struct {
- // Server configuration
- Server ServerConfig
-
- // OpenTelemetry configuration
- OpenTelemetry OpenTelemetryConfig
-
- // Aggregation configuration
- Aggregation AggregationConfig
-
- // TLS configuration (optional, defaults to disabled)
- TLS *TLSConfig
-}
-
-// ServerConfig holds server-specific configuration
-type ServerConfig struct {
- // Port to listen on
- Port string
-
- // Address to bind to
- Address string
-}
-
-// AggregationConfig holds aggregation-specific configuration
-type AggregationConfig struct {
- // Interval for usage summary aggregation
- Interval string
-}
-
-// OpenTelemetryConfig holds OpenTelemetry configuration
-type OpenTelemetryConfig struct {
- // Enabled indicates if OpenTelemetry is enabled
- Enabled bool
-
- // ServiceName for resource attributes
- ServiceName string
-
- // ServiceVersion for resource attributes
- ServiceVersion string
-
- // TracingSamplingRate from 0.0 to 1.0
- TracingSamplingRate float64
-
- // OTLPEndpoint for sending traces and metrics
- OTLPEndpoint string
-
- // PrometheusEnabled enables Prometheus metrics endpoint
- PrometheusEnabled bool
-
- // PrometheusPort for scraping metrics
- PrometheusPort string
-
- // HighCardinalityLabelsEnabled allows high-cardinality labels like vm_id and customer_id
- // Set to false in production to reduce cardinality
- HighCardinalityLabelsEnabled bool
-
- // PrometheusInterface controls the binding interface for metrics endpoint
- // Default "127.0.0.1" for localhost only (secure)
- // Set to "0.0.0.0" if remote access needed (not recommended)
- PrometheusInterface string
-}
-
-// TLSConfig holds TLS configuration
-// AIDEV-BUSINESS_RULE: SPIFFE/mTLS is required by default for security - no fallback to disabled mode
-type TLSConfig struct {
- // Mode can be "disabled", "file", or "spiffe"
- Mode string `json:"mode,omitempty"`
-
- // File-based TLS options
- CertFile string `json:"cert_file,omitempty"`
- KeyFile string `json:"-"` // AIDEV-NOTE: Never serialize private key paths
- CAFile string `json:"ca_file,omitempty"`
-
- // SPIFFE options
- SPIFFESocketPath string `json:"spiffe_socket_path,omitempty"`
-}
-
-// LoadConfig loads configuration from environment variables
-func LoadConfig() (*Config, error) {
- return LoadConfigWithLogger(slog.Default())
-}
-
-// LoadConfigWithLogger loads configuration from environment variables with custom logger
-func LoadConfigWithLogger(logger *slog.Logger) (*Config, error) {
- // Parse sampling rate
- samplingRate := 1.0
- if samplingStr := os.Getenv("UNKEY_BILLAGED_OTEL_SAMPLING_RATE"); samplingStr != "" {
- if parsed, err := strconv.ParseFloat(samplingStr, 64); err == nil {
- samplingRate = parsed
- } else {
- logger.Warn("invalid UNKEY_BILLAGED_OTEL_SAMPLING_RATE, using default 1.0",
- slog.String("value", samplingStr),
- slog.String("error", err.Error()),
- )
- }
- }
-
- // Parse enabled flag
- otelEnabled := false
- if enabledStr := os.Getenv("UNKEY_BILLAGED_OTEL_ENABLED"); enabledStr != "" {
- if parsed, err := strconv.ParseBool(enabledStr); err == nil {
- otelEnabled = parsed
- } else {
- logger.Warn("invalid UNKEY_BILLAGED_OTEL_ENABLED, using default false",
- slog.String("value", enabledStr),
- slog.String("error", err.Error()),
- )
- }
- }
-
- // Parse Prometheus enabled flag
- prometheusEnabled := true // Default to true when OTEL is enabled
- if promStr := os.Getenv("UNKEY_BILLAGED_OTEL_PROMETHEUS_ENABLED"); promStr != "" {
- if parsed, err := strconv.ParseBool(promStr); err == nil {
- prometheusEnabled = parsed
- } else {
- logger.Warn("invalid UNKEY_BILLAGED_OTEL_PROMETHEUS_ENABLED, using default true",
- slog.String("value", promStr),
- slog.String("error", err.Error()),
- )
- }
- }
-
- // Parse high cardinality labels flag
- highCardinalityLabelsEnabled := false // Default to false for production safety
- if highCardStr := os.Getenv("UNKEY_BILLAGED_OTEL_HIGH_CARDINALITY_ENABLED"); highCardStr != "" {
- if parsed, err := strconv.ParseBool(highCardStr); err == nil {
- highCardinalityLabelsEnabled = parsed
- } else {
- logger.Warn("invalid UNKEY_BILLAGED_OTEL_HIGH_CARDINALITY_ENABLED, using default false",
- slog.String("value", highCardStr),
- slog.String("error", err.Error()),
- )
- }
- }
-
- cfg := &Config{
- Server: ServerConfig{
- Port: getEnvOrDefault("UNKEY_BILLAGED_PORT", "8081"),
- Address: getEnvOrDefault("UNKEY_BILLAGED_ADDRESS", "0.0.0.0"),
- },
- Aggregation: AggregationConfig{
- Interval: getEnvOrDefault("UNKEY_BILLAGED_AGGREGATION_INTERVAL", "60s"),
- },
- OpenTelemetry: OpenTelemetryConfig{
- Enabled: otelEnabled,
- ServiceName: getEnvOrDefault("UNKEY_BILLAGED_OTEL_SERVICE_NAME", "billaged"),
- ServiceVersion: getEnvOrDefault("UNKEY_BILLAGED_OTEL_SERVICE_VERSION", "0.1.0"),
- TracingSamplingRate: samplingRate,
- OTLPEndpoint: getEnvOrDefault("UNKEY_BILLAGED_OTEL_ENDPOINT", "localhost:4318"),
- PrometheusEnabled: prometheusEnabled,
- PrometheusPort: getEnvOrDefault("UNKEY_BILLAGED_OTEL_PROMETHEUS_PORT", "9465"),
- PrometheusInterface: getEnvOrDefault("UNKEY_BILLAGED_OTEL_PROMETHEUS_INTERFACE", "127.0.0.1"),
- HighCardinalityLabelsEnabled: highCardinalityLabelsEnabled,
- },
- TLS: &TLSConfig{
- Mode: getEnvOrDefault("UNKEY_BILLAGED_TLS_MODE", "spiffe"),
- CertFile: getEnvOrDefault("UNKEY_BILLAGED_TLS_CERT_FILE", ""),
- KeyFile: getEnvOrDefault("UNKEY_BILLAGED_TLS_KEY_FILE", ""),
- CAFile: getEnvOrDefault("UNKEY_BILLAGED_TLS_CA_FILE", ""),
- SPIFFESocketPath: getEnvOrDefault("UNKEY_BILLAGED_SPIFFE_SOCKET", "/var/lib/spire/agent/agent.sock"),
- },
- }
-
- // Validate configuration
- if err := cfg.Validate(); err != nil {
- return nil, fmt.Errorf("invalid configuration: %w", err)
- }
-
- return cfg, nil
-}
-
-// Validate validates the configuration
-func (c *Config) Validate() error {
- if c.OpenTelemetry.Enabled {
- if c.OpenTelemetry.TracingSamplingRate < 0.0 || c.OpenTelemetry.TracingSamplingRate > 1.0 {
- return fmt.Errorf("tracing sampling rate must be between 0.0 and 1.0, got %f", c.OpenTelemetry.TracingSamplingRate)
- }
- if c.OpenTelemetry.OTLPEndpoint == "" {
- return fmt.Errorf("OTLP endpoint is required when OpenTelemetry is enabled")
- }
- if c.OpenTelemetry.ServiceName == "" {
- return fmt.Errorf("service name is required when OpenTelemetry is enabled")
- }
- }
-
- return nil
-}
-
-// getEnvOrDefault gets an environment variable or returns a default value
-func getEnvOrDefault(key, defaultValue string) string {
- if value := os.Getenv(key); value != "" {
- return value
- }
- return defaultValue
-}
diff --git a/go/apps/billaged/internal/observability/metrics.go b/go/apps/billaged/internal/observability/metrics.go
deleted file mode 100644
index 1301e562b9..0000000000
--- a/go/apps/billaged/internal/observability/metrics.go
+++ /dev/null
@@ -1,118 +0,0 @@
-package observability
-
-import (
- "context"
- "fmt"
- "log/slog"
-
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/metric"
-)
-
-// BillingMetrics holds billing-specific metrics
-type BillingMetrics struct {
- usageRecordsProcessed metric.Int64Counter
- aggregationDuration metric.Float64Histogram
- activeVMs metric.Int64UpDownCounter
- billingErrors metric.Int64Counter
- highCardinalityEnabled bool
-}
-
-// NewBillingMetrics creates new billing metrics
-func NewBillingMetrics(logger *slog.Logger, highCardinalityEnabled bool) (*BillingMetrics, error) {
- meter := meter()
- if meter == nil {
- return nil, fmt.Errorf("OpenTelemetry meter not available")
- }
-
- usageRecordsProcessed, err := meter.Int64Counter(
- "billaged_usage_records_processed_total",
- metric.WithDescription("Total number of usage records processed"),
- metric.WithUnit("1"),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create usage records counter: %w", err)
- }
-
- aggregationDuration, err := meter.Float64Histogram(
- "billaged_aggregation_duration_seconds",
- metric.WithDescription("Time spent aggregating usage metrics"),
- metric.WithUnit("s"),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create aggregation duration histogram: %w", err)
- }
-
- activeVMs, err := meter.Int64UpDownCounter(
- "billaged_active_vms",
- metric.WithDescription("Number of active VMs being tracked"),
- metric.WithUnit("1"),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create active VMs counter: %w", err)
- }
-
- billingErrors, err := meter.Int64Counter(
- "billaged_billing_errors_total",
- metric.WithDescription("Total number of billing processing errors"),
- metric.WithUnit("1"),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create billing errors counter: %w", err)
- }
-
- logger.Info("billing metrics initialized")
-
- return &BillingMetrics{
- usageRecordsProcessed: usageRecordsProcessed,
- aggregationDuration: aggregationDuration,
- activeVMs: activeVMs,
- billingErrors: billingErrors,
- highCardinalityEnabled: highCardinalityEnabled,
- }, nil
-}
-
-// meter returns the global meter
-func meter() metric.Meter {
- return otel.Meter("billaged/billing")
-}
-
-// RecordUsageProcessed records that a usage record was processed
-func (m *BillingMetrics) RecordUsageProcessed(ctx context.Context, vmID, customerID string) {
- if m != nil {
- var attrs []attribute.KeyValue
- if m.highCardinalityEnabled {
- attrs = []attribute.KeyValue{
- attribute.String("vm_id", vmID),
- attribute.String("customer_id", customerID),
- }
- }
- m.usageRecordsProcessed.Add(ctx, 1, metric.WithAttributes(attrs...))
- }
-}
-
-// RecordAggregationDuration records how long aggregation took
-func (m *BillingMetrics) RecordAggregationDuration(ctx context.Context, duration float64) {
- if m != nil {
- m.aggregationDuration.Record(ctx, duration)
- }
-}
-
-// UpdateActiveVMs updates the number of active VMs
-func (m *BillingMetrics) UpdateActiveVMs(ctx context.Context, count int64) {
- if m != nil {
- m.activeVMs.Add(ctx, count)
- }
-}
-
-// RecordBillingError records a billing processing error
-func (m *BillingMetrics) RecordBillingError(ctx context.Context, errorType string) {
- if m != nil {
- m.billingErrors.Add(ctx, 1,
- metric.WithAttributes(
- attribute.String("error_type", errorType),
- ),
- )
- }
-}
diff --git a/go/apps/billaged/internal/observability/otel.go b/go/apps/billaged/internal/observability/otel.go
deleted file mode 100644
index 9471aff008..0000000000
--- a/go/apps/billaged/internal/observability/otel.go
+++ /dev/null
@@ -1,220 +0,0 @@
-package observability
-
-import (
- "context"
- "errors"
- "fmt"
- "net/http"
- "time"
-
- "github.com/unkeyed/unkey/go/apps/billaged/internal/config"
-
- "github.com/prometheus/client_golang/prometheus/promhttp"
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/codes"
- "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
- "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
- "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
- "go.opentelemetry.io/otel/exporters/prometheus"
- "go.opentelemetry.io/otel/metric"
- "go.opentelemetry.io/otel/propagation"
- sdkmetric "go.opentelemetry.io/otel/sdk/metric"
- "go.opentelemetry.io/otel/sdk/resource"
- sdktrace "go.opentelemetry.io/otel/sdk/trace"
- semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
- "go.opentelemetry.io/otel/trace"
- "go.opentelemetry.io/otel/trace/noop"
-)
-
-// Providers holds the OpenTelemetry providers
-type Providers struct {
- TracerProvider trace.TracerProvider
- MeterProvider metric.MeterProvider
- PrometheusHTTP http.Handler
- Shutdown func(context.Context) error
-}
-
-// InitProviders initializes OpenTelemetry providers
-func InitProviders(ctx context.Context, cfg *config.Config, version string) (*Providers, error) {
- if !cfg.OpenTelemetry.Enabled {
- // Return no-op providers
- return &Providers{
- TracerProvider: noop.NewTracerProvider(),
- MeterProvider: nil,
- PrometheusHTTP: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusNotFound)
- _, _ = w.Write([]byte("OpenTelemetry is disabled"))
- }),
- Shutdown: func(context.Context) error { return nil },
- }, nil
- }
-
- // Schema conflict fix - Using semconv v1.24.0 with OTEL v1.36.0
- res, err := resource.New(ctx,
- resource.WithAttributes(
- semconv.ServiceNamespace("unkey"),
- semconv.ServiceName(cfg.OpenTelemetry.ServiceName),
- semconv.ServiceVersion(version),
- ),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create OTEL resource: %w", err)
- }
-
- // Initialize trace provider
- tracerProvider, tracerShutdown, err := initTracerProvider(ctx, cfg, res)
- if err != nil {
- return nil, fmt.Errorf("failed to initialize tracer provider: %w", err)
- }
-
- // Initialize meter provider
- meterProvider, promHandler, meterShutdown, err := initMeterProvider(ctx, cfg, res)
- if err != nil {
- _ = tracerShutdown(ctx)
- return nil, fmt.Errorf("failed to initialize meter provider: %w", err)
- }
-
- // Set global providers
- otel.SetTracerProvider(tracerProvider)
- otel.SetMeterProvider(meterProvider)
- otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
- propagation.TraceContext{},
- propagation.Baggage{},
- ))
-
- // Combined shutdown function
- shutdown := func(ctx context.Context) error {
- var errs []error
-
- if err := tracerShutdown(ctx); err != nil {
- errs = append(errs, fmt.Errorf("tracer shutdown error: %w", err))
- }
-
- if err := meterShutdown(ctx); err != nil {
- errs = append(errs, fmt.Errorf("meter shutdown error: %w", err))
- }
-
- if len(errs) > 0 {
- return errors.Join(errs...)
- }
-
- return nil
- }
-
- return &Providers{
- TracerProvider: tracerProvider,
- MeterProvider: meterProvider,
- PrometheusHTTP: promHandler,
- Shutdown: shutdown,
- }, nil
-}
-
-// initTracerProvider initializes the tracer provider
-func initTracerProvider(ctx context.Context, cfg *config.Config, res *resource.Resource) (trace.TracerProvider, func(context.Context) error, error) {
- // Create OTLP trace exporter
- traceExporter, err := otlptrace.New(ctx,
- otlptracehttp.NewClient(
- otlptracehttp.WithEndpoint(cfg.OpenTelemetry.OTLPEndpoint),
- otlptracehttp.WithInsecure(), // For local development
- otlptracehttp.WithTimeout(30*time.Second),
- ),
- )
- if err != nil {
- return nil, nil, fmt.Errorf("failed to create trace exporter: %w", err)
- }
-
- // Create sampler with parent-based + ratio
- ratioSampler := sdktrace.TraceIDRatioBased(cfg.OpenTelemetry.TracingSamplingRate)
- parentBasedSampler := sdktrace.ParentBased(ratioSampler)
-
- // Create tracer provider
- tp := sdktrace.NewTracerProvider(
- sdktrace.WithBatcher(traceExporter),
- sdktrace.WithResource(res),
- sdktrace.WithSampler(parentBasedSampler),
- )
-
- return tp, tp.Shutdown, nil
-}
-
-// initMeterProvider initializes the meter provider
-func initMeterProvider(ctx context.Context, cfg *config.Config, res *resource.Resource) (metric.MeterProvider, http.Handler, func(context.Context) error, error) {
- var readers []sdkmetric.Reader
-
- // OTLP metric exporter
- metricExporter, err := otlpmetrichttp.New(ctx,
- otlpmetrichttp.WithEndpoint(cfg.OpenTelemetry.OTLPEndpoint),
- otlpmetrichttp.WithInsecure(), // For local development
- otlpmetrichttp.WithTimeout(30*time.Second),
- )
- if err != nil {
- return nil, nil, nil, fmt.Errorf("failed to create metric exporter: %w", err)
- }
-
- readers = append(readers, sdkmetric.NewPeriodicReader(
- metricExporter,
- sdkmetric.WithInterval(30*time.Second),
- ))
-
- // Prometheus exporter
- var promHandler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusNotFound)
- _, _ = w.Write([]byte("Prometheus metrics disabled"))
- })
-
- if cfg.OpenTelemetry.PrometheusEnabled {
- promExporter, err := prometheus.New()
- if err != nil {
- return nil, nil, nil, fmt.Errorf("failed to create prometheus exporter: %w", err)
- }
- readers = append(readers, promExporter)
- promHandler = promhttp.Handler()
- }
-
- // Create meter provider with readers
- mpOpts := []sdkmetric.Option{
- sdkmetric.WithResource(res),
- }
- for _, reader := range readers {
- mpOpts = append(mpOpts, sdkmetric.WithReader(reader))
- }
- mp := sdkmetric.NewMeterProvider(mpOpts...)
-
- return mp, promHandler, mp.Shutdown, nil
-}
-
-// RecordError records an error in the current span and sets the status
-func RecordError(span trace.Span, err error) {
- if err != nil {
- span.RecordError(err)
- span.SetStatus(codes.Error, err.Error())
- }
-}
-
-// HTTPStatusCode returns the appropriate trace status code for an HTTP status
-func HTTPStatusCode(httpStatus int) codes.Code {
- if httpStatus >= 200 && httpStatus < 400 {
- return codes.Ok
- }
- return codes.Error
-}
-
-// SpanKindFromMethod returns the appropriate span kind for a method
-func SpanKindFromMethod(method string) trace.SpanKind {
- switch method {
- case "GET", "HEAD", "OPTIONS":
- return trace.SpanKindClient
- default:
- return trace.SpanKindInternal
- }
-}
-
-// ServiceAttributes returns common service attributes
-func ServiceAttributes(cfg *config.Config, version string) []attribute.KeyValue {
- return []attribute.KeyValue{
- semconv.ServiceName(cfg.OpenTelemetry.ServiceName),
- semconv.ServiceVersion(version),
- semconv.ServiceNamespace("unkey"),
- }
-}
diff --git a/go/apps/billaged/internal/service/billing.go b/go/apps/billaged/internal/service/billing.go
deleted file mode 100644
index 1cb2061523..0000000000
--- a/go/apps/billaged/internal/service/billing.go
+++ /dev/null
@@ -1,179 +0,0 @@
-package service
-
-import (
- "context"
- "fmt"
- "log/slog"
- "time"
-
- "connectrpc.com/connect"
- "github.com/unkeyed/unkey/go/apps/billaged/internal/aggregator"
- "github.com/unkeyed/unkey/go/apps/billaged/internal/observability"
- billingv1 "github.com/unkeyed/unkey/go/gen/proto/billaged/v1"
- "github.com/unkeyed/unkey/go/gen/proto/billaged/v1/billagedv1connect"
-)
-
-// BillingService implements the billaged ConnectRPC service
-type BillingService struct {
- logger *slog.Logger
- aggregator *aggregator.Aggregator
- metrics *observability.BillingMetrics
-}
-
-// NewBillingService creates a new billing service
-func NewBillingService(logger *slog.Logger, agg *aggregator.Aggregator, metrics *observability.BillingMetrics) *BillingService {
- return &BillingService{
- logger: logger.With("component", "billing_service"),
- aggregator: agg,
- metrics: metrics,
- }
-}
-
-// SendMetricsBatch processes a batch of VM metrics from metald
-func (s *BillingService) SendMetricsBatch(
- ctx context.Context,
- req *connect.Request[billingv1.SendMetricsBatchRequest],
-) (*connect.Response[billingv1.SendMetricsBatchResponse], error) {
- vmID := req.Msg.GetVmId()
- customerID := req.Msg.GetCustomerId()
- metrics := req.Msg.GetMetrics()
-
- s.logger.InfoContext(ctx, "received metrics batch",
- "vm_id", vmID,
- "customer_id", customerID,
- "metrics_count", len(metrics),
- )
-
- if len(metrics) == 0 {
- return connect.NewResponse(&billingv1.SendMetricsBatchResponse{
- Success: false,
- Message: "no metrics provided",
- }), nil
- }
-
- // Log first and last metric for debugging
- first := metrics[0]
- last := metrics[len(metrics)-1]
- s.logger.DebugContext(ctx, "metrics batch details",
- "vm_id", vmID,
- "first_timestamp", first.GetTimestamp().AsTime().Format("15:04:05.000"),
- "last_timestamp", last.GetTimestamp().AsTime().Format("15:04:05.000"),
- "first_cpu_nanos", first.GetCpuTimeNanos(),
- "last_cpu_nanos", last.GetCpuTimeNanos(),
- "timespan_ms", last.GetTimestamp().AsTime().Sub(first.GetTimestamp().AsTime()).Milliseconds(),
- )
-
- // Record metrics
- start := time.Now()
- if s.metrics != nil {
- s.metrics.RecordUsageProcessed(ctx, vmID, customerID)
- }
-
- // Process metrics through aggregator
- s.aggregator.ProcessMetricsBatch(vmID, customerID, metrics)
-
- // Record aggregation duration
- if s.metrics != nil {
- s.metrics.RecordAggregationDuration(ctx, time.Since(start).Seconds())
- }
-
- return connect.NewResponse(&billingv1.SendMetricsBatchResponse{
- Success: true,
- Message: fmt.Sprintf("processed %d metrics", len(metrics)),
- }), nil
-}
-
-// SendHeartbeat processes heartbeat from metald with active VM list
-func (s *BillingService) SendHeartbeat(
- ctx context.Context,
- req *connect.Request[billingv1.SendHeartbeatRequest],
-) (*connect.Response[billingv1.SendHeartbeatResponse], error) {
- instanceID := req.Msg.GetInstanceId()
- activeVMs := req.Msg.GetActiveVms()
-
- s.logger.DebugContext(ctx, "received heartbeat",
- "instance_id", instanceID,
- "active_vms_count", len(activeVMs),
- "active_vms", activeVMs,
- )
-
- // Heartbeat processing could include health checks,
- // gap detection, or VM lifecycle validation here
-
- return connect.NewResponse(&billingv1.SendHeartbeatResponse{
- Success: true,
- }), nil
-}
-
-// NotifyVmStarted handles VM start notifications
-func (s *BillingService) NotifyVmStarted(
- ctx context.Context,
- req *connect.Request[billingv1.NotifyVmStartedRequest],
-) (*connect.Response[billingv1.NotifyVmStartedResponse], error) {
- vmID := req.Msg.GetVmId()
- customerID := req.Msg.GetCustomerId()
- startTime := req.Msg.GetStartTime()
-
- s.logger.InfoContext(ctx, "VM started notification",
- "vm_id", vmID,
- "customer_id", customerID,
- "start_time", startTime,
- )
-
- s.aggregator.NotifyVMStarted(vmID, customerID, startTime)
-
- return connect.NewResponse(&billingv1.NotifyVmStartedResponse{
- Success: true,
- }), nil
-}
-
-// NotifyVmStopped handles VM stop notifications
-func (s *BillingService) NotifyVmStopped(
- ctx context.Context,
- req *connect.Request[billingv1.NotifyVmStoppedRequest],
-) (*connect.Response[billingv1.NotifyVmStoppedResponse], error) {
- vmID := req.Msg.GetVmId()
- stopTime := req.Msg.GetStopTime()
-
- s.logger.InfoContext(ctx, "VM stopped notification",
- "vm_id", vmID,
- "stop_time", stopTime,
- )
-
- s.aggregator.NotifyVMStopped(vmID, stopTime)
-
- return connect.NewResponse(&billingv1.NotifyVmStoppedResponse{
- Success: true,
- }), nil
-}
-
-// NotifyPossibleGap handles data gap notifications
-func (s *BillingService) NotifyPossibleGap(
- ctx context.Context,
- req *connect.Request[billingv1.NotifyPossibleGapRequest],
-) (*connect.Response[billingv1.NotifyPossibleGapResponse], error) {
- vmID := req.Msg.GetVmId()
- lastSent := req.Msg.GetLastSent()
- resumeTime := req.Msg.GetResumeTime()
-
- gapDurationMs := (resumeTime - lastSent) / 1_000_000
-
- s.logger.WarnContext(ctx, "possible data gap notification",
- "vm_id", vmID,
- "last_sent", lastSent,
- "resume_time", resumeTime,
- "gap_duration_ms", gapDurationMs,
- )
-
- // Gap handling could include:
- // - Marking billing periods as incomplete
- // - Triggering reconciliation processes
- // - Alerting operations teams
-
- return connect.NewResponse(&billingv1.NotifyPossibleGapResponse{
- Success: true,
- }), nil
-}
-
-// Ensure BillingService implements the interface
-var _ billagedv1connect.BillingServiceHandler = (*BillingService)(nil)
diff --git a/go/apps/builderd/.gitignore b/go/apps/builderd/.gitignore
deleted file mode 100644
index 486d26f3c0..0000000000
--- a/go/apps/builderd/.gitignore
+++ /dev/null
@@ -1,91 +0,0 @@
-# Compiled binaries (SECURITY: Never commit compiled binaries)
-build/
-*.exe
-*.dll
-*.so
-*.dylib
-
-# Test binaries, built with `go test -c`
-*.test
-
-# Output of the go coverage tool
-*.out
-
-# Dependency directories (remove the comment below to include it)
-vendor/
-
-# Go workspace file
-go.work
-go.work.sum
-
-# IDE files
-.vscode/
-.idea/
-*.swp
-*.swo
-*~
-
-# OS files
-.DS_Store
-Thumbs.db
-
-# Local development files
-.env
-.env.local
-.env.development
-.env.test
-.env.production
-
-# Temporary files
-tmp/
-temp/
-*.tmp
-
-# Logs
-*.log
-logs/
-
-# Database files
-*.db
-*.sqlite
-*.sqlite3
-
-# Build artifacts and cache
-dist/
-cache/
-.cache/
-
-# Coverage reports
-coverage.html
-coverage.out
-profile.out
-
-# Backup files
-*.bak
-*.backup
-
-# Docker build context (if using dockerignore isn't sufficient)
-.dockerignore
-
-# Certificate files (never commit certificates or keys)
-*.pem
-*.key
-*.crt
-*.p12
-*.pfx
-
-# Secret files
-secrets.yaml
-secrets.json
-.secrets
-
-# Local storage directories for development
-data/
-storage/
-scratch/
-rootfs/
-workspace/
-
-# binaries from make etc
-client/builderd-cli
-cmd/builderd/builderd
diff --git a/go/apps/builderd/CHANGELOG.md b/go/apps/builderd/CHANGELOG.md
deleted file mode 100644
index 98a139ecd1..0000000000
--- a/go/apps/builderd/CHANGELOG.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# Changelog
-
-All notable changes to builderd will be documented in this file.
-
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-
-## [0.5.1] - 2025-07-02
-
-### Changed
-- Update client.go
-
diff --git a/go/apps/builderd/Makefile b/go/apps/builderd/Makefile
deleted file mode 100644
index 747c43949f..0000000000
--- a/go/apps/builderd/Makefile
+++ /dev/null
@@ -1,152 +0,0 @@
-# Builderd Makefile
-# Unified Makefile following Unkey service standards
-
-# Service name and binary
-SERVICE_NAME := builderd
-BINARY_NAME := $(SERVICE_NAME)
-
-# Versioning
-VERSION ?= 0.5.1
-COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
-BUILD_TIME := $(shell date -u +%Y%m%d-%H%M%S)
-
-# Build flags
-LDFLAGS := -ldflags "-X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.buildTime=$(BUILD_TIME)"
-
-# Directories
-BUILD_DIR := build
-PROTO_DIR := proto
-GEN_DIR := gen
-INSTALL_DIR := /usr/local/bin
-SYSTEMD_DIR := /etc/systemd/system
-CONFIG_DIR := /etc/$(SERVICE_NAME)
-DATA_DIR := /var/lib/$(SERVICE_NAME)
-LOG_DIR := /var/log/$(SERVICE_NAME)
-
-# Go commands
-GOCMD := go
-GOBUILD := $(GOCMD) build
-GOTEST := $(GOCMD) test
-GOVET := $(GOCMD) vet
-GOFMT := goimports
-GOLINT := golangci-lint
-
-# Default target
-.DEFAULT_GOAL := help
-
-# Targets (alphabetically ordered)
-.PHONY: all build build-linux check ci clean clean-gen debug deps dev env-example fmt generate help install install-tools lint lint-proto proto-breaking quick-build quick-test release run service-logs service-logs-tail service-restart service-start service-status service-stop setup test test-coverage uninstall version vet
-
-all: clean generate build ## Clean, generate, and build
-
-build: deps ## Build the binary
- @mkdir -p $(BUILD_DIR)
- @$(GOBUILD) $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME) ./cmd/$(SERVICE_NAME)
-
-build-linux: ## Build Linux binary for deployment
- @mkdir -p $(BUILD_DIR)
- @GOOS=linux GOARCH=amd64 $(GOBUILD) $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-linux ./cmd/$(SERVICE_NAME)
-
-check: fmt vet lint test ## Run all checks (fmt, vet, lint with proto, test)
-
-ci: deps generate lint vet test ## Run CI pipeline
-
-clean: ## Clean build artifacts
- @rm -rf $(BUILD_DIR)
- @rm -rf $(GEN_DIR)
- @rm -f coverage.out coverage.html
-
-debug: build ## Run with debug logging
- @UNKEY_BUILDERD_LOG_LEVEL=debug ./$(BUILD_DIR)/$(BINARY_NAME)
-
-deps: ## Download and tidy dependencies
- @go mod download
- @go mod tidy
-
-dev: ## Run in development mode
- @go run ./cmd/$(SERVICE_NAME)
-
-fmt: ## Format code
- @$(GOFMT) -w .
- @which goimports >/dev/null && goimports -w . || echo "goimports not found, install with: go install golang.org/x/tools/cmd/goimports@latest"
-
-generate: ## Generate protobuf code
- @buf generate
- @buf lint
-
-help: ## Show this help message
- @echo 'Usage: make [target]'
- @echo ''
- @echo 'Targets:'
- @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-20s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
-
-install: build ## Install the service (requires sudo)
- @sudo systemctl stop $(SERVICE_NAME) 2>/dev/null || true
- @sudo mkdir -p $(CONFIG_DIR)
- @sudo cp $(BUILD_DIR)/$(BINARY_NAME) $(INSTALL_DIR)/
- @sudo chmod +x $(INSTALL_DIR)/$(BINARY_NAME)
- @sudo cp contrib/systemd/$(SERVICE_NAME).service $(SYSTEMD_DIR)/
- @sudo systemctl daemon-reload
- @sudo systemctl enable $(SERVICE_NAME) >/dev/null 2>&1
- @sudo systemctl start $(SERVICE_NAME) 2>/dev/null || true
- @echo "✓ $(SERVICE_NAME) installed and started"
-
-quick-build: ## Quick build without optimizations
- @mkdir -p $(BUILD_DIR)
- @$(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME) ./cmd/$(SERVICE_NAME)
-
-quick-test: ## Run tests without verbose output
- @$(GOTEST) ./...
-
-release: clean build build-linux test ## Build release artifacts
- @echo "✓ Release artifacts in $(BUILD_DIR)/"
-
-run: build ## Build and run the service
- @./$(BUILD_DIR)/$(BINARY_NAME)
-
-service-logs: ## Follow service logs
- @sudo journalctl -u $(SERVICE_NAME) -f
-
-service-logs-tail: ## Show last 50 lines of logs
- @sudo journalctl -u $(SERVICE_NAME) -n 50 --no-pager
-
-service-restart: ## Restart the service
- @sudo systemctl restart $(SERVICE_NAME)
- @echo "✓ $(SERVICE_NAME) restarted"
-
-service-start: ## Start the service
- @sudo systemctl start $(SERVICE_NAME)
- @echo "✓ $(SERVICE_NAME) started"
-
-service-status: ## Check service status
- @sudo systemctl status $(SERVICE_NAME) --no-pager
-
-service-stop: ## Stop the service
- @sudo systemctl stop $(SERVICE_NAME)
- @echo "✓ $(SERVICE_NAME) stopped"
-
-setup: deps generate ## Complete development setup
-
-test: ## Run tests
- @$(GOTEST) -v ./...
-
-test-coverage: ## Run tests with coverage
- @$(GOTEST) -v -coverprofile=coverage.out ./...
- @go tool cover -html=coverage.out -o coverage.html
- @echo "✓ Coverage report: coverage.html"
-
-uninstall: ## Uninstall the service (requires sudo)
- @sudo systemctl stop $(SERVICE_NAME) 2>/dev/null || true
- @sudo systemctl disable $(SERVICE_NAME) 2>/dev/null || true
- @sudo rm -f $(SYSTEMD_DIR)/$(SERVICE_NAME).service
- @sudo rm -f $(INSTALL_DIR)/$(BINARY_NAME)
- @sudo systemctl daemon-reload
- @echo "✓ $(SERVICE_NAME) uninstalled (config/data preserved)"
-
-version: ## Show version information
- @echo "$(SERVICE_NAME) version: $(VERSION)"
- @echo "Commit: $(COMMIT)"
- @echo "Build time: $(BUILD_TIME)"
-
-vet: ## Run go vet
- @$(GOVET) ./...
diff --git a/go/apps/builderd/README.md b/go/apps/builderd/README.md
deleted file mode 100644
index 8fb43888bb..0000000000
--- a/go/apps/builderd/README.md
+++ /dev/null
@@ -1,285 +0,0 @@
-# Builderd - Multi-Tenant Build Service
-
-Builderd transforms various source types into optimized rootfs images for Firecracker microVM execution with comprehensive multi-tenant isolation and resource management.
-
-## Quick Links
-
-- [API Documentation](./docs/api/README.md) - Complete API reference with examples
-- [Architecture & Dependencies](./docs/architecture/README.md) - Service design and integrations
-- [Operations Guide](./docs/operations/README.md) - Production deployment and monitoring
-- [Development Setup](./docs/development/README.md) - Build, test, and local development
-
-## Service Overview
-
-**Purpose**: Multi-tenant build execution service that processes Docker images, Git repositories, and archives to produce optimized ext4 rootfs images for microVM deployment.
-
-**Implementation**: [BuilderService](internal/service/builder.go:23) with [DockerExecutor](internal/executor/docker.go:25) for Docker image processing and [tenant manager](internal/tenant/manager.go:14) for multi-tenant isolation.
-
-### Key Features
-
-- **Multi-Tenant Isolation**: Linux namespaces, cgroups, and tenant-specific resource limits
-- **Docker Image Processing**: Pull, extract, and optimize Docker images to rootfs
-- **Asset Registration**: Automatic registration with [assetmanagerd](../assetmanagerd/README.md) for VM deployment
-- **Real-time Monitoring**: OpenTelemetry tracing, build metrics, and streaming logs
-- **Resource Management**: Per-tenant quotas for CPU, memory, disk, and concurrent builds
-- **Optimization**: Rootfs size reduction through layer flattening and cleanup
-- **Security**: SPIFFE/mTLS authentication and sandboxed build execution
-
-### Dependencies
-
-- [assetmanagerd](../assetmanagerd/README.md) - Registers built artifacts for VM provisioning ([client implementation](internal/assetmanager/client.go:63))
-- [metald](../metald/README.md) - Consumes registered assets for VM creation
-- SPIFFE/Spire - Service authentication and mTLS ([TLS provider](cmd/builderd/main.go:147))
-- Docker Engine - Image pulling and container operations ([executor implementation](internal/executor/docker.go:25))
-- OpenTelemetry - Observability and metrics collection ([metrics setup](internal/observability/otel.go:1))
-
-// AIDEV-NOTE: Documentation updated with source code references for easy navigation
-
-## Quick Start
-
-### Installation
-
-```bash
-# Build from source
-cd builderd
-make build
-
-# Install with systemd
-sudo make install
-```
-
-### Basic Configuration
-
-```bash
-# Minimal configuration for development
-export UNKEY_BUILDERD_PORT=8082
-export UNKEY_BUILDERD_STORAGE_BACKEND=local
-export UNKEY_BUILDERD_ROOTFS_OUTPUT_DIR=/opt/builderd/rootfs
-export UNKEY_BUILDERD_TLS_MODE=spiffe
-export UNKEY_BUILDERD_ASSETMANAGER_ENABLED=true
-
-./builderd
-```
-
-### Create Your First Build
-
-```bash
-# Submit a Docker image build
-curl -X POST http://localhost:8082/builder.v1.BuilderService/CreateBuild \
- -H "Content-Type: application/json" \
- -d '{
- "config": {
- "tenant": {
- "tenant_id": "example-tenant",
- "tier": "TENANT_TIER_FREE"
- },
- "source": {
- "docker_image": {
- "image_uri": "nginx:1.21-alpine"
- }
- },
- "target": {
- "microvm_rootfs": {
- "init_strategy": "INIT_STRATEGY_TINI"
- }
- },
- "strategy": {
- "docker_extract": {}
- }
- }
- }'
-```
-
-## Overview
-
-builderd is designed to handle the complexities of multi-tenant build execution with a focus on:
-
-- **Multi-Tenant Isolation**: Secure build environments with resource quotas per tenant
-- **Flexible Source Support**: Docker images, Git repositories, and archive formats
-- **Build Optimization**: Automatic rootfs optimization for microVM deployment
-- **Resource Management**: CPU, memory, disk, and time limits per tenant tier
-- **Comprehensive Observability**: OpenTelemetry integration with metrics and tracing
-- **High Performance**: Concurrent build execution with efficient caching
-
-### Key Features
-
-- **Source Types**:
- - Docker image extraction with registry authentication
- - Git repository builds (planned)
- - Archive extraction (planned)
-
-- **Build Targets**:
- - MicroVM rootfs with init strategies (tini, direct, custom)
- - Container images (planned)
- - WebAssembly modules (planned)
-
-- **Tenant Management**:
- - Service tiers (Free, Pro, Enterprise, Dedicated)
- - Resource quotas and limits
- - Build history and statistics
- - Cost tracking for billing integration
-
-- **Security**:
- - SPIFFE/mTLS for service communication
- - Tenant isolation with namespaces and cgroups
- - Registry access controls
- - Build-time security scanning (planned)
-
-## Service Endpoints
-
-- **gRPC/ConnectRPC**: `:8082/builder.v1.BuilderService/*`
-- **Health Check**: `:8082/health` (rate limited)
-- **Prometheus Metrics**: `:9466/metrics` (when enabled)
-
-## Configuration
-
-builderd uses environment variables following the `UNKEY_BUILDERD_*` pattern:
-
-### Core Settings
-```bash
-UNKEY_BUILDERD_PORT=8082 # Service port
-UNKEY_BUILDERD_ADDRESS=0.0.0.0 # Bind address
-UNKEY_BUILDERD_SHUTDOWN_TIMEOUT=15s # Graceful shutdown timeout
-UNKEY_BUILDERD_RATE_LIMIT=100 # Health endpoint rate limit/sec
-```
-
-### Build Configuration
-```bash
-UNKEY_BUILDERD_MAX_CONCURRENT_BUILDS=5 # Concurrent build limit
-UNKEY_BUILDERD_BUILD_TIMEOUT=15m # Maximum build duration
-UNKEY_BUILDERD_SCRATCH_DIR=/tmp/builderd # Temporary build directory
-UNKEY_BUILDERD_ROOTFS_OUTPUT_DIR=/opt/builderd/rootfs # Output directory
-UNKEY_BUILDERD_WORKSPACE_DIR=/opt/builderd/workspace # Build workspace
-```
-
-### Storage Backend
-```bash
-UNKEY_BUILDERD_STORAGE_BACKEND=local # Backend type: local, s3, gcs
-UNKEY_BUILDERD_STORAGE_RETENTION_DAYS=30 # Artifact retention period
-UNKEY_BUILDERD_STORAGE_MAX_SIZE_GB=100 # Maximum storage size
-UNKEY_BUILDERD_STORAGE_CACHE_ENABLED=true # Enable build cache
-UNKEY_BUILDERD_STORAGE_CACHE_MAX_SIZE_GB=50 # Cache size limit
-```
-
-### Docker Registry
-```bash
-UNKEY_BUILDERD_DOCKER_REGISTRY_AUTH=true # Enable registry authentication
-UNKEY_BUILDERD_DOCKER_MAX_IMAGE_SIZE_GB=5 # Maximum image size
-UNKEY_BUILDERD_DOCKER_PULL_TIMEOUT=10m # Image pull timeout
-UNKEY_BUILDERD_DOCKER_REGISTRY_MIRROR="" # Optional registry mirror
-```
-
-### Multi-Tenancy
-```bash
-UNKEY_BUILDERD_TENANT_ISOLATION_ENABLED=true # Enable tenant isolation
-UNKEY_BUILDERD_TENANT_DEFAULT_TIER=free # Default service tier
-UNKEY_BUILDERD_TENANT_QUOTA_CHECK_INTERVAL=5m # Quota check frequency
-
-# Default resource limits
-UNKEY_BUILDERD_TENANT_DEFAULT_MAX_MEMORY_BYTES=2147483648 # 2GB
-UNKEY_BUILDERD_TENANT_DEFAULT_MAX_CPU_CORES=2
-UNKEY_BUILDERD_TENANT_DEFAULT_MAX_DISK_BYTES=10737418240 # 10GB
-UNKEY_BUILDERD_TENANT_DEFAULT_TIMEOUT_SECONDS=900 # 15min
-UNKEY_BUILDERD_TENANT_DEFAULT_MAX_CONCURRENT_BUILDS=3
-UNKEY_BUILDERD_TENANT_DEFAULT_MAX_DAILY_BUILDS=100
-```
-
-### AssetManagerd Integration
-```bash
-UNKEY_BUILDERD_ASSETMANAGER_ENABLED=true # Enable asset registration
-UNKEY_BUILDERD_ASSETMANAGER_ENDPOINT=https://localhost:8083 # AssetManagerd endpoint
-```
-
-### OpenTelemetry
-```bash
-UNKEY_BUILDERD_OTEL_ENABLED=false # Enable observability
-UNKEY_BUILDERD_OTEL_SERVICE_NAME=builderd # Service identifier
-UNKEY_BUILDERD_OTEL_ENDPOINT=localhost:4318 # OTLP endpoint
-UNKEY_BUILDERD_OTEL_SAMPLING_RATE=1.0 # Trace sampling rate
-UNKEY_BUILDERD_OTEL_PROMETHEUS_ENABLED=true # Enable metrics
-UNKEY_BUILDERD_OTEL_PROMETHEUS_PORT=9466 # Metrics port
-```
-
-### TLS/SPIFFE
-```bash
-UNKEY_BUILDERD_TLS_MODE=spiffe # TLS mode: disabled, file, spiffe
-UNKEY_BUILDERD_SPIFFE_SOCKET=/run/spire/sockets/agent.sock # SPIFFE socket
-```
-
-## Integration Examples
-
-### Creating a Build
-
-```go
-import (
- builderv1 "github.com/unkeyed/unkey/go/deploy/builderd/gen/builder/v1"
- "github.com/unkeyed/unkey/go/deploy/builderd/gen/builder/v1/builderv1connect"
-)
-
-// Create a Docker image build
-req := &builderv1.CreateBuildRequest{
- Config: &builderv1.BuildConfig{
- Tenant: &builderv1.TenantContext{
- TenantId: "example-tenant",
- CustomerId: "example-customer",
- Tier: builderv1.TenantTier_TENANT_TIER_PRO,
- },
- Source: &builderv1.BuildSource{
- SourceType: &builderv1.BuildSource_DockerImage{
- DockerImage: &builderv1.DockerImageSource{
- ImageUri: "ghcr.io/myorg/myapp:v1.0.0",
- },
- },
- },
- Target: &builderv1.BuildTarget{
- TargetType: &builderv1.BuildTarget_MicrovmRootfs{
- MicrovmRootfs: &builderv1.MicroVMRootfs{
- InitStrategy: builderv1.InitStrategy_INIT_STRATEGY_TINI,
- },
- },
- },
- Strategy: &builderv1.BuildStrategy{
- StrategyType: &builderv1.BuildStrategy_DockerExtract{
- DockerExtract: &builderv1.DockerExtractStrategy{
- FlattenFilesystem: true,
- },
- },
- },
- },
-}
-
-resp, err := client.CreateBuild(ctx, connect.NewRequest(req))
-if err != nil {
- log.Fatal(err)
-}
-
-fmt.Printf("Build started: %s\n", resp.Msg.BuildId)
-fmt.Printf("Rootfs will be at: %s\n", resp.Msg.RootfsPath)
-```
-
-### Monitoring Build Progress
-
-```go
-// Stream build logs
-stream, err := client.StreamBuildLogs(ctx, connect.NewRequest(&builderv1.StreamBuildLogsRequest{
- BuildId: buildId,
- TenantId: tenantId,
- Follow: true,
-}))
-
-for stream.Receive() {
- log := stream.Msg()
- fmt.Printf("[%s] %s: %s\n", log.Timestamp.AsTime(), log.Level, log.Message)
-}
-```
-
-## Version
-
-Current version: **0.1.0** ([proto definition](proto/builder/v1/builder.proto))
-
-## Related Documentation
-
-- [Service Pillar Overview](../docs/PILLAR_SERVICES.md)
-- [Multi-Tenant Architecture](../docs/architecture/multi-tenancy.md)
-- [SPIFFE/mTLS Setup](../docs/tls-implementation.md)
-- [Observability Guide](../docs/telemetry-migration-guide.md)
diff --git a/go/apps/builderd/client/Makefile b/go/apps/builderd/client/Makefile
deleted file mode 100644
index 79947be358..0000000000
--- a/go/apps/builderd/client/Makefile
+++ /dev/null
@@ -1,38 +0,0 @@
-# Makefile for builderd CLI client
-
-# Variables
-BINARY_NAME := builderd-cli
-BUILD_DIR := build
-VERSION ?= 0.5.1
-
-# Default target
-.DEFAULT_GOAL := help
-
-# Targets (alphabetically ordered)
-
-.PHONY: build
-build: ## Build the builderd CLI client
- @echo "Building $(BINARY_NAME)..."
- @mkdir -p $(BUILD_DIR)
- @go build -o $(BUILD_DIR)/$(BINARY_NAME) ../cmd/builderd-cli/main.go
- @echo "Build complete: $(BUILD_DIR)/$(BINARY_NAME)"
-
-.PHONY: clean
-clean: ## Clean build artifacts
- @echo "Cleaning..."
- @rm -rf $(BUILD_DIR)
-
-.PHONY: help
-help: ## Show this help message
- @echo "Available targets:"
- @echo " build - Build the builderd CLI client"
- @echo " clean - Clean build artifacts"
- @echo " install - Install the CLI client to /usr/local/bin"
- @echo " help - Show this help message"
-
-.PHONY: install
-install: build ## Install the CLI client to /usr/local/bin
- @echo "Installing $(BINARY_NAME) to /usr/local/bin..."
- @sudo mv $(BUILD_DIR)/$(BINARY_NAME) /usr/local/bin/$(BINARY_NAME)
- @sudo chmod +x /usr/local/bin/$(BINARY_NAME)
- @echo "Installation complete"
\ No newline at end of file
diff --git a/go/apps/builderd/client/client.go b/go/apps/builderd/client/client.go
deleted file mode 100644
index 055f75de82..0000000000
--- a/go/apps/builderd/client/client.go
+++ /dev/null
@@ -1,226 +0,0 @@
-package client
-
-import (
- "context"
- "fmt"
- "time"
-
- "connectrpc.com/connect"
- "github.com/unkeyed/unkey/go/deploy/pkg/tls"
- builderv1 "github.com/unkeyed/unkey/go/gen/proto/builderd/v1"
- "github.com/unkeyed/unkey/go/gen/proto/builderd/v1/builderdv1connect"
-)
-
-// AIDEV-NOTE: Builderd client with SPIFFE/SPIRE socket integration
-// This client provides a high-level interface for builderd operations with proper authentication
-
-// Config holds the configuration for the builderd client
-type Config struct {
- // ServerAddress is the builderd server endpoint (e.g., "https://builderd:8082")
- ServerAddress string
-
- // I do not understand what this userID is.
- // Is it an unkey user? or is it a linux user on the metal host?
- UserID string
-
- // TLS configuration
- TLSMode string // "disabled", "file", or "spiffe"
- SPIFFESocketPath string // Path to SPIFFE agent socket
- TLSCertFile string // TLS certificate file (for file mode)
- TLSKeyFile string // TLS key file (for file mode)
- TLSCAFile string // TLS CA file (for file mode)
- EnableCertCaching bool // Enable certificate caching
- CertCacheTTL time.Duration // Certificate cache TTL
-
- // Optional HTTP client timeout
- Timeout time.Duration
-}
-
-type Client struct {
- builderService builderdv1connect.BuilderServiceClient
- tlsProvider tls.Provider
- serverAddr string
-}
-
-// New creates a new builderd client with SPIFFE/SPIRE integration
-func New(ctx context.Context, config Config) (*Client, error) {
- // Set defaults
- if config.SPIFFESocketPath == "" {
- config.SPIFFESocketPath = "/var/lib/spire/agent/agent.sock"
- }
- if config.TLSMode == "" {
- config.TLSMode = "spiffe"
- }
- if config.Timeout == 0 {
- config.Timeout = 30 * time.Second
- }
- if config.CertCacheTTL == 0 {
- config.CertCacheTTL = 5 * time.Second
- }
-
- // Create TLS provider
- tlsConfig := tls.Config{
- Mode: tls.Mode(config.TLSMode),
- CertFile: config.TLSCertFile,
- KeyFile: config.TLSKeyFile,
- CAFile: config.TLSCAFile,
- SPIFFESocketPath: config.SPIFFESocketPath,
- EnableCertCaching: config.EnableCertCaching,
- CertCacheTTL: config.CertCacheTTL,
- }
-
- tlsProvider, err := tls.NewProvider(ctx, tlsConfig)
- if err != nil {
- return nil, fmt.Errorf("failed to create TLS provider: %w", err)
- }
-
- // Get HTTP client with SPIFFE mTLS
- httpClient := tlsProvider.HTTPClient()
- httpClient.Timeout = config.Timeout
-
- // Create ConnectRPC client
- builderService := builderdv1connect.NewBuilderServiceClient(
- httpClient,
- config.ServerAddress,
- )
-
- return &Client{
- builderService: builderService,
- tlsProvider: tlsProvider,
- serverAddr: config.ServerAddress,
- }, nil
-}
-
-// Close closes the client and cleans up resources
-func (c *Client) Close() error {
- if c.tlsProvider != nil {
- return c.tlsProvider.Close()
- }
- return nil
-}
-
-// CreateBuild creates a new build job
-func (c *Client) CreateBuild(ctx context.Context, req *CreateBuildRequest) (*CreateBuildResponse, error) {
- pbReq := &builderv1.CreateBuildRequest{
- Config: req.Config,
- }
-
- resp, err := c.builderService.CreateBuild(ctx, connect.NewRequest(pbReq))
- if err != nil {
- return nil, fmt.Errorf("failed to create build: %w", err)
- }
-
- return &CreateBuildResponse{
- BuildID: resp.Msg.BuildId,
- State: resp.Msg.State,
- CreatedAt: resp.Msg.CreatedAt,
- RootfsPath: resp.Msg.RootfsPath,
- }, nil
-}
-
-// GetBuild retrieves build status and progress
-func (c *Client) GetBuild(ctx context.Context, req *GetBuildRequest) (*GetBuildResponse, error) {
- pbReq := &builderv1.GetBuildRequest{
- BuildId: req.BuildID,
- }
-
- resp, err := c.builderService.GetBuild(ctx, connect.NewRequest(pbReq))
- if err != nil {
- return nil, fmt.Errorf("failed to get build: %w", err)
- }
-
- return &GetBuildResponse{
- Build: resp.Msg.Build,
- }, nil
-}
-
-// ListBuilds lists builds with filtering
-func (c *Client) ListBuilds(ctx context.Context, req *ListBuildsRequest) (*ListBuildsResponse, error) {
- pbReq := &builderv1.ListBuildsRequest{
- StateFilter: req.State,
- PageSize: req.PageSize,
- PageToken: req.PageToken,
- }
-
- resp, err := c.builderService.ListBuilds(ctx, connect.NewRequest(pbReq))
- if err != nil {
- return nil, fmt.Errorf("failed to list builds: %w", err)
- }
-
- return &ListBuildsResponse{
- Builds: resp.Msg.Builds,
- NextPageToken: resp.Msg.NextPageToken,
- TotalCount: resp.Msg.TotalCount,
- }, nil
-}
-
-// CancelBuild cancels a running build
-func (c *Client) CancelBuild(ctx context.Context, req *CancelBuildRequest) (*CancelBuildResponse, error) {
- pbReq := &builderv1.CancelBuildRequest{
- BuildId: req.BuildID,
- }
-
- resp, err := c.builderService.CancelBuild(ctx, connect.NewRequest(pbReq))
- if err != nil {
- return nil, fmt.Errorf("failed to cancel build: %w", err)
- }
-
- return &CancelBuildResponse{
- Success: resp.Msg.Success,
- State: resp.Msg.State,
- }, nil
-}
-
-// DeleteBuild deletes a build and its artifacts
-func (c *Client) DeleteBuild(ctx context.Context, req *DeleteBuildRequest) (*DeleteBuildResponse, error) {
- pbReq := &builderv1.DeleteBuildRequest{
- BuildId: req.BuildID,
- Force: req.Force,
- }
-
- resp, err := c.builderService.DeleteBuild(ctx, connect.NewRequest(pbReq))
- if err != nil {
- return nil, fmt.Errorf("failed to delete build: %w", err)
- }
-
- return &DeleteBuildResponse{
- Success: resp.Msg.Success,
- }, nil
-}
-
-// StreamBuildLogs streams build logs in real-time
-func (c *Client) StreamBuildLogs(ctx context.Context, req *StreamBuildLogsRequest) (*connect.ServerStreamForClient[builderv1.StreamBuildLogsResponse], error) {
- pbReq := &builderv1.StreamBuildLogsRequest{
- BuildId: req.BuildID,
- Follow: req.Follow,
- }
-
- stream, err := c.builderService.StreamBuildLogs(ctx, connect.NewRequest(pbReq))
- if err != nil {
- return nil, fmt.Errorf("failed to stream build logs: %w", err)
- }
-
- return stream, nil
-}
-
-// GetBuildStats retrieves build statistics
-func (c *Client) GetBuildStats(ctx context.Context, req *GetBuildStatsRequest) (*GetBuildStatsResponse, error) {
- pbReq := &builderv1.GetBuildStatsRequest{
- StartTime: req.StartTime,
- EndTime: req.EndTime,
- }
-
- resp, err := c.builderService.GetBuildStats(ctx, connect.NewRequest(pbReq))
- if err != nil {
- return nil, fmt.Errorf("failed to get build stats: %w", err)
- }
-
- return &GetBuildStatsResponse{
- Stats: resp.Msg,
- }, nil
-}
-
-// GetServerAddress returns the server address this client is connected to
-func (c *Client) GetServerAddress() string {
- return c.serverAddr
-}
diff --git a/go/apps/builderd/client/types.go b/go/apps/builderd/client/types.go
deleted file mode 100644
index 8000c7ec7b..0000000000
--- a/go/apps/builderd/client/types.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package client
-
-import (
- builderv1 "github.com/unkeyed/unkey/go/gen/proto/builderd/v1"
- "google.golang.org/protobuf/types/known/timestamppb"
-)
-
-// AIDEV-NOTE: Clean Go types wrapping builderd protobuf interfaces
-// These types provide a simplified interface while maintaining compatibility with the underlying protobuf structures
-
-// CreateBuildRequest wraps builderv1.CreateBuildRequest
-type CreateBuildRequest struct {
- Config *builderv1.BuildConfig
-}
-
-// CreateBuildResponse wraps builderv1.CreateBuildResponse
-type CreateBuildResponse struct {
- BuildID string
- State builderv1.BuildState
- CreatedAt *timestamppb.Timestamp
- RootfsPath string
-}
-
-// GetBuildRequest wraps builderv1.GetBuildRequest
-type GetBuildRequest struct {
- BuildID string
-}
-
-// GetBuildResponse wraps builderv1.GetBuildResponse
-type GetBuildResponse struct {
- Build *builderv1.BuildJob
-}
-
-// ListBuildsRequest wraps builderv1.ListBuildsRequest
-type ListBuildsRequest struct {
- State []builderv1.BuildState
- PageSize int32
- PageToken string
-}
-
-// ListBuildsResponse wraps builderv1.ListBuildsResponse
-type ListBuildsResponse struct {
- Builds []*builderv1.BuildJob
- NextPageToken string
- TotalCount int32
-}
-
-// CancelBuildRequest wraps builderv1.CancelBuildRequest
-type CancelBuildRequest struct {
- BuildID string
-}
-
-// CancelBuildResponse wraps builderv1.CancelBuildResponse
-type CancelBuildResponse struct {
- Success bool
- State builderv1.BuildState
-}
-
-// DeleteBuildRequest wraps builderv1.DeleteBuildRequest
-type DeleteBuildRequest struct {
- BuildID string
- Force bool
-}
-
-// DeleteBuildResponse wraps builderv1.DeleteBuildResponse
-type DeleteBuildResponse struct {
- Success bool
-}
-
-// StreamBuildLogsRequest wraps builderv1.StreamBuildLogsRequest
-type StreamBuildLogsRequest struct {
- BuildID string
- Follow bool
-}
-
-// GetBuildStatsRequest wraps builderv1.GetBuildStatsRequest
-type GetBuildStatsRequest struct {
- StartTime *timestamppb.Timestamp
- EndTime *timestamppb.Timestamp
-}
-
-// GetBuildStatsResponse wraps builderv1.GetBuildStatsResponse
-type GetBuildStatsResponse struct {
- Stats *builderv1.GetBuildStatsResponse
-}
diff --git a/go/apps/builderd/cmd/builderd-cli/main.go b/go/apps/builderd/cmd/builderd-cli/main.go
deleted file mode 100644
index 442f340aa5..0000000000
--- a/go/apps/builderd/cmd/builderd-cli/main.go
+++ /dev/null
@@ -1,373 +0,0 @@
-package main
-
-import (
- "context"
- "encoding/json"
- "flag"
- "fmt"
- "log"
- "os"
- "strconv"
- "time"
-
- "github.com/unkeyed/unkey/go/apps/builderd/client"
- builderv1 "github.com/unkeyed/unkey/go/gen/proto/builderd/v1"
- "google.golang.org/protobuf/types/known/timestamppb"
-)
-
-func main() {
- var (
- serverAddr = flag.String("server", getEnvOrDefault("UNKEY_BUILDERD_SERVER_ADDRESS", "https://localhost:8082"), "builderd server address")
- tlsMode = flag.String("tls-mode", getEnvOrDefault("UNKEY_BUILDERD_TLS_MODE", "spiffe"), "TLS mode: disabled, file, or spiffe")
- spiffeSocket = flag.String("spiffe-socket", getEnvOrDefault("UNKEY_BUILDERD_SPIFFE_SOCKET", "/var/lib/spire/agent/agent.sock"), "SPIFFE agent socket path")
- tlsCert = flag.String("tls-cert", "", "TLS certificate file (for file mode)")
- tlsKey = flag.String("tls-key", "", "TLS key file (for file mode)")
- tlsCA = flag.String("tls-ca", "", "TLS CA file (for file mode)")
- timeout = flag.Duration("timeout", 30*time.Second, "request timeout")
- jsonOutput = flag.Bool("json", false, "output results as JSON")
- )
- flag.Parse()
-
- if flag.NArg() == 0 {
- printUsage()
- os.Exit(1)
- }
-
- ctx := context.Background()
-
- // Create builderd client
- config := client.Config{
- ServerAddress: *serverAddr,
- TLSMode: *tlsMode,
- SPIFFESocketPath: *spiffeSocket,
- TLSCertFile: *tlsCert,
- TLSKeyFile: *tlsKey,
- TLSCAFile: *tlsCA,
- Timeout: *timeout,
- }
-
- builderClient, err := client.New(ctx, config)
- if err != nil {
- log.Fatalf("Failed to create builderd client: %v", err)
- }
- defer builderClient.Close()
-
- // Execute command
- command := flag.Arg(0)
- switch command {
- case "create-build":
- handleCreateBuild(ctx, builderClient, *jsonOutput)
- case "get-build":
- handleGetBuild(ctx, builderClient, *jsonOutput)
- case "list-builds":
- handleListBuilds(ctx, builderClient, *jsonOutput)
- case "cancel-build":
- handleCancelBuild(ctx, builderClient, *jsonOutput)
- case "delete-build":
- handleDeleteBuild(ctx, builderClient, *jsonOutput)
- case "stream-logs":
- handleStreamLogs(ctx, builderClient, *jsonOutput)
- case "get-stats":
- handleGetStats(ctx, builderClient, *jsonOutput)
- default:
- fmt.Fprintf(os.Stderr, "Unknown command: %s\n", command)
- printUsage()
- os.Exit(1)
- }
-}
-
-func printUsage() {
- fmt.Printf(`builderd-cli - CLI tool for builderd operations
-
-Usage: %s [flags] [args...]
-
-Commands:
- create-build Create a new build from Docker image
- get-build Get build status and details
- list-builds List builds
- cancel-build Cancel a running build
- delete-build Delete a build and its artifacts
- stream-logs Stream build logs in real-time
- get-stats Get build statistics
-
-Environment Variables:
- UNKEY_BUILDERD_SERVER_ADDRESS Server address (default: https://localhost:8082)
- UNKEY_BUILDERD_TLS_MODE TLS mode (default: spiffe)
- UNKEY_BUILDERD_SPIFFE_SOCKET SPIFFE socket path (default: /var/lib/spire/agent/agent.sock)
-
-Examples:
- # Create build from Docker image with SPIFFE authentication
- %s create-build ubuntu:latest
-
- # Get build status
- %s get-build build-12345
-
- # List builds
- %s list-builds
-
- # Stream build logs
- %s stream-logs build-12345
-
-
- # Get build statistics
- %s get-stats
-
- # Get response with JSON output
- %s get-build build-12345 -json
-
-`, os.Args[0], os.Args[0], os.Args[0], os.Args[0], os.Args[0], os.Args[0], os.Args[0])
-}
-
-func handleCreateBuild(ctx context.Context, builderClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("Image URI is required for create-build command")
- }
- imageURI := flag.Arg(1)
-
- // Create a basic Docker image build configuration
- config := &builderv1.BuildConfig{
- Source: &builderv1.BuildSource{
- SourceType: &builderv1.BuildSource_DockerImage{
- DockerImage: &builderv1.DockerImageSource{
- ImageUri: imageURI,
- },
- },
- },
- Target: &builderv1.BuildTarget{
- TargetType: &builderv1.BuildTarget_MicrovmRootfs{
- MicrovmRootfs: &builderv1.MicroVMRootfs{},
- },
- },
- Strategy: &builderv1.BuildStrategy{
- StrategyType: &builderv1.BuildStrategy_DockerExtract{
- DockerExtract: &builderv1.DockerExtractStrategy{
- FlattenFilesystem: true,
- },
- },
- },
- BuildName: fmt.Sprintf("cli-build-%d", time.Now().Unix()),
- }
-
- req := &client.CreateBuildRequest{
- Config: config,
- }
-
- resp, err := builderClient.CreateBuild(ctx, req)
- if err != nil {
- log.Fatalf("Failed to create build: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- fmt.Printf("Build created:\n")
- fmt.Printf(" Build ID: %s\n", resp.BuildID)
- fmt.Printf(" State: %s\n", resp.State.String())
- fmt.Printf(" Created at: %s\n", resp.CreatedAt.AsTime().Format(time.RFC3339))
- if resp.RootfsPath != "" {
- fmt.Printf(" Rootfs path: %s\n", resp.RootfsPath)
- }
- }
-}
-
-func handleGetBuild(ctx context.Context, builderClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("Build ID is required for get-build command")
- }
- buildID := flag.Arg(1)
-
- req := &client.GetBuildRequest{
- BuildID: buildID,
- }
-
- resp, err := builderClient.GetBuild(ctx, req)
- if err != nil {
- log.Fatalf("Failed to get build: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- build := resp.Build
- fmt.Printf("Build details:\n")
- fmt.Printf(" Build ID: %s\n", build.BuildId)
- fmt.Printf(" State: %s\n", build.State.String())
- fmt.Printf(" Created at: %s\n", build.CreatedAt.AsTime().Format(time.RFC3339))
- if build.CompletedAt != nil {
- fmt.Printf(" Completed at: %s\n", build.CompletedAt.AsTime().Format(time.RFC3339))
- }
- if build.RootfsPath != "" {
- fmt.Printf(" Rootfs path: %s\n", build.RootfsPath)
- }
- }
-}
-
-func handleListBuilds(ctx context.Context, builderClient *client.Client, jsonOutput bool) {
- req := &client.ListBuildsRequest{
- PageSize: 50,
- }
-
- resp, err := builderClient.ListBuilds(ctx, req)
- if err != nil {
- log.Fatalf("Failed to list builds: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- fmt.Printf("Builds (total: %d):\n", resp.TotalCount)
- for _, build := range resp.Builds {
- fmt.Printf(" %s: %s (created: %s)\n",
- build.BuildId,
- build.State.String(),
- build.CreatedAt.AsTime().Format(time.RFC3339))
- }
- }
-}
-
-func handleCancelBuild(ctx context.Context, builderClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("Build ID is required for cancel-build command")
- }
- buildID := flag.Arg(1)
-
- req := &client.CancelBuildRequest{
- BuildID: buildID,
- }
-
- resp, err := builderClient.CancelBuild(ctx, req)
- if err != nil {
- log.Fatalf("Failed to cancel build: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- fmt.Printf("Build cancellation:\n")
- fmt.Printf(" Build ID: %s\n", buildID)
- fmt.Printf(" Success: %v\n", resp.Success)
- fmt.Printf(" State: %s\n", resp.State.String())
- }
-}
-
-func handleDeleteBuild(ctx context.Context, builderClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("Build ID is required for delete-build command")
- }
- buildID := flag.Arg(1)
-
- // Check for force flag from additional args
- force := false
- if flag.NArg() > 2 && flag.Arg(2) == "--force" {
- force = true
- }
-
- req := &client.DeleteBuildRequest{
- BuildID: buildID,
- Force: force,
- }
-
- resp, err := builderClient.DeleteBuild(ctx, req)
- if err != nil {
- log.Fatalf("Failed to delete build: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- fmt.Printf("Build deletion:\n")
- fmt.Printf(" Build ID: %s\n", buildID)
- fmt.Printf(" Success: %v\n", resp.Success)
- fmt.Printf(" Force: %v\n", force)
- }
-}
-
-func handleStreamLogs(ctx context.Context, builderClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("Build ID is required for stream-logs command")
- }
- buildID := flag.Arg(1)
-
- req := &client.StreamBuildLogsRequest{
- BuildID: buildID,
- Follow: true,
- }
-
- stream, err := builderClient.StreamBuildLogs(ctx, req)
- if err != nil {
- log.Fatalf("Failed to stream build logs: %v", err)
- }
-
- fmt.Printf("Streaming logs for build %s (press Ctrl+C to stop):\n", buildID)
- fmt.Println("---")
-
- for stream.Receive() {
- msg := stream.Msg()
- timestamp := msg.Timestamp.AsTime().Format("15:04:05")
-
- if jsonOutput {
- outputJSON(msg)
- } else {
- fmt.Printf("[%s] %s: %s\n", timestamp, msg.Component, msg.Message)
- }
- }
-
- if err := stream.Err(); err != nil {
- log.Fatalf("Stream error: %v", err)
- }
-}
-
-func handleGetStats(ctx context.Context, builderClient *client.Client, jsonOutput bool) {
- // Default to last 24 hours
- endTime := time.Now()
- startTime := endTime.Add(-24 * time.Hour)
-
- // Allow custom time range from CLI args
- if flag.NArg() > 1 {
- if hours, err := strconv.Atoi(flag.Arg(1)); err == nil {
- startTime = endTime.Add(-time.Duration(hours) * time.Hour)
- }
- }
-
- req := &client.GetBuildStatsRequest{
- StartTime: timestamppb.New(startTime),
- EndTime: timestamppb.New(endTime),
- }
-
- resp, err := builderClient.GetBuildStats(ctx, req)
- if err != nil {
- log.Fatalf("Failed to get build stats: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- stats := resp.Stats
- duration := endTime.Sub(startTime)
- fmt.Printf("Build statistics (last %v):\n", duration)
- fmt.Printf(" Total builds: %d\n", stats.TotalBuilds)
- fmt.Printf(" Successful builds: %d\n", stats.SuccessfulBuilds)
- fmt.Printf(" Failed builds: %d\n", stats.FailedBuilds)
- fmt.Printf(" Average build time: %d ms\n", stats.AvgBuildTimeMs)
- fmt.Printf(" Total storage: %d bytes\n", stats.TotalStorageBytes)
- fmt.Printf(" Total compute minutes: %d\n", stats.TotalComputeMinutes)
- if len(stats.RecentBuilds) > 0 {
- fmt.Printf(" Recent builds: %d\n", len(stats.RecentBuilds))
- }
- }
-}
-
-func outputJSON(data interface{}) {
- encoder := json.NewEncoder(os.Stdout)
- encoder.SetIndent("", " ")
- if err := encoder.Encode(data); err != nil {
- log.Fatalf("Failed to encode JSON: %v", err)
- }
-}
-
-func getEnvOrDefault(key, defaultValue string) string {
- if value := os.Getenv(key); value != "" {
- return value
- }
- return defaultValue
-}
diff --git a/go/apps/builderd/cmd/builderd/main.go b/go/apps/builderd/cmd/builderd/main.go
deleted file mode 100644
index 5b7f666b5b..0000000000
--- a/go/apps/builderd/cmd/builderd/main.go
+++ /dev/null
@@ -1,667 +0,0 @@
-package main
-
-import (
- "context"
- "flag"
- "fmt"
- "log/slog"
- "net/http"
- "os"
- "os/signal"
- "runtime"
- "runtime/debug"
- "sync"
- "sync/atomic"
- "syscall"
- "time"
-
- "connectrpc.com/connect"
- "github.com/prometheus/client_golang/prometheus/promhttp"
- "github.com/unkeyed/unkey/go/apps/builderd/internal/assetmanager"
- "github.com/unkeyed/unkey/go/apps/builderd/internal/assets"
- "github.com/unkeyed/unkey/go/apps/builderd/internal/config"
- "github.com/unkeyed/unkey/go/apps/builderd/internal/observability"
- "github.com/unkeyed/unkey/go/apps/builderd/internal/service"
- healthpkg "github.com/unkeyed/unkey/go/deploy/pkg/health"
- "github.com/unkeyed/unkey/go/deploy/pkg/observability/interceptors"
- tlspkg "github.com/unkeyed/unkey/go/deploy/pkg/tls"
- "github.com/unkeyed/unkey/go/gen/proto/builderd/v1/builderdv1connect"
- "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
- "go.opentelemetry.io/otel"
- "golang.org/x/net/http2"
- "golang.org/x/net/http2/h2c"
- "golang.org/x/sync/errgroup"
- "golang.org/x/time/rate"
-)
-
-// version is set at build time via ldflags
-var version = ""
-
-// AIDEV-NOTE: Enhanced version management with debug.ReadBuildInfo fallback
-// Handles production builds (ldflags), development builds (git commit), and module builds
-// getVersion returns the version string, with fallback to debug.ReadBuildInfo
-func getVersion() string {
- // If version was set via ldflags (production builds), use it
- if version != "" {
- return version
- }
-
- // Fallback to debug.ReadBuildInfo for development/module builds
- if info, ok := debug.ReadBuildInfo(); ok {
- // Use the module version if available
- if info.Main.Version != "(devel)" && info.Main.Version != "" {
- return info.Main.Version
- }
-
- // Try to get version from VCS info
- for _, setting := range info.Settings {
- if setting.Key == "vcs.revision" && len(setting.Value) >= 7 {
- return "dev-" + setting.Value[:7] // First 7 chars of commit hash
- }
- }
-
- // Last resort: indicate it's a development build
- return "dev"
- }
-
- // Final fallback
- return version
-}
-
-func main() {
- // Track application start time for uptime calculations
- startTime := time.Now()
-
- // Create root context for coordinated shutdown
- rootCtx, rootCancel := context.WithCancel(context.Background())
- defer rootCancel()
-
- // Atomic state tracking for shutdown coordination
- var (
- shutdownStarted int64
- shutdownMutex sync.Mutex
- )
-
- // Parse command-line flags
- var (
- showHelp = flag.Bool("help", false, "Show help information")
- showVersion = flag.Bool("version", false, "Show version information")
- )
- flag.Parse()
-
- // Handle help and version flags
- if *showHelp {
- printUsage()
- os.Exit(0)
- }
-
- if *showVersion {
- printVersion()
- os.Exit(0)
- }
-
- // Initialize structured logger with JSON output
- //nolint:exhaustruct // Only Level field is needed for handler options
- logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
- Level: slog.LevelInfo,
- }))
- slog.SetDefault(logger)
-
- // Log startup
- logger.Info("starting builderd service",
- slog.String("version", getVersion()),
- slog.String("go_version", runtime.Version()),
- )
-
- // Load configuration
- cfg, err := config.LoadConfigWithLogger(logger)
- if err != nil {
- logger.Error("failed to load configuration",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
-
- // Configuration validation handled in LoadConfig
-
- logger.Info("configuration loaded",
- slog.String("address", cfg.Server.Address),
- slog.String("port", cfg.Server.Port),
- slog.String("storage_backend", cfg.Storage.Backend),
- slog.Bool("otel_enabled", cfg.OpenTelemetry.Enabled),
- slog.Int("max_concurrent_builds", cfg.Builder.MaxConcurrentBuilds),
- )
-
- // Initialize TLS provider (defaults to disabled)
- //nolint:exhaustruct // Only specified TLS fields are needed for this configuration
- tlsConfig := tlspkg.Config{
- Mode: tlspkg.Mode(cfg.TLS.Mode),
- CertFile: cfg.TLS.CertFile,
- KeyFile: cfg.TLS.KeyFile,
- CAFile: cfg.TLS.CAFile,
- SPIFFESocketPath: cfg.TLS.SPIFFESocketPath,
- }
- tlsProvider, err := tlspkg.NewProvider(rootCtx, tlsConfig)
- if err != nil {
- // AIDEV-NOTE: TLS/SPIFFE is now required - no fallback to disabled mode
- logger.Error("TLS initialization failed",
- "error", err,
- "mode", cfg.TLS.Mode)
- os.Exit(1)
- }
- defer tlsProvider.Close()
-
- logger.Info("TLS provider initialized",
- "mode", cfg.TLS.Mode,
- "spiffe_enabled", cfg.TLS.Mode == "spiffe")
-
- // Initialize OpenTelemetry with root context
- providers, err := observability.InitProviders(rootCtx, cfg, getVersion())
- if err != nil {
- logger.Error("failed to initialize OpenTelemetry",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
- // OpenTelemetry shutdown will be handled in coordinated shutdown
-
- if cfg.OpenTelemetry.Enabled {
- logger.Info("OpenTelemetry initialized",
- slog.String("service_name", cfg.OpenTelemetry.ServiceName),
- slog.String("service_version", cfg.OpenTelemetry.ServiceVersion),
- slog.Float64("sampling_rate", cfg.OpenTelemetry.TracingSamplingRate),
- slog.String("otlp_endpoint", cfg.OpenTelemetry.OTLPEndpoint),
- slog.Bool("prometheus_enabled", cfg.OpenTelemetry.PrometheusEnabled),
- slog.Bool("high_cardinality_enabled", cfg.OpenTelemetry.HighCardinalityLabelsEnabled),
- )
- }
-
- // Initialize build metrics if OpenTelemetry is enabled
- var buildMetrics *observability.BuildMetrics
- if cfg.OpenTelemetry.Enabled {
- buildMetrics, err = observability.NewBuildMetrics(logger, cfg.OpenTelemetry.HighCardinalityLabelsEnabled)
- if err != nil {
- logger.Warn("failed to initialize build metrics, entering degraded mode",
- slog.String("error", err.Error()),
- )
- // Continue without metrics rather than failing completely
- } else {
- logger.Info("build metrics initialized",
- slog.Bool("high_cardinality_enabled", cfg.OpenTelemetry.HighCardinalityLabelsEnabled),
- )
- }
- }
-
- // TODO: Initialize database
- // TODO: Initialize storage backend
- // TODO: Initialize Docker client
- // TODO: Initialize build executor registry
-
- // Initialize assetmanagerd client
- assetClient, err := assetmanager.NewClient(cfg, logger, tlsProvider)
- if err != nil {
- logger.Error("failed to initialize assetmanagerd client",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
-
- // Initialize base assets (kernel, rootfs) required for VM creation
- // AIDEV-NOTE: This ensures builderd can create VMs without external setup scripts
- if cfg.AssetManager.Enabled {
- logger.Info("initializing base VM assets")
- baseAssetInitCtx, cancel := context.WithTimeout(rootCtx, 10*time.Minute)
- defer cancel()
-
- // Use proper assets package with metrics and retry logic
- assetManager := assets.NewBaseAssetManager(logger, cfg, assetClient)
- if buildMetrics != nil {
- assetManager = assetManager.WithMetrics(buildMetrics)
- }
- if err := assetManager.InitializeBaseAssetsWithRetry(baseAssetInitCtx); err != nil {
- logger.Error("failed to initialize base assets",
- slog.String("error", err.Error()),
- )
- // Don't exit - continue with degraded functionality
- logger.Warn("continuing with degraded functionality - base assets may not be available")
- } else {
- logger.Info("base assets initialization completed")
- }
- }
-
- // Create builder service
- builderService := service.NewBuilderService(logger, buildMetrics, cfg, assetClient)
-
- // Configure shared interceptor options
- interceptorOpts := []interceptors.Option{
- interceptors.WithServiceName("builderd"),
- interceptors.WithLogger(logger),
- interceptors.WithActiveRequestsMetric(true),
- interceptors.WithRequestDurationMetric(false), // Match existing behavior
- interceptors.WithErrorResampling(true),
- interceptors.WithPanicStackTrace(true),
- }
-
- // Add meter if OpenTelemetry is enabled
- if cfg.OpenTelemetry.Enabled {
- interceptorOpts = append(interceptorOpts, interceptors.WithMeter(otel.Meter("builderd")))
- }
-
- // Get default interceptors (metrics, logging)
- sharedInterceptors := interceptors.NewDefaultInterceptors("builderd", interceptorOpts...)
-
- // Convert UnaryInterceptorFunc to Interceptor
- var interceptorList []connect.Interceptor
- for _, interceptor := range sharedInterceptors {
- interceptorList = append(interceptorList, connect.Interceptor(interceptor))
- }
-
- mux := http.NewServeMux()
- path, handler := builderdv1connect.NewBuilderServiceHandler(builderService,
- connect.WithInterceptors(interceptorList...),
- )
- mux.Handle(path, handler)
-
- // Create HTTP server address
- addr := cfg.Server.Address + ":" + cfg.Server.Port
-
- // Service health validation after initialization
- if err := validateServiceHealth(logger, cfg, buildMetrics); err != nil {
- logger.Error("service health validation failed",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
-
- // Wrap handler with OTEL HTTP middleware if enabled
- var httpHandler http.Handler = mux
- if cfg.OpenTelemetry.Enabled {
- httpHandler = otelhttp.NewHandler(mux, "http",
- otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string {
- return fmt.Sprintf("%s %s", r.Method, r.URL.Path)
- }),
- )
- }
-
- // Configure server with optional TLS and security timeouts
- server := &http.Server{
- Addr: addr,
- //nolint:exhaustruct // Default http2.Server configuration is sufficient
- Handler: h2c.NewHandler(httpHandler, &http2.Server{}),
- // AIDEV-NOTE: Security timeouts to prevent slowloris attacks
- ReadTimeout: 30 * time.Second, // Time to read request headers
- WriteTimeout: 30 * time.Second, // Time to write response
- IdleTimeout: 120 * time.Second, // Keep-alive timeout
- MaxHeaderBytes: 1 << 20, // 1MB max header size
- }
-
- // Apply TLS configuration if enabled
- serverTLSConfig, _ := tlsProvider.ServerTLSConfig()
- if serverTLSConfig != nil {
- server.TLSConfig = serverTLSConfig
- // For TLS, we need to use regular handler, not h2c
- server.Handler = httpHandler
- }
-
- // Use errgroup for coordinated goroutine management
- g, gCtx := errgroup.WithContext(rootCtx)
-
- // Start main server with proper error coordination
- g.Go(func() error {
- // AIDEV-NOTE: Start server with proper context cancellation to prevent startup goroutine deadlock
- errCh := make(chan error, 1)
-
- if serverTLSConfig != nil {
- logger.Info("starting HTTPS server with TLS",
- slog.String("address", addr),
- slog.String("tls_mode", cfg.TLS.Mode),
- )
- go func() {
- // Empty strings for cert/key paths - SPIFFE provides them in memory
- errCh <- server.ListenAndServeTLS("", "")
- }()
- } else {
- logger.Info("starting HTTP server without TLS",
- slog.String("address", addr),
- )
- go func() {
- errCh <- server.ListenAndServe()
- }()
- }
-
- select {
- case err := <-errCh:
- if err != nil && err != http.ErrServerClosed {
- return fmt.Errorf("server failed: %w", err)
- }
- return nil
- case <-gCtx.Done():
- // AIDEV-NOTE: Immediately shutdown server when context is cancelled to prevent deadlock
- shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
- if err := server.Shutdown(shutdownCtx); err != nil {
- logger.Warn("server shutdown during startup failed", slog.String("error", err.Error()))
- }
- return gCtx.Err()
- }
- })
-
- // Start Prometheus server on separate port if enabled
- var promServer *http.Server
- if cfg.OpenTelemetry.Enabled && cfg.OpenTelemetry.PrometheusEnabled {
- // AIDEV-NOTE: Use configured interface, defaulting to localhost for security
- promAddr := fmt.Sprintf("%s:%s", cfg.OpenTelemetry.PrometheusInterface, cfg.OpenTelemetry.PrometheusPort)
- promMux := http.NewServeMux()
- promMux.Handle("/metrics", promhttp.Handler())
- // Add rate-limited health check endpoint with unified handler
- healthHandler := newRateLimitedHandler(healthpkg.Handler("builderd", getVersion(), startTime), cfg.Server.RateLimit)
- promMux.Handle("/health", healthHandler)
-
- promServer = &http.Server{
- Addr: promAddr,
- Handler: promMux,
- ReadTimeout: 30 * time.Second,
- WriteTimeout: 30 * time.Second,
- IdleTimeout: 120 * time.Second,
- }
-
- g.Go(func() error {
- localhostOnly := cfg.OpenTelemetry.PrometheusInterface == "127.0.0.1" || cfg.OpenTelemetry.PrometheusInterface == "localhost"
- logger.Info("starting prometheus metrics server",
- slog.String("address", promAddr),
- slog.Bool("localhost_only", localhostOnly),
- )
-
- // AIDEV-NOTE: Start Prometheus server with context cancellation support
- errCh := make(chan error, 1)
- go func() {
- errCh <- promServer.ListenAndServe()
- }()
-
- select {
- case err := <-errCh:
- if err != nil && err != http.ErrServerClosed {
- return fmt.Errorf("prometheus server failed: %w", err)
- }
- return nil
- case <-gCtx.Done():
- // AIDEV-NOTE: Immediately shutdown Prometheus server when context is cancelled
- shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
- if err := promServer.Shutdown(shutdownCtx); err != nil {
- logger.Warn("prometheus server shutdown during startup failed", slog.String("error", err.Error()))
- }
- return gCtx.Err()
- }
- })
- }
-
- // Implement proper signal handling with buffered channel
- sigChan := make(chan os.Signal, 2) // Buffer for multiple signals
- signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
-
- // AIDEV-NOTE: Signal handling continues during graceful shutdown to prevent SIGABRT panics
- shutdownSignalReceived := make(chan struct{})
-
- // Handle shutdown coordination
- g.Go(func() error {
- select {
- case sig := <-sigChan:
- logger.Info("received shutdown signal",
- slog.String("signal", sig.String()),
- )
- close(shutdownSignalReceived)
- return fmt.Errorf("shutdown signal received: %s", sig)
- case <-gCtx.Done():
- return gCtx.Err()
- }
- })
-
- // Wait for any goroutine to complete/fail
- if err := g.Wait(); err != nil {
- logger.Info("initiating graceful shutdown",
- slog.String("reason", err.Error()),
- )
- }
-
- // Continue handling signals during graceful shutdown to prevent SIGABRT panics
- go func() {
- for {
- select {
- case <-shutdownSignalReceived:
- // Already shutting down, ignore
- return
- case sig := <-sigChan:
- logger.Warn("received additional signal during shutdown, ignoring",
- slog.String("signal", sig.String()),
- )
- // Continue listening for more signals
- }
- }
- }()
-
- // Coordinated shutdown with proper ordering
- performGracefulShutdown(logger, server, promServer, providers, builderService, &shutdownStarted, &shutdownMutex, cfg.Server.ShutdownTimeout)
-}
-
-// printUsage displays help information
-func printUsage() {
- fmt.Printf("Builderd - Multi-Tenant Build Service\n\n")
- fmt.Printf("Usage: %s [OPTIONS]\n\n", os.Args[0])
- fmt.Printf("Options:\n")
- flag.PrintDefaults()
- fmt.Printf("\nEnvironment Variables:\n")
- fmt.Printf(" UNKEY_BUILDERD_PORT Server port (default: 8082)\n")
- fmt.Printf(" UNKEY_BUILDERD_ADDRESS Bind address (default: 0.0.0.0)\n")
- fmt.Printf(" UNKEY_BUILDERD_SHUTDOWN_TIMEOUT Graceful shutdown timeout (default: 15s)\n")
- fmt.Printf(" UNKEY_BUILDERD_RATE_LIMIT Health endpoint rate limit/sec (default: 100)\n")
- fmt.Printf(" UNKEY_BUILDERD_MAX_CONCURRENT_BUILDS Max concurrent builds (default: 5)\n")
- fmt.Printf(" UNKEY_BUILDERD_BUILD_TIMEOUT Build timeout (default: 15m)\n")
- fmt.Printf(" UNKEY_BUILDERD_STORAGE_BACKEND Storage backend (local, s3, gcs)\n")
- fmt.Printf(" UNKEY_BUILDERD_STORAGE_RETENTION_DAYS Storage retention days (default: 30)\n")
- fmt.Printf(" UNKEY_BUILDERD_DOCKER_MAX_IMAGE_SIZE_GB Max Docker image size (default: 5)\n")
- fmt.Printf(" UNKEY_BUILDERD_TENANT_ISOLATION_ENABLED Enable tenant isolation (default: true)\n")
- fmt.Printf("\nDatabase Configuration:\n")
- fmt.Printf(" UNKEY_BUILDERD_DATABASE_TYPE Database type (default: sqlite)\n")
- fmt.Printf(" UNKEY_BUILDERD_DATABASE_DATA_DIR SQLite data directory (default: /opt/builderd/data)\n")
- fmt.Printf("\nOpenTelemetry Configuration:\n")
- fmt.Printf(" UNKEY_BUILDERD_OTEL_ENABLED Enable OpenTelemetry (default: false)\n")
- fmt.Printf(" UNKEY_BUILDERD_OTEL_SERVICE_NAME Service name (default: builderd)\n")
- fmt.Printf(" UNKEY_BUILDERD_OTEL_SERVICE_VERSION Service version (default: 0.1.0)\n")
- fmt.Printf(" UNKEY_BUILDERD_OTEL_SAMPLING_RATE Trace sampling rate 0.0-1.0 (default: 1.0)\n")
- fmt.Printf(" UNKEY_BUILDERD_OTEL_ENDPOINT OTLP endpoint (default: localhost:4318)\n")
- fmt.Printf(" UNKEY_BUILDERD_OTEL_PROMETHEUS_ENABLED Enable Prometheus metrics (default: true)\n")
- fmt.Printf(" UNKEY_BUILDERD_OTEL_PROMETHEUS_PORT Prometheus metrics port (default: 9466)\n")
- fmt.Printf(" UNKEY_BUILDERD_OTEL_PROMETHEUS_INTERFACE Prometheus binding interface (default: 127.0.0.1)\n")
- fmt.Printf(" UNKEY_BUILDERD_OTEL_HIGH_CARDINALITY_ENABLED Enable high-cardinality labels (default: false)\n")
- fmt.Printf("\nTLS Configuration:\n")
- fmt.Printf(" UNKEY_BUILDERD_TLS_MODE TLS mode: disabled, file, spiffe (default: disabled)\n")
- fmt.Printf(" UNKEY_BUILDERD_TLS_CERT_FILE Path to certificate file (file mode)\n")
- fmt.Printf(" UNKEY_BUILDERD_TLS_KEY_FILE Path to private key file (file mode)\n")
- fmt.Printf(" UNKEY_BUILDERD_TLS_CA_FILE Path to CA bundle file (file mode)\n")
- fmt.Printf(" UNKEY_BUILDERD_SPIFFE_SOCKET SPIFFE workload API socket (default: /run/spire/sockets/agent.sock)\n")
- fmt.Printf("\nDescription:\n")
- fmt.Printf(" Builderd processes various source types (Docker images, Git repositories,\n")
- fmt.Printf(" archives) and produces optimized rootfs images for microVM execution.\n")
- fmt.Printf(" It supports multi-tenant isolation, resource quotas, and comprehensive\n")
- fmt.Printf(" observability with OpenTelemetry.\n\n")
- fmt.Printf("Endpoints:\n")
- fmt.Printf(" /builder.v1.BuilderService/* - ConnectRPC builder service\n")
- fmt.Printf(" /health - Health check endpoint (rate limited)\n")
- fmt.Printf(" /metrics - Prometheus metrics (if enabled)\n\n")
- fmt.Printf("Examples:\n")
- fmt.Printf(" %s # Default settings (port 8082)\n", os.Args[0])
- fmt.Printf(" UNKEY_BUILDERD_OTEL_ENABLED=true %s # Enable telemetry\n", os.Args[0])
- fmt.Printf(" UNKEY_BUILDERD_MAX_CONCURRENT_BUILDS=10 %s # Allow 10 concurrent builds\n", os.Args[0])
-}
-
-// printVersion displays version information
-func printVersion() {
- fmt.Printf("Builderd - Multi-Tenant Build Service\n")
- fmt.Printf("Version: %s\n", getVersion())
- fmt.Printf("Built with: %s\n", runtime.Version())
-}
-
-// Rate-limited handler using token bucket algorithm for better efficiency
-type rateLimitedHandler struct {
- handler http.Handler
- limiter *rate.Limiter
-}
-
-func newRateLimitedHandler(handler http.Handler, rateLimit int) *rateLimitedHandler {
- // Allow burst of 10 requests, then limit to rateLimit per second
- return &rateLimitedHandler{
- handler: handler,
- limiter: rate.NewLimiter(rate.Limit(rateLimit), 10), // 10 request burst
- }
-}
-
-func (rl *rateLimitedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- if !rl.limiter.Allow() {
- // Rate limit exceeded
- w.Header().Set("Content-Type", "application/json")
- w.Header().Set("X-RateLimit-Limit", fmt.Sprintf("%v", rl.limiter.Limit()))
- w.Header().Set("Retry-After", "1")
- w.WriteHeader(http.StatusTooManyRequests)
- _, _ = w.Write([]byte(`{"error":"rate limit exceeded","status":429}`))
- return
- }
- rl.handler.ServeHTTP(w, r)
-}
-
-// AIDEV-NOTE: Health handler removed - using unified health package instead
-
-// Service health validation function
-func validateServiceHealth(logger *slog.Logger, cfg *config.Config, buildMetrics *observability.BuildMetrics) error {
- // Validate critical configuration
- if cfg.Server.Port == "" {
- return fmt.Errorf("server port not configured")
- }
-
- if cfg.Builder.MaxConcurrentBuilds <= 0 {
- return fmt.Errorf("invalid max concurrent builds: %d", cfg.Builder.MaxConcurrentBuilds)
- }
-
- if cfg.Server.ShutdownTimeout <= 0 {
- return fmt.Errorf("invalid shutdown timeout: %v", cfg.Server.ShutdownTimeout)
- }
-
- if cfg.Server.RateLimit <= 0 {
- return fmt.Errorf("invalid rate limit: %d", cfg.Server.RateLimit)
- }
-
- // Check if required directories are accessible
- requiredDirs := []string{
- cfg.Builder.ScratchDir,
- cfg.Builder.RootfsOutputDir,
- cfg.Builder.WorkspaceDir,
- }
-
- for _, dir := range requiredDirs {
- if err := os.MkdirAll(dir, 0o755); err != nil {
- return fmt.Errorf("cannot create/access directory %s: %w", dir, err)
- }
- }
-
- logger.Info("service health validation passed",
- slog.String("status", "healthy"),
- slog.Bool("metrics_available", buildMetrics != nil),
- slog.Int("rate_limit", cfg.Server.RateLimit),
- slog.Duration("shutdown_timeout", cfg.Server.ShutdownTimeout),
- )
-
- return nil
-}
-
-// Coordinated graceful shutdown function
-func performGracefulShutdown(logger *slog.Logger, server *http.Server, promServer *http.Server, providers *observability.Providers, builderService *service.BuilderService, shutdownStarted *int64, shutdownMutex *sync.Mutex, shutdownTimeout time.Duration) {
- // Ensure shutdown only happens once
- if !atomic.CompareAndSwapInt64(shutdownStarted, 0, 1) {
- logger.Warn("shutdown already in progress")
- return
- }
-
- logger.Info("attempting to acquire shutdown mutex")
- shutdownMutex.Lock()
- defer shutdownMutex.Unlock()
-
- logger.Info("acquired shutdown mutex, performing graceful shutdown")
-
- // Create shutdown context with configurable timeout
- // AIDEV-NOTE: Use a shorter timeout to avoid systemd SIGABRT
- actualTimeout := shutdownTimeout
- if actualTimeout > 12*time.Second {
- actualTimeout = 12 * time.Second // Leave 3s buffer before systemd timeout
- }
- shutdownCtx, cancel := context.WithTimeout(context.Background(), actualTimeout)
- defer cancel()
-
- logger.Info("starting graceful shutdown with timeout",
- slog.Duration("timeout", actualTimeout),
- )
-
- // Use errgroup for coordinated shutdown
- g, gCtx := errgroup.WithContext(shutdownCtx)
-
- // AIDEV-NOTE: Shutdown BuilderService first to stop new builds and wait for running ones
- if builderService != nil {
- g.Go(func() error {
- logger.Info("starting BuilderService shutdown")
- if err := builderService.Shutdown(gCtx); err != nil {
- logger.Error("BuilderService shutdown failed", slog.String("error", err.Error()))
- return fmt.Errorf("BuilderService shutdown failed: %w", err)
- }
- logger.Info("BuilderService shutdown complete")
- return nil
- })
- }
-
- // Shutdown HTTP server
- if server != nil {
- g.Go(func() error {
- logger.Info("starting HTTP server shutdown")
- if err := server.Shutdown(gCtx); err != nil {
- logger.Error("HTTP server shutdown failed", slog.String("error", err.Error()))
- return fmt.Errorf("HTTP server shutdown failed: %w", err)
- }
- logger.Info("HTTP server shutdown complete")
- return nil
- })
- }
-
- // Shutdown Prometheus server if running
- if promServer != nil {
- g.Go(func() error {
- logger.Info("starting Prometheus server shutdown")
- if err := promServer.Shutdown(gCtx); err != nil {
- logger.Error("Prometheus server shutdown failed", slog.String("error", err.Error()))
- return fmt.Errorf("prometheus server shutdown failed: %w", err)
- }
- logger.Info("Prometheus server shutdown complete")
- return nil
- })
- }
-
- // Shutdown OpenTelemetry providers
- if providers != nil {
- g.Go(func() error {
- logger.Info("starting OpenTelemetry providers shutdown")
- if err := providers.Shutdown(gCtx); err != nil {
- logger.Error("OpenTelemetry shutdown failed", slog.String("error", err.Error()))
- return fmt.Errorf("OpenTelemetry shutdown failed: %w", err)
- }
- logger.Info("OpenTelemetry shutdown complete")
- return nil
- })
- }
-
- // Wait for all shutdown operations to complete
- if err := g.Wait(); err != nil {
- logger.Error("graceful shutdown completed with errors",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
-
- logger.Info("graceful shutdown completed successfully")
-}
diff --git a/go/apps/builderd/cmd/builderd/shutdown_test.go b/go/apps/builderd/cmd/builderd/shutdown_test.go
deleted file mode 100644
index 2d071f8749..0000000000
--- a/go/apps/builderd/cmd/builderd/shutdown_test.go
+++ /dev/null
@@ -1,213 +0,0 @@
-package main
-
-import (
- "log/slog"
- "net/http"
- "os"
- "sync"
- "sync/atomic"
- "testing"
- "time"
-
- "github.com/unkeyed/unkey/go/apps/builderd/internal/config"
- "github.com/unkeyed/unkey/go/apps/builderd/internal/observability"
- "github.com/unkeyed/unkey/go/apps/builderd/internal/service"
-)
-
-// TestShutdownRace tests the graceful shutdown sequence for race conditions
-func TestShutdownRace(t *testing.T) {
- // Create a minimal logger for testing
- logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
- Level: slog.LevelDebug,
- }))
-
- // Create minimal config
- cfg := &config.Config{
- Server: config.ServerConfig{
- ShutdownTimeout: 5 * time.Second,
- },
- }
-
- // Create mock servers
- server := &http.Server{
- Addr: ":0", // Use any available port
- }
- promServer := &http.Server{
- Addr: ":0", // Use any available port
- }
-
- // Create minimal providers (can be nil for this test)
- var providers *observability.Providers
-
- // Create minimal builder service (can be nil for this test)
- var builderService *service.BuilderService
-
- // Shutdown coordination variables
- var shutdownStarted int64
- var shutdownMutex sync.Mutex
-
- // Test concurrent shutdown attempts to detect race conditions
- const numGoroutines = 10
- var wg sync.WaitGroup
-
- for i := 0; i < numGoroutines; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
-
- // Reset shutdown state before each attempt
- atomic.StoreInt64(&shutdownStarted, 0)
-
- // Call performGracefulShutdown concurrently
- performGracefulShutdown(
- logger.With("goroutine_id", id),
- server,
- promServer,
- providers,
- builderService,
- &shutdownStarted,
- &shutdownMutex,
- cfg.Server.ShutdownTimeout,
- )
- }(i)
- }
-
- // Wait for all goroutines to complete
- done := make(chan struct{})
- go func() {
- wg.Wait()
- close(done)
- }()
-
- // Set a reasonable timeout for the test
- select {
- case <-done:
- t.Log("All shutdown goroutines completed successfully")
- case <-time.After(10 * time.Second):
- t.Fatal("Test timed out - likely a deadlock or race condition")
- }
-}
-
-// TestShutdownSequence tests the shutdown sequence with realistic components
-func TestShutdownSequence(t *testing.T) {
- // Create a logger for testing
- logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
- Level: slog.LevelInfo,
- }))
-
- // Create config
- cfg := &config.Config{
- Server: config.ServerConfig{
- ShutdownTimeout: 2 * time.Second,
- },
- OpenTelemetry: config.OpenTelemetryConfig{
- Enabled: false, // Disable OTel to avoid external dependencies
- },
- }
-
- // Create HTTP servers
- mux := http.NewServeMux()
- mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusOK)
- _, _ = w.Write([]byte("OK"))
- })
-
- server := &http.Server{
- Addr: "127.0.0.1:0", // Use any available port
- Handler: mux,
- }
-
- promMux := http.NewServeMux()
- promMux.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusOK)
- _, _ = w.Write([]byte("# Metrics"))
- })
-
- promServer := &http.Server{
- Addr: "127.0.0.1:0", // Use any available port
- Handler: promMux,
- }
-
- // Start servers in background
- go func() {
- if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
- t.Logf("HTTP server error: %v", err)
- }
- }()
-
- go func() {
- if err := promServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
- t.Logf("Prometheus server error: %v", err)
- }
- }()
-
- // Give servers time to start
- time.Sleep(100 * time.Millisecond)
-
- // Test graceful shutdown
- var shutdownStarted int64
- var shutdownMutex sync.Mutex
-
- start := time.Now()
- performGracefulShutdown(
- logger,
- server,
- promServer,
- nil, // No OTel providers
- nil, // No builder service
- &shutdownStarted,
- &shutdownMutex,
- cfg.Server.ShutdownTimeout,
- )
- duration := time.Since(start)
-
- t.Logf("Shutdown completed in %v", duration)
-
- // Verify shutdown completed within reasonable time
- if duration > 5*time.Second {
- t.Errorf("Shutdown took too long: %v", duration)
- }
-}
-
-// TestShutdownTimeout tests that shutdown respects the configured timeout
-func TestShutdownTimeout(t *testing.T) {
- logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
- Level: slog.LevelInfo,
- }))
-
- // Create a server that will hang during shutdown
- hangingServer := &http.Server{
- Addr: "127.0.0.1:0",
- Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- // Simulate long-running request
- time.Sleep(10 * time.Second)
- w.WriteHeader(http.StatusOK)
- }),
- }
-
- // Very short timeout to test timeout behavior
- shortTimeout := 100 * time.Millisecond
-
- var shutdownStarted int64
- var shutdownMutex sync.Mutex
-
- start := time.Now()
- performGracefulShutdown(
- logger,
- hangingServer,
- nil, // No prom server
- nil, // No OTel providers
- nil, // No builder service
- &shutdownStarted,
- &shutdownMutex,
- shortTimeout,
- )
- duration := time.Since(start)
-
- t.Logf("Shutdown with timeout completed in %v", duration)
-
- // Should complete within a reasonable time even with hanging components
- if duration > 5*time.Second {
- t.Errorf("Shutdown took too long even with timeout: %v", duration)
- }
-}
diff --git a/go/apps/builderd/contrib/grafana-dashboards/README.md b/go/apps/builderd/contrib/grafana-dashboards/README.md
deleted file mode 100644
index aba651c5e7..0000000000
--- a/go/apps/builderd/contrib/grafana-dashboards/README.md
+++ /dev/null
@@ -1,408 +0,0 @@
-# Builderd Grafana Dashboards
-
-This directory contains Grafana dashboard definitions for monitoring builderd operations, performance, and health.
-
-## Dashboard Overview
-
-| Dashboard | Purpose | Audience |
-|-----------|---------|----------|
-| `builderd-overview.json` | High-level service metrics and health | Operations, SRE |
-| `builderd-builds.json` | Build execution and performance metrics | Developers, Operations |
-| `builderd-tenants.json` | Multi-tenant usage and quota monitoring | Account Management, Operations |
-| `builderd-infrastructure.json` | System resources and infrastructure health | SRE, Infrastructure |
-| `builderd-security.json` | Security events and isolation monitoring | Security, Operations |
-
-## Quick Setup
-
-### Import Dashboards
-
-```bash
-# Import all dashboards using Grafana API
-for dashboard in *.json; do
- curl -X POST \
- -H "Authorization: Bearer $GRAFANA_API_KEY" \
- -H "Content-Type: application/json" \
- -d @"$dashboard" \
- "$GRAFANA_URL/api/dashboards/db"
-done
-```
-
-### Configure Data Sources
-
-Ensure these data sources are configured in Grafana:
-
-1. **Prometheus** - Metrics collection
- ```yaml
- url: http://prometheus:9090
- access: proxy
- isDefault: true
- ```
-
-2. **Loki** - Log aggregation (optional)
- ```yaml
- url: http://loki:3100
- access: proxy
- ```
-
-3. **Jaeger** - Distributed tracing (optional)
- ```yaml
- url: http://jaeger:16686
- access: proxy
- ```
-
-## Dashboard Details
-
-### 1. Builderd Overview Dashboard
-
-**File**: `builderd-overview.json`
-
-**Key Metrics**:
-- Service uptime and availability
-- Build success rates and failure trends
-- Active builds and queue size
-- Resource utilization (CPU, Memory, Disk)
-- API response times and error rates
-
-**Panels**:
-```
-┌─────────────────┬─────────────────┬─────────────────┐
-│ Service Status │ Build Success │ Active Builds │
-│ 🟢 UP 99.9% │ Rate 97.2% │ Queue: 3 │
-│ │ │ Running: 12 │
-├─────────────────┴─────────────────┴─────────────────┤
-│ Build Duration Over Time │
-│ ▲ ████████████████████████████████████████████████ │
-├─────────────────┬─────────────────┬─────────────────┤
-│ CPU Usage │ Memory Usage │ Disk Usage │
-│ ████████ 72% │ ██████ 68% │ ████ 45% │
-└─────────────────┴─────────────────┴─────────────────┘
-```
-
-### 2. Builderd Builds Dashboard
-
-**File**: `builderd-builds.json`
-
-**Key Metrics**:
-- Build execution timeline
-- Build duration distribution
-- Failure analysis by source type
-- Build size and optimization metrics
-- Cache hit rates
-
-**Use Cases**:
-- Performance optimization
-- Build failure investigation
-- Capacity planning
-- SLA monitoring
-
-### 3. Builderd Tenants Dashboard
-
-**File**: `builderd-tenants.json`
-
-**Key Metrics**:
-- Per-tenant build usage
-- Quota utilization and violations
-- Storage usage by tenant
-- Tier-based resource consumption
-- Billing-relevant metrics
-
-**Use Cases**:
-- Account management
-- Quota planning
-- Billing verification
-- Tenant performance analysis
-
-### 4. Builderd Infrastructure Dashboard
-
-**File**: `builderd-infrastructure.json`
-
-**Key Metrics**:
-- Docker daemon health and performance
-- Storage backend performance
-- Network I/O and registry access
-- Database connection pool status
-- OpenTelemetry trace sampling
-
-**Use Cases**:
-- Infrastructure troubleshooting
-- Capacity planning
-- Performance optimization
-- Dependency monitoring
-
-### 5. Builderd Security Dashboard
-
-**File**: `builderd-security.json`
-
-**Key Metrics**:
-- Tenant isolation violations
-- Authentication and authorization events
-- Resource quota violations
-- Network policy violations
-- Audit log analysis
-
-**Use Cases**:
-- Security monitoring
-- Compliance reporting
-- Incident investigation
-- Policy enforcement
-
-## Metric Reference
-
-### Core Service Metrics
-
-```prometheus
-# Service health
-up{job="builderd"}
-builderd_info{version, commit}
-
-# Build metrics
-builderd_builds_total{status, tenant_id, source_type}
-builderd_builds_duration_seconds{tenant_id, source_type}
-builderd_builds_queue_size
-builderd_builds_concurrent{tenant_id}
-
-# Resource metrics
-builderd_memory_usage_bytes
-builderd_cpu_usage_percent
-builderd_disk_usage_bytes{path}
-builderd_disk_free_bytes{path}
-```
-
-### Tenant Metrics
-
-```prometheus
-# Quota usage
-builderd_tenant_quota_usage{tenant_id, quota_type}
-builderd_tenant_quota_limit{tenant_id, quota_type}
-builderd_tenant_quota_violations_total{tenant_id, quota_type}
-
-# Build metrics per tenant
-builderd_tenant_builds_total{tenant_id, status}
-builderd_tenant_builds_duration_seconds{tenant_id}
-builderd_tenant_storage_usage_bytes{tenant_id}
-```
-
-### Docker Metrics
-
-```prometheus
-# Docker operations
-builderd_docker_pulls_total{registry, tenant_id}
-builderd_docker_pull_duration_seconds{registry}
-builderd_docker_build_duration_seconds{tenant_id}
-builderd_docker_cache_hits_total{tenant_id}
-builderd_docker_cache_misses_total{tenant_id}
-```
-
-### Storage Metrics
-
-```prometheus
-# Storage operations
-builderd_storage_operations_total{operation, backend}
-builderd_storage_operation_duration_seconds{operation, backend}
-builderd_storage_errors_total{operation, backend}
-
-# Cache metrics
-builderd_cache_size_bytes{tenant_id, cache_type}
-builderd_cache_hit_ratio{tenant_id, cache_type}
-builderd_cache_evictions_total{tenant_id, cache_type}
-```
-
-## Alert Integration
-
-### Grafana Alerting
-
-Configure alerts within dashboards using Grafana's built-in alerting:
-
-```json
-{
- "alert": {
- "name": "High Build Failure Rate",
- "frequency": "1m",
- "conditions": [
- {
- "query": {
- "queryType": "A",
- "refId": "A"
- },
- "reducer": {
- "type": "avg"
- },
- "evaluator": {
- "params": [0.1],
- "type": "gt"
- }
- }
- ],
- "message": "Build failure rate is above 10% for the last 5 minutes",
- "noDataState": "no_data",
- "executionErrorState": "alerting"
- }
-}
-```
-
-### External Alert Manager
-
-Export alerts to external systems:
-
-```yaml
-# Prometheus AlertManager rules
-groups:
-- name: builderd-dashboards
- rules:
- - alert: DashboardBuildFailureRate
- expr: rate(builderd_builds_total{status="failed"}[5m]) > 0.1
- for: 5m
- labels:
- severity: warning
- dashboard: builderd-builds
- annotations:
- summary: "High build failure rate detected"
- grafana_url: "{{ $externalURL }}/d/builderd-builds"
-```
-
-## Customization
-
-### Variable Templates
-
-Add dynamic filtering using Grafana variables:
-
-```json
-{
- "templating": {
- "list": [
- {
- "name": "tenant_id",
- "type": "query",
- "query": "label_values(builderd_tenant_builds_total, tenant_id)",
- "refresh": "on_time_range_change",
- "includeAll": true,
- "allValue": ".*"
- },
- {
- "name": "time_range",
- "type": "interval",
- "query": "5m,15m,1h,6h,12h,1d",
- "auto": true,
- "auto_min": "5m"
- }
- ]
- }
-}
-```
-
-### Panel Customization
-
-Example panel configuration for build success rate:
-
-```json
-{
- "title": "Build Success Rate",
- "type": "stat",
- "targets": [
- {
- "expr": "rate(builderd_builds_total{status=\"completed\", tenant_id=~\"$tenant_id\"}[5m]) / rate(builderd_builds_total{tenant_id=~\"$tenant_id\"}[5m]) * 100",
- "legendFormat": "Success Rate"
- }
- ],
- "fieldConfig": {
- "defaults": {
- "unit": "percent",
- "min": 0,
- "max": 100,
- "thresholds": {
- "steps": [
- {"color": "red", "value": 0},
- {"color": "yellow", "value": 90},
- {"color": "green", "value": 95}
- ]
- }
- }
- }
-}
-```
-
-## Best Practices
-
-### Dashboard Design
-
-1. **Hierarchy**: Start with high-level overview, drill down to details
-2. **Time Ranges**: Use consistent time ranges across related panels
-3. **Color Coding**: Use consistent colors for status (green=good, red=bad)
-4. **Units**: Always specify appropriate units for metrics
-5. **Thresholds**: Set meaningful thresholds for status indicators
-
-### Performance
-
-1. **Query Optimization**: Use recording rules for complex queries
-2. **Time Series Limits**: Limit high-cardinality labels in queries
-3. **Refresh Rates**: Use appropriate refresh intervals (5-30 seconds)
-4. **Panel Count**: Limit dashboards to 20-30 panels for performance
-
-### Maintenance
-
-1. **Version Control**: Store dashboard JSON in source control
-2. **Automated Deployment**: Use infrastructure-as-code for dashboard deployment
-3. **Regular Review**: Review and update dashboards quarterly
-4. **User Feedback**: Collect feedback from dashboard users
-
-## Troubleshooting
-
-### Common Issues
-
-#### No Data in Panels
-
-```bash
-# Check if builderd is exposing metrics
-curl http://builderd:9090/metrics
-
-# Verify Prometheus can scrape builderd
-curl http://prometheus:9090/api/v1/label/__name__/values | grep builderd
-
-# Check Grafana data source configuration
-curl -H "Authorization: Bearer $GRAFANA_API_KEY" \
- http://grafana:3000/api/datasources
-```
-
-#### Dashboard Import Errors
-
-```bash
-# Validate JSON syntax
-cat builderd-overview.json | jq '.'
-
-# Check for missing data source UIDs
-grep -o '"datasource":{[^}]*}' builderd-overview.json
-```
-
-#### Performance Issues
-
-```bash
-# Check query performance in Prometheus
-curl -G http://prometheus:9090/api/v1/query \
- --data-urlencode 'query=rate(builderd_builds_total[5m])'
-
-# Review Grafana query inspector for slow panels
-# Access via panel menu -> Inspect -> Query
-```
-
-### Support
-
-For dashboard issues:
-1. Check the [main builderd documentation](../docs/README.md)
-2. Review Grafana documentation for panel configuration
-3. Verify Prometheus metrics are being exported correctly
-4. Check data source connectivity and permissions
-
-## Contributing
-
-To contribute new dashboards or improvements:
-
-1. Create dashboards in Grafana UI
-2. Export as JSON (`Share` -> `Export` -> `Save to file`)
-3. Remove data source UIDs and make generic
-4. Add appropriate documentation
-5. Test with clean Grafana instance
-6. Submit pull request with dashboard and documentation
-
-Dashboard naming convention: `builderd-{category}.json`
-
-Categories: `overview`, `builds`, `tenants`, `infrastructure`, `security`, `custom-{name}`
diff --git a/go/apps/builderd/contrib/systemd/README.md b/go/apps/builderd/contrib/systemd/README.md
deleted file mode 100644
index b72ffeeecc..0000000000
--- a/go/apps/builderd/contrib/systemd/README.md
+++ /dev/null
@@ -1,154 +0,0 @@
-# Builderd Systemd Integration
-
-This directory contains systemd service files and configuration for running builderd as a system service.
-
-## Files
-
-- `builderd.service`: Systemd unit file for the builderd service
-- `builderd.env.example`: Example environment configuration file
-
-## Installation
-
-### Manual Installation
-
-1. Copy the service file to systemd directory:
- ```bash
- sudo cp builderd.service /etc/systemd/system/
- ```
-
-2. Create builderd user and directories:
- ```bash
- sudo useradd -r -s /bin/false -d /opt/builderd builderd
- sudo mkdir -p /opt/builderd/{scratch,rootfs,workspace,data}
- sudo chown -R builderd:builderd /opt/builderd
- ```
-
-3. Install the builderd binary:
- ```bash
- sudo cp builderd /usr/local/bin/
- sudo chmod +x /usr/local/bin/builderd
- ```
-
-4. Configure environment (optional):
- ```bash
- sudo cp builderd.env.example /etc/default/builderd
- sudo nano /etc/default/builderd
- ```
-
-5. Enable and start the service:
- ```bash
- sudo systemctl daemon-reload
- sudo systemctl enable builderd
- sudo systemctl start builderd
- ```
-
-### Package Installation
-
-If installing via package manager (deb/rpm), the installation steps are handled automatically.
-
-## Service Management
-
-```bash
-# Start the service
-sudo systemctl start builderd
-
-# Stop the service
-sudo systemctl stop builderd
-
-# Restart the service
-sudo systemctl restart builderd
-
-# Check service status
-sudo systemctl status builderd
-
-# View logs
-sudo journalctl -u builderd -f
-
-# Enable auto-start on boot
-sudo systemctl enable builderd
-
-# Disable auto-start on boot
-sudo systemctl disable builderd
-```
-
-## Configuration
-
-The service can be configured through environment variables. The following methods are supported:
-
-1. **System environment file**: `/etc/default/builderd`
-2. **Service-specific environment**: Modify the `Environment=` lines in the service file
-3. **Runtime environment**: Set environment variables in the shell before starting
-
-### Key Configuration Options
-
-- `UNKEY_BUILDERD_PORT`: Service port (default: 8082)
-- `UNKEY_BUILDERD_MAX_CONCURRENT_BUILDS`: Maximum concurrent builds (default: 5)
-- `UNKEY_BUILDERD_OTEL_ENABLED`: Enable OpenTelemetry (default: true in systemd, false in development)
-- `UNKEY_BUILDERD_STORAGE_BACKEND`: Storage backend (local, s3, gcs)
-
-## Directory Structure
-
-The service expects the following directory structure:
-
-```
-/opt/builderd/
-├── scratch/ # Temporary build workspaces
-├── rootfs/ # Output rootfs images
-├── workspace/ # Build workspace directories
-└── data/ # Database and persistent data
-```
-
-## Security
-
-The service runs as the `builderd` user with limited privileges:
-
-- Non-login user account
-- Home directory: `/opt/builderd`
-- No shell access
-- Resource limits configured via systemd
-
-## Monitoring
-
-The service provides several monitoring endpoints:
-
-- `/health`: Health check endpoint
-- `/stats`: Service statistics (JSON)
-- `/metrics`: Prometheus metrics (if enabled)
-
-## Troubleshooting
-
-### Service won't start
-
-1. Check service status: `sudo systemctl status builderd`
-2. Check logs: `sudo journalctl -u builderd -n 50`
-3. Verify binary exists: `ls -la /usr/local/bin/builderd`
-4. Check permissions: `ls -la /opt/builderd`
-
-### Permission issues
-
-```bash
-# Fix ownership
-sudo chown -R builderd:builderd /opt/builderd
-
-# Fix permissions
-sudo chmod 755 /opt/builderd
-sudo chmod 755 /opt/builderd/{scratch,rootfs,workspace,data}
-```
-
-### Port conflicts
-
-Check if another service is using port 8082:
-```bash
-sudo netstat -tlnp | grep 8082
-```
-
-Change the port in the service file if needed:
-```bash
-sudo systemctl edit builderd
-```
-
-Add:
-```ini
-[Service]
-Environment=UNKEY_BUILDERD_PORT=8083
-```
diff --git a/go/apps/builderd/contrib/systemd/builderd.env.example b/go/apps/builderd/contrib/systemd/builderd.env.example
deleted file mode 100644
index f42cdeba26..0000000000
--- a/go/apps/builderd/contrib/systemd/builderd.env.example
+++ /dev/null
@@ -1,41 +0,0 @@
-# Builderd Multi-Tenant Build Service Configuration
-# Copy this file to /etc/default/builderd or /opt/builderd/builderd.env and modify as needed
-
-# Server Configuration
-UNKEY_BUILDERD_PORT=8082
-UNKEY_BUILDERD_ADDRESS=0.0.0.0
-
-# Build Configuration
-UNKEY_BUILDERD_MAX_CONCURRENT_BUILDS=5
-UNKEY_BUILDERD_BUILD_TIMEOUT=15m
-UNKEY_BUILDERD_SCRATCH_DIR=/opt/builderd/scratch
-UNKEY_BUILDERD_ROOTFS_OUTPUT_DIR=/opt/builderd/rootfs
-UNKEY_BUILDERD_WORKSPACE_DIR=/opt/builderd/workspace
-
-# Storage Configuration
-UNKEY_BUILDERD_STORAGE_BACKEND=local
-UNKEY_BUILDERD_STORAGE_RETENTION_DAYS=30
-
-# Database Configuration
-UNKEY_BUILDERD_DATABASE_TYPE=sqlite
-UNKEY_BUILDERD_DATABASE_DATA_DIR=/opt/builderd/data
-
-# Docker Configuration
-UNKEY_BUILDERD_DOCKER_MAX_IMAGE_SIZE_GB=5
-
-# Tenant Isolation
-UNKEY_BUILDERD_TENANT_ISOLATION_ENABLED=true
-
-# OpenTelemetry Configuration
-# Set to false for development environments
-UNKEY_BUILDERD_OTEL_ENABLED=false
-UNKEY_BUILDERD_OTEL_SERVICE_NAME=builderd
-UNKEY_BUILDERD_OTEL_SERVICE_VERSION=0.0.1
-UNKEY_BUILDERD_OTEL_SAMPLING_RATE=1.0
-UNKEY_BUILDERD_OTEL_ENDPOINT=localhost:4318
-UNKEY_BUILDERD_OTEL_PROMETHEUS_ENABLED=true
-UNKEY_BUILDERD_OTEL_HIGH_CARDINALITY_ENABLED=false
-
-# Development/Testing Options
-# UNKEY_BUILDERD_LOG_LEVEL=debug
-# UNKEY_BUILDERD_OTEL_SAMPLING_RATE=0.1
\ No newline at end of file
diff --git a/go/apps/builderd/contrib/systemd/builderd.service b/go/apps/builderd/contrib/systemd/builderd.service
deleted file mode 100644
index 3f2cf6e5af..0000000000
--- a/go/apps/builderd/contrib/systemd/builderd.service
+++ /dev/null
@@ -1,84 +0,0 @@
-[Unit]
-Description=Builderd Multi-Tenant Build Service
-Documentation=https://github.com/unkeyed/unkey/go/deploy/builderd
-After=network.target spire-agent.service assetmanagerd.service
-Wants=network.target
-Requires=spire-agent.service assetmanagerd.service
-
-[Service]
-Type=simple
-# Running as root for filesystem operations
-User=root
-Group=root
-# AIDEV-NOTE: WorkingDirectory removed - not needed for builderd
-# Create required directories (+ prefix runs as root)
-ExecStartPre=+/usr/bin/mkdir -p /opt/builderd/scratch
-ExecStartPre=+/usr/bin/mkdir -p /opt/builderd/rootfs
-ExecStartPre=+/usr/bin/mkdir -p /opt/builderd/workspace
-ExecStartPre=+/usr/bin/mkdir -p /opt/builderd/data
-ExecStartPre=+/usr/bin/mkdir -p /var/log/builderd
-# No ownership changes needed when running as root
-ExecStart=/usr/local/bin/builderd
-Restart=always
-RestartSec=5
-StandardOutput=journal
-StandardError=journal
-SyslogIdentifier=builderd
-
-# Core service configuration
-Environment=UNKEY_BUILDERD_PORT=8082
-Environment=UNKEY_BUILDERD_ADDRESS=0.0.0.0
-
-# Build configuration
-Environment=UNKEY_BUILDERD_MAX_CONCURRENT_BUILDS=5
-Environment=UNKEY_BUILDERD_BUILD_TIMEOUT=15m
-Environment=UNKEY_BUILDERD_SCRATCH_DIR=/opt/builderd/scratch
-Environment=UNKEY_BUILDERD_ROOTFS_OUTPUT_DIR=/opt/builderd/rootfs
-Environment=UNKEY_BUILDERD_WORKSPACE_DIR=/opt/builderd/workspace
-Environment=UNKEY_BUILDERD_USE_PIPELINE_EXECUTOR=false
-
-# Storage configuration
-Environment=UNKEY_BUILDERD_STORAGE_BACKEND=local
-Environment=UNKEY_BUILDERD_STORAGE_RETENTION_DAYS=30
-
-# Database configuration
-Environment=UNKEY_BUILDERD_DATABASE_TYPE=sqlite
-Environment=UNKEY_BUILDERD_DATABASE_DATA_DIR=/opt/builderd/data
-
-# Docker configuration
-Environment=UNKEY_BUILDERD_DOCKER_MAX_IMAGE_SIZE_GB=5
-
-# Tenant isolation
-Environment=UNKEY_BUILDERD_TENANT_ISOLATION_ENABLED=true
-
-# AssetManagerd integration
-Environment=UNKEY_BUILDERD_ASSETMANAGER_ENABLED=true
-Environment=UNKEY_BUILDERD_ASSETMANAGER_ENDPOINT=https://localhost:8083
-
-# OpenTelemetry Configuration (enabled for production)
-Environment=UNKEY_BUILDERD_OTEL_ENABLED=true
-Environment=UNKEY_BUILDERD_OTEL_SERVICE_NAME=builderd
-Environment=UNKEY_BUILDERD_OTEL_SERVICE_VERSION=0.0.1
-Environment=UNKEY_BUILDERD_OTEL_SAMPLING_RATE=1.0
-Environment=UNKEY_BUILDERD_OTEL_ENDPOINT=localhost:4318
-Environment=UNKEY_BUILDERD_OTEL_PROMETHEUS_ENABLED=true
-Environment=UNKEY_BUILDERD_OTEL_PROMETHEUS_PORT=9466
-Environment=UNKEY_BUILDERD_OTEL_PROMETHEUS_INTERFACE=127.0.0.1
-Environment=UNKEY_BUILDERD_OTEL_HIGH_CARDINALITY_ENABLED=false
-
-# TLS/SPIFFE configuration (REQUIRED)
-# AIDEV-BUSINESS_RULE: mTLS is required for secure inter-service communication
-Environment=UNKEY_BUILDERD_TLS_MODE=spiffe
-Environment=UNKEY_BUILDERD_SPIFFE_SOCKET=/var/lib/spire/agent/agent.sock
-
-# Resource limits
-LimitNOFILE=65536
-LimitNPROC=4096
-
-# Shutdown configuration
-# AIDEV-NOTE: Give builderd time to finish builds and shutdown gracefully
-TimeoutStopSec=30
-KillMode=mixed
-
-[Install]
-WantedBy=multi-user.target
diff --git a/go/apps/builderd/internal/assetmanager/client.go b/go/apps/builderd/internal/assetmanager/client.go
deleted file mode 100644
index fe03211bf2..0000000000
--- a/go/apps/builderd/internal/assetmanager/client.go
+++ /dev/null
@@ -1,180 +0,0 @@
-package assetmanager
-
-import (
- "context"
- "crypto/sha256"
- "fmt"
- "io"
- "log/slog"
- "os"
- "path/filepath"
-
- "github.com/unkeyed/unkey/go/apps/builderd/internal/config"
- "github.com/unkeyed/unkey/go/deploy/pkg/tls"
- assetv1 "github.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1"
- "github.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1/assetmanagerdv1connect"
- "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
-)
-
-// Client provides access to the assetmanagerd service
-type Client struct {
- client assetmanagerdv1connect.AssetManagerServiceClient
- logger *slog.Logger
- enabled bool
- endpoint string
-}
-
-// NewClient creates a new assetmanagerd client
-func NewClient(cfg *config.Config, logger *slog.Logger, tlsProvider tls.Provider) (*Client, error) {
- if !cfg.AssetManager.Enabled {
- logger.Info("assetmanagerd integration disabled")
- return &Client{
- client: nil,
- logger: logger,
- enabled: false,
- endpoint: "",
- }, nil
- }
-
- // Get HTTP client with TLS configuration
- httpClient := tlsProvider.HTTPClient()
-
- // Wrap with OpenTelemetry instrumentation for trace propagation
- httpClient.Transport = otelhttp.NewTransport(httpClient.Transport)
-
- // Create Connect client
- client := assetmanagerdv1connect.NewAssetManagerServiceClient(
- httpClient,
- cfg.AssetManager.Endpoint,
- )
-
- logger.Info("initialized assetmanagerd client",
- slog.String("endpoint", cfg.AssetManager.Endpoint),
- )
-
- return &Client{
- client: client,
- logger: logger,
- enabled: true,
- endpoint: cfg.AssetManager.Endpoint,
- }, nil
-}
-
-// RegisterBuildArtifact registers a successfully built artifact with assetmanagerd
-// AIDEV-NOTE: This is called after a successful build to make the artifact available for VM creation
-func (c *Client) RegisterBuildArtifact(ctx context.Context, buildID, artifactPath string, assetType assetv1.AssetType, labels map[string]string) (string, error) {
- return c.RegisterBuildArtifactWithID(ctx, buildID, artifactPath, assetType, labels, "")
-}
-
-// RegisterBuildArtifactWithID uploads and registers a successfully built artifact with a specific asset ID
-func (c *Client) RegisterBuildArtifactWithID(ctx context.Context, buildID, artifactPath string, assetType assetv1.AssetType, labels map[string]string, assetID string) (string, error) {
- if !c.enabled {
- c.logger.DebugContext(ctx, "assetmanagerd integration disabled, skipping artifact registration")
- return "", nil
- }
-
- // Get file info
- fileInfo, err := os.Stat(artifactPath)
- if err != nil {
- return "", fmt.Errorf("failed to stat artifact file: %w", err)
- }
-
- // Prepare labels
- if labels == nil {
- labels = make(map[string]string)
- }
- labels["build_id"] = buildID
- labels["created_by"] = "builderd"
-
- // Create upload metadata
- metadata := &assetv1.UploadAssetMetadata{
- Name: filepath.Base(artifactPath),
- Type: assetType,
- SizeBytes: fileInfo.Size(),
- Labels: labels,
- CreatedBy: "builderd",
- BuildId: buildID,
- SourceImage: labels["docker_image"], // Optional, from build metadata
- Id: assetID, // Optional, use pre-generated ID if provided
- }
-
- // Upload asset via streaming API
- // AIDEV-NOTE: This properly uploads the file to assetmanagerd's storage and registers it
- stream := c.client.UploadAsset(ctx)
-
- // Send metadata first
- metadataReq := &assetv1.UploadAssetRequest{
- Data: &assetv1.UploadAssetRequest_Metadata{
- Metadata: metadata,
- },
- }
- if err := stream.Send(metadataReq); err != nil {
- return "", fmt.Errorf("failed to send metadata: %w", err)
- }
-
- // Open file for streaming
- file, err := os.Open(artifactPath)
- if err != nil {
- return "", fmt.Errorf("failed to open artifact file: %w", err)
- }
- defer file.Close()
-
- // Stream file in chunks
- const chunkSize = 64 * 1024 // 64KB chunks
- buffer := make([]byte, chunkSize)
-
- for {
- n, err := file.Read(buffer)
- if err != nil && err != io.EOF {
- return "", fmt.Errorf("failed to read file chunk: %w", err)
- }
- if n == 0 {
- break
- }
-
- chunkReq := &assetv1.UploadAssetRequest{
- Data: &assetv1.UploadAssetRequest_Chunk{
- Chunk: buffer[:n],
- },
- }
- if err := stream.Send(chunkReq); err != nil {
- return "", fmt.Errorf("failed to send chunk: %w", err)
- }
- }
-
- // Close and receive response
- resp, err := stream.CloseAndReceive()
- if err != nil {
- return "", fmt.Errorf("failed to upload asset: %w", err)
- }
-
- c.logger.InfoContext(ctx, "uploaded and registered build artifact with assetmanagerd",
- slog.String("asset_id", resp.Msg.GetAsset().GetId()),
- slog.String("build_id", buildID),
- slog.String("artifact_path", artifactPath),
- slog.String("asset_type", assetType.String()),
- )
-
- return resp.Msg.GetAsset().GetId(), nil
-}
-
-// calculateChecksum calculates SHA256 checksum of a file
-func (c *Client) calculateChecksum(path string) (string, error) {
- file, err := os.Open(path)
- if err != nil {
- return "", err
- }
- defer file.Close()
-
- hash := sha256.New()
- if _, err := io.Copy(hash, file); err != nil {
- return "", err
- }
-
- return fmt.Sprintf("%x", hash.Sum(nil)), nil
-}
-
-// IsEnabled returns whether assetmanagerd integration is enabled
-func (c *Client) IsEnabled() bool {
- return c.enabled
-}
diff --git a/go/apps/builderd/internal/assets/base.go b/go/apps/builderd/internal/assets/base.go
deleted file mode 100644
index ba7596a380..0000000000
--- a/go/apps/builderd/internal/assets/base.go
+++ /dev/null
@@ -1,336 +0,0 @@
-package assets
-
-import (
- "context"
- "crypto/sha256"
- "fmt"
- "io"
- "log/slog"
- "maps"
- "net/http"
- "os"
- "path/filepath"
- "strings"
- "time"
-
- "github.com/unkeyed/unkey/go/apps/builderd/internal/assetmanager"
- "github.com/unkeyed/unkey/go/apps/builderd/internal/config"
- assetv1 "github.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1"
-)
-
-// BaseAssetManager handles initialization and registration of base VM assets
-type BaseAssetManager struct {
- logger *slog.Logger
- config *config.Config
- assetClient *assetmanager.Client
- storageDir string
- metrics MetricsRecorder
-}
-
-// MetricsRecorder interface for recording asset initialization metrics
-type MetricsRecorder interface {
- RecordBaseAssetInitRetry(ctx context.Context, attempt int, reason string)
- RecordBaseAssetInitFailure(ctx context.Context, totalAttempts int, finalError string)
-}
-
-// BaseAsset represents a base asset that needs to be downloaded and registered
-type BaseAsset struct {
- Name string
- URL string
- Type assetv1.AssetType
- Description string
- Labels map[string]string
-}
-
-// NewBaseAssetManager creates a new base asset manager
-func NewBaseAssetManager(logger *slog.Logger, cfg *config.Config, assetClient *assetmanager.Client) *BaseAssetManager {
- return &BaseAssetManager{
- logger: logger.With("component", "base-asset-manager"),
- config: cfg,
- assetClient: assetClient,
- storageDir: cfg.Builder.RootfsOutputDir,
- metrics: nil, // No metrics by default
- }
-}
-
-// WithMetrics adds metrics recording to the asset manager
-func (m *BaseAssetManager) WithMetrics(metrics MetricsRecorder) *BaseAssetManager {
- m.metrics = metrics
- return m
-}
-
-// InitializeBaseAssetsWithRetry ensures all required base assets are available with retry logic
-func (m *BaseAssetManager) InitializeBaseAssetsWithRetry(ctx context.Context) error {
- maxRetries := 8 // ~4-ish minutes total with exponential backoff: 1s, 2s, 4s, 8s, 16s, 32s, 64s, 128s
-
- for attempt := range maxRetries {
- if attempt > 0 {
- delay := time.Duration(1< 0 {
- m.logger.InfoContext(ctx, "base asset initialization succeeded after retries",
- "successful_attempt", attempt+1,
- )
- }
- return nil // Success
- }
-
- // Log the error
- m.logger.WarnContext(ctx, "base asset initialization attempt failed",
- "attempt", attempt+1,
- "max_retries", maxRetries,
- "error", err,
- )
-
- // Record retry metric if metrics are available
- if m.metrics != nil && attempt > 0 {
- m.metrics.RecordBaseAssetInitRetry(ctx, attempt, err.Error())
- }
-
- // Don't retry if it's the last attempt
- if attempt == maxRetries-1 {
- // Record final failure metric if metrics are available
- if m.metrics != nil {
- m.metrics.RecordBaseAssetInitFailure(ctx, maxRetries, err.Error())
- }
- return fmt.Errorf("failed to initialize base assets after %d attempts: %w", maxRetries, err)
- }
- }
-
- return nil // Should never reach here
-}
-
-// InitializeBaseAssets ensures all required base assets are available
-func (m *BaseAssetManager) InitializeBaseAssets(ctx context.Context) error {
- // AIDEV-NOTE: Base assets required for VM creation
- // These are downloaded from Firecracker quickstart guide if not already available
- baseAssets := []BaseAsset{
- {
- Name: "vmlinux",
- URL: "https://s3.amazonaws.com/spec.ccfc.min/img/quickstart_guide/x86_64/kernels/vmlinux.bin",
- Type: assetv1.AssetType_ASSET_TYPE_KERNEL,
- Description: "Firecracker x86_64 kernel",
- Labels: map[string]string{
- "architecture": "x86_64",
- "source": "firecracker-quickstart",
- "asset_type": "kernel",
- },
- },
- {
- Name: "rootfs.ext4",
- URL: "https://s3.amazonaws.com/spec.ccfc.min/img/quickstart_guide/x86_64/rootfs/bionic.rootfs.ext4",
- Type: assetv1.AssetType_ASSET_TYPE_ROOTFS,
- Description: "Ubuntu Bionic base rootfs",
- Labels: map[string]string{
- "architecture": "x86_64",
- "source": "firecracker-quickstart",
- "asset_type": "rootfs",
- "os": "ubuntu",
- "version": "bionic",
- },
- },
- }
-
- for _, asset := range baseAssets {
- if err := m.ensureAssetAvailable(ctx, asset); err != nil {
- return fmt.Errorf("failed to ensure asset %s is available: %w", asset.Name, err)
- }
- }
-
- m.logger.InfoContext(ctx, "base assets initialization completed")
- return nil
-}
-
-// ensureAssetAvailable checks if an asset exists and is registered, downloads and registers if needed
-func (m *BaseAssetManager) ensureAssetAvailable(ctx context.Context, asset BaseAsset) error {
- // Check if asset is already registered
- if m.assetClient != nil {
- exists, err := m.checkAssetRegistered(ctx, asset)
- if err != nil {
- m.logger.WarnContext(ctx, "failed to check asset registration, proceeding with download",
- "asset", asset.Name,
- "error", err,
- )
- } else if exists {
- m.logger.InfoContext(ctx, "asset already registered",
- "asset", asset.Name,
- )
- return nil
- }
- }
-
- // Download asset if not present locally
- localPath := filepath.Join(m.storageDir, "base", asset.Name)
- if err := m.downloadAsset(ctx, asset, localPath); err != nil {
- return fmt.Errorf("failed to download asset: %w", err)
- }
-
- // Register with assetmanagerd if enabled
- if m.assetClient != nil {
- if err := m.registerAsset(ctx, asset, localPath); err != nil {
- return fmt.Errorf("failed to register asset: %w", err)
- }
- }
-
- return nil
-}
-
-// checkAssetRegistered checks if an asset is already registered in assetmanagerd
-func (m *BaseAssetManager) checkAssetRegistered(ctx context.Context, asset BaseAsset) (bool, error) {
- // TODO: Implement asset query to check if base asset already exists
- // For now, return false to always download/register
- return false, nil
-}
-
-// downloadAsset downloads an asset from URL to local path
-func (m *BaseAssetManager) downloadAsset(ctx context.Context, asset BaseAsset, localPath string) error {
- // Create directory if it doesn't exist
- if err := os.MkdirAll(filepath.Dir(localPath), 0755); err != nil {
- return fmt.Errorf("failed to create directory: %w", err)
- }
-
- // Check if file already exists
- if _, err := os.Stat(localPath); err == nil {
- m.logger.InfoContext(ctx, "asset already exists locally",
- "asset", asset.Name,
- "path", localPath,
- )
- return nil
- }
-
- m.logger.InfoContext(ctx, "downloading asset",
- "asset", asset.Name,
- "url", asset.URL,
- "path", localPath,
- )
-
- // Download with context
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, asset.URL, nil)
- if err != nil {
- return fmt.Errorf("failed to create request: %w", err)
- }
-
- // TODO: replace with shared configured http client
- resp, err := http.DefaultClient.Do(req)
- if err != nil {
- return fmt.Errorf("failed to download asset: %w", err)
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return fmt.Errorf("failed to download asset: HTTP %d", resp.StatusCode)
- }
-
- // Create temporary file
- tmpPath := localPath + ".tmp"
- tmpFile, err := os.Create(tmpPath)
- if err != nil {
- return fmt.Errorf("failed to create temporary file: %w", err)
- }
- defer os.Remove(tmpPath)
-
- written, err := io.Copy(tmpFile, resp.Body)
- if err != nil {
- tmpFile.Close()
- return fmt.Errorf("failed to write asset: %w", err)
- }
- tmpFile.Close()
-
- // Atomic rename
- if err := os.Rename(tmpPath, localPath); err != nil {
- return fmt.Errorf("failed to finalize asset: %w", err)
- }
-
- m.logger.InfoContext(ctx, "asset downloaded successfully",
- "asset", asset.Name,
- "size_bytes", written,
- "path", localPath,
- )
-
- return nil
-}
-
-// registerAsset registers an asset with assetmanagerd
-func (m *BaseAssetManager) registerAsset(ctx context.Context, asset BaseAsset, localPath string) error {
- // Get file info
- fileInfo, err := os.Stat(localPath)
- if err != nil {
- return fmt.Errorf("failed to stat asset file: %w", err)
- }
-
- // Calculate checksum
- checksum, err := m.calculateChecksum(localPath)
- if err != nil {
- return fmt.Errorf("failed to calculate checksum: %w", err)
- }
-
- // Prepare labels
- labels := make(map[string]string)
- maps.Copy(labels, asset.Labels)
-
- labels["created_by"] = "builderd"
-
- // Get relative path within storage directory
- relPath, err := filepath.Rel(m.storageDir, localPath)
- if err != nil {
- return fmt.Errorf("failed to get relative path: %w", err)
- }
-
- // Register via assetmanager client
- assetID, err := m.assetClient.RegisterBuildArtifact(ctx, "base-assets", localPath, asset.Type, labels)
- if err != nil {
- // Already exists errors are fine, connection errors should cause retry
- errStr := strings.ToLower(err.Error())
- if strings.Contains(errStr, "already exists") || strings.Contains(errStr, "duplicate") ||
- strings.Contains(errStr, "conflict") {
- m.logger.InfoContext(ctx, "base asset already registered, skipping",
- "asset", asset.Name,
- "error", err,
- )
- return nil // Success - asset already registered
- } else {
- // This is likely a connection/service unavailable error - should trigger retry
- return fmt.Errorf("failed to register base asset %s (service may not be ready): %w", asset.Name, err)
- }
- }
-
- m.logger.InfoContext(ctx, "asset registered successfully",
- "asset", asset.Name,
- "asset_id", assetID,
- "location", relPath,
- "size_bytes", fileInfo.Size(),
- "checksum", checksum,
- )
-
- return nil
-}
-
-// calculateChecksum calculates SHA256 checksum of a file
-func (m *BaseAssetManager) calculateChecksum(filePath string) (string, error) {
- file, err := os.Open(filePath)
- if err != nil {
- return "", err
- }
- defer file.Close()
-
- hash := sha256.New()
- if _, err := io.Copy(hash, file); err != nil {
- return "", err
- }
-
- return fmt.Sprintf("%x", hash.Sum(nil)), nil
-}
diff --git a/go/apps/builderd/internal/config/config.go b/go/apps/builderd/internal/config/config.go
deleted file mode 100644
index 89189e56f1..0000000000
--- a/go/apps/builderd/internal/config/config.go
+++ /dev/null
@@ -1,317 +0,0 @@
-package config
-
-import (
- "fmt"
- "log/slog"
- "os"
- "strconv"
- "time"
-)
-
-// Config holds the complete builderd configuration
-type Config struct {
- Server ServerConfig `yaml:"server"`
- Builder BuilderConfig `yaml:"builder"`
- Storage StorageConfig `yaml:"storage"`
- Docker DockerConfig `yaml:"docker"`
- Database DatabaseConfig `yaml:"database"`
- OpenTelemetry OpenTelemetryConfig `yaml:"opentelemetry"`
- TLS *TLSConfig `yaml:"tls,omitempty"`
- AssetManager AssetManagerConfig `yaml:"assetmanager"`
-}
-
-// ServerConfig holds HTTP server configuration
-type ServerConfig struct {
- Address string `yaml:"address"`
- Port string `yaml:"port"`
- ShutdownTimeout time.Duration `yaml:"shutdown_timeout"`
- RateLimit int `yaml:"rate_limit"` // Requests per second for health endpoint
-}
-
-// BuilderConfig holds build execution configuration
-type BuilderConfig struct {
- MaxConcurrentBuilds int `yaml:"max_concurrent_builds"`
- BuildTimeout time.Duration `yaml:"build_timeout"`
- ScratchDir string `yaml:"scratch_dir"`
- RootfsOutputDir string `yaml:"rootfs_output_dir"`
- WorkspaceDir string `yaml:"workspace_dir"`
- CleanupInterval time.Duration `yaml:"cleanup_interval"`
- UsePipelineExecutor bool `yaml:"use_pipeline_executor"` // Feature flag for step-based execution
-}
-
-// StorageConfig holds storage backend configuration
-type StorageConfig struct {
- Backend string `yaml:"backend"` // "local", "s3", "gcs"
- RetentionDays int `yaml:"retention_days"`
- MaxSizeGB int `yaml:"max_size_gb"`
- CacheEnabled bool `yaml:"cache_enabled"`
- CacheMaxSizeGB int `yaml:"cache_max_size_gb"`
- S3Config S3Config `yaml:"s3,omitempty"`
- GCSConfig GCSConfig `yaml:"gcs,omitempty"`
-}
-
-// S3Config holds S3 storage configuration
-type S3Config struct {
- Bucket string `yaml:"bucket"`
- Region string `yaml:"region"`
- AccessKey string `yaml:"access_key"`
- SecretKey string `yaml:"secret_key"`
- Endpoint string `yaml:"endpoint,omitempty"` // For S3-compatible services
-}
-
-// GCSConfig holds Google Cloud Storage configuration
-type GCSConfig struct {
- Bucket string `yaml:"bucket"`
- Project string `yaml:"project"`
- CredentialsPath string `yaml:"credentials_path"`
-}
-
-// DockerConfig holds Docker-related configuration
-type DockerConfig struct {
- RegistryAuth bool `yaml:"registry_auth"`
- MaxImageSizeGB int `yaml:"max_image_size_gb"`
- AllowedRegistries []string `yaml:"allowed_registries"`
- PullTimeout time.Duration `yaml:"pull_timeout"`
- RegistryMirror string `yaml:"registry_mirror,omitempty"`
- InsecureRegistries []string `yaml:"insecure_registries,omitempty"`
-}
-
-// ResourceLimits defines default resource limits
-type ResourceLimits struct {
- MaxMemoryBytes int64 `yaml:"max_memory_bytes"`
- MaxCPUCores int32 `yaml:"max_cpu_cores"`
- MaxDiskBytes int64 `yaml:"max_disk_bytes"`
- TimeoutSeconds int32 `yaml:"timeout_seconds"`
- MaxConcurrentBuilds int32 `yaml:"max_concurrent_builds"`
- MaxDailyBuilds int32 `yaml:"max_daily_builds"`
- MaxStorageBytes int64 `yaml:"max_storage_bytes"`
- MaxBuildTimeMinutes int32 `yaml:"max_build_time_minutes"`
-}
-
-// DatabaseConfig holds database configuration
-type DatabaseConfig struct {
- DataDir string `yaml:"data_dir"`
- Type string `yaml:"type"` // "sqlite" (recommended), "postgres"
-
- // PostgreSQL specific (optional)
- Host string `yaml:"host,omitempty"`
- Port int `yaml:"port,omitempty"`
- Database string `yaml:"database,omitempty"`
- Username string `yaml:"username,omitempty"`
- Password string `yaml:"password,omitempty"`
- SSLMode string `yaml:"ssl_mode,omitempty"`
-}
-
-// OpenTelemetryConfig holds observability configuration
-type OpenTelemetryConfig struct {
- Enabled bool `yaml:"enabled"`
- ServiceName string `yaml:"service_name"`
- ServiceVersion string `yaml:"service_version"`
- TracingSamplingRate float64 `yaml:"tracing_sampling_rate"`
- OTLPEndpoint string `yaml:"otlp_endpoint"`
- PrometheusEnabled bool `yaml:"prometheus_enabled"`
- PrometheusPort string `yaml:"prometheus_port"`
- PrometheusInterface string `yaml:"prometheus_interface"`
- HighCardinalityLabelsEnabled bool `yaml:"high_cardinality_labels_enabled"`
-}
-
-// AssetManagerConfig holds assetmanagerd client configuration
-type AssetManagerConfig struct {
- Enabled bool `yaml:"enabled"`
- Endpoint string `yaml:"endpoint"`
-}
-
-// TLSConfig holds TLS configuration
-// AIDEV-BUSINESS_RULE: SPIFFE/mTLS is required by default for security - no fallback to disabled mode
-type TLSConfig struct {
- // Mode can be "disabled", "file", or "spiffe"
- Mode string `json:"mode,omitempty"`
-
- // File-based TLS options
- CertFile string `json:"cert_file,omitempty"`
- KeyFile string `json:"-"` // AIDEV-NOTE: Never serialize private key paths
- CAFile string `json:"ca_file,omitempty"`
-
- // SPIFFE options
- SPIFFESocketPath string `json:"spiffe_socket_path,omitempty"`
-}
-
-// LoadConfig loads configuration from environment variables
-func LoadConfig() (*Config, error) {
- return LoadConfigWithLogger(slog.Default())
-}
-
-// LoadConfigWithLogger loads configuration with a custom logger
-func LoadConfigWithLogger(logger *slog.Logger) (*Config, error) {
- config := &Config{
- Server: ServerConfig{
- Address: getEnvOrDefault("UNKEY_BUILDERD_ADDRESS", "0.0.0.0"),
- Port: getEnvOrDefault("UNKEY_BUILDERD_PORT", "8082"),
- ShutdownTimeout: getEnvDurationOrDefault("UNKEY_BUILDERD_SHUTDOWN_TIMEOUT", 15*time.Second),
- RateLimit: getEnvIntOrDefault("UNKEY_BUILDERD_RATE_LIMIT", 100),
- },
- Builder: BuilderConfig{
- MaxConcurrentBuilds: getEnvIntOrDefault("UNKEY_BUILDERD_MAX_CONCURRENT_BUILDS", 5),
- BuildTimeout: getEnvDurationOrDefault("UNKEY_BUILDERD_BUILD_TIMEOUT", 15*time.Minute),
- ScratchDir: getEnvOrDefault("UNKEY_BUILDERD_SCRATCH_DIR", "/tmp/builderd"),
- RootfsOutputDir: getEnvOrDefault("UNKEY_BUILDERD_ROOTFS_OUTPUT_DIR", "/opt/builderd/rootfs"),
- WorkspaceDir: getEnvOrDefault("UNKEY_BUILDERD_WORKSPACE_DIR", "/opt/builderd/workspace"),
- CleanupInterval: getEnvDurationOrDefault("UNKEY_BUILDERD_CLEANUP_INTERVAL", 1*time.Hour),
- UsePipelineExecutor: getEnvBoolOrDefault("UNKEY_BUILDERD_USE_PIPELINE_EXECUTOR", false),
- },
- Storage: StorageConfig{ //nolint:exhaustruct // S3Config and GCSConfig are optional backend-specific configs
- Backend: getEnvOrDefault("UNKEY_BUILDERD_STORAGE_BACKEND", "local"),
- RetentionDays: getEnvIntOrDefault("UNKEY_BUILDERD_STORAGE_RETENTION_DAYS", 30),
- MaxSizeGB: getEnvIntOrDefault("UNKEY_BUILDERD_STORAGE_MAX_SIZE_GB", 100),
- CacheEnabled: getEnvBoolOrDefault("UNKEY_BUILDERD_STORAGE_CACHE_ENABLED", true),
- CacheMaxSizeGB: getEnvIntOrDefault("UNKEY_BUILDERD_STORAGE_CACHE_MAX_SIZE_GB", 50),
- },
- Docker: DockerConfig{
- RegistryAuth: getEnvBoolOrDefault("UNKEY_BUILDERD_DOCKER_REGISTRY_AUTH", true),
- MaxImageSizeGB: getEnvIntOrDefault("UNKEY_BUILDERD_DOCKER_MAX_IMAGE_SIZE_GB", 5),
- AllowedRegistries: getEnvSliceOrDefault("UNKEY_BUILDERD_DOCKER_ALLOWED_REGISTRIES", []string{}),
- PullTimeout: getEnvDurationOrDefault("UNKEY_BUILDERD_DOCKER_PULL_TIMEOUT", 10*time.Minute),
- RegistryMirror: getEnvOrDefault("UNKEY_BUILDERD_DOCKER_REGISTRY_MIRROR", ""),
- InsecureRegistries: getEnvSliceOrDefault("UNKEY_BUILDERD_DOCKER_INSECURE_REGISTRIES", []string{}),
- },
- Database: DatabaseConfig{
- DataDir: getEnvOrDefault("UNKEY_BUILDERD_DATABASE_DATA_DIR", "/opt/builderd/data"),
- Type: getEnvOrDefault("UNKEY_BUILDERD_DATABASE_TYPE", "sqlite"),
- Host: getEnvOrDefault("UNKEY_BUILDERD_DATABASE_HOST", "localhost"),
- Port: getEnvIntOrDefault("UNKEY_BUILDERD_DATABASE_PORT", 5432),
- Database: getEnvOrDefault("UNKEY_BUILDERD_DATABASE_NAME", "builderd"),
- Username: getEnvOrDefault("UNKEY_BUILDERD_DATABASE_USERNAME", "builderd"),
- Password: getEnvOrDefault("UNKEY_BUILDERD_DATABASE_PASSWORD", ""),
- SSLMode: getEnvOrDefault("UNKEY_BUILDERD_DATABASE_SSL_MODE", "disable"),
- },
- OpenTelemetry: OpenTelemetryConfig{
- Enabled: getEnvBoolOrDefault("UNKEY_BUILDERD_OTEL_ENABLED", false),
- ServiceName: getEnvOrDefault("UNKEY_BUILDERD_OTEL_SERVICE_NAME", "builderd"),
- ServiceVersion: getEnvOrDefault("UNKEY_BUILDERD_OTEL_SERVICE_VERSION", "0.1.0"),
- TracingSamplingRate: getEnvFloat64OrDefault("UNKEY_BUILDERD_OTEL_SAMPLING_RATE", 1.0),
- OTLPEndpoint: getEnvOrDefault("UNKEY_BUILDERD_OTEL_ENDPOINT", "localhost:4318"),
- PrometheusEnabled: getEnvBoolOrDefault("UNKEY_BUILDERD_OTEL_PROMETHEUS_ENABLED", true),
- PrometheusPort: getEnvOrDefault("UNKEY_BUILDERD_OTEL_PROMETHEUS_PORT", "9466"),
- PrometheusInterface: getEnvOrDefault("UNKEY_BUILDERD_OTEL_PROMETHEUS_INTERFACE", "127.0.0.1"),
- HighCardinalityLabelsEnabled: getEnvBoolOrDefault("UNKEY_BUILDERD_OTEL_HIGH_CARDINALITY_ENABLED", false),
- },
- AssetManager: AssetManagerConfig{
- Enabled: getEnvBoolOrDefault("UNKEY_BUILDERD_ASSETMANAGER_ENABLED", true),
- Endpoint: getEnvOrDefault("UNKEY_BUILDERD_ASSETMANAGER_ENDPOINT", "https://localhost:8083"),
- },
- TLS: &TLSConfig{
- Mode: getEnvOrDefault("UNKEY_BUILDERD_TLS_MODE", "spiffe"),
- CertFile: getEnvOrDefault("UNKEY_BUILDERD_TLS_CERT_FILE", ""),
- KeyFile: getEnvOrDefault("UNKEY_BUILDERD_TLS_KEY_FILE", ""),
- CAFile: getEnvOrDefault("UNKEY_BUILDERD_TLS_CA_FILE", ""),
- SPIFFESocketPath: getEnvOrDefault("UNKEY_BUILDERD_SPIFFE_SOCKET", "/var/lib/spire/agent/agent.sock"),
- },
- }
-
- // Validate configuration
- if err := validateConfig(config); err != nil {
- return nil, fmt.Errorf("invalid configuration: %w", err)
- }
-
- logger.Info("configuration loaded successfully",
- slog.String("server_address", config.Server.Address),
- slog.String("server_port", config.Server.Port),
- slog.String("storage_backend", config.Storage.Backend),
- slog.Bool("otel_enabled", config.OpenTelemetry.Enabled),
- slog.Int("max_concurrent_builds", config.Builder.MaxConcurrentBuilds),
- )
-
- return config, nil
-}
-
-// validateConfig validates the loaded configuration
-func validateConfig(config *Config) error {
- if config.Builder.MaxConcurrentBuilds <= 0 {
- return fmt.Errorf("max_concurrent_builds must be positive")
- }
-
- if config.Builder.BuildTimeout <= 0 {
- return fmt.Errorf("build_timeout must be positive")
- }
-
- if config.Storage.MaxSizeGB <= 0 {
- return fmt.Errorf("storage max_size_gb must be positive")
- }
-
- if config.Docker.MaxImageSizeGB <= 0 {
- return fmt.Errorf("docker max_image_size_gb must be positive")
- }
-
- if config.OpenTelemetry.TracingSamplingRate < 0 || config.OpenTelemetry.TracingSamplingRate > 1 {
- return fmt.Errorf("tracing_sampling_rate must be between 0.0 and 1.0")
- }
-
- return nil
-}
-
-// Helper functions for environment variable parsing
-func getEnvOrDefault(key, defaultValue string) string {
- if value := os.Getenv(key); value != "" {
- return value
- }
- return defaultValue
-}
-
-func getEnvIntOrDefault(key string, defaultValue int) int {
- if value := os.Getenv(key); value != "" {
- if parsed, err := strconv.Atoi(value); err == nil {
- return parsed
- }
- }
- return defaultValue
-}
-
-func getEnvInt32OrDefault(key string, defaultValue int32) int32 {
- if value := os.Getenv(key); value != "" {
- if parsed, err := strconv.ParseInt(value, 10, 32); err == nil {
- return int32(parsed)
- }
- }
- return defaultValue
-}
-
-func getEnvInt64OrDefault(key string, defaultValue int64) int64 {
- if value := os.Getenv(key); value != "" {
- if parsed, err := strconv.ParseInt(value, 10, 64); err == nil {
- return parsed
- }
- }
- return defaultValue
-}
-
-func getEnvFloat64OrDefault(key string, defaultValue float64) float64 {
- if value := os.Getenv(key); value != "" {
- if parsed, err := strconv.ParseFloat(value, 64); err == nil {
- return parsed
- }
- }
- return defaultValue
-}
-
-func getEnvBoolOrDefault(key string, defaultValue bool) bool {
- if value := os.Getenv(key); value != "" {
- if parsed, err := strconv.ParseBool(value); err == nil {
- return parsed
- }
- }
- return defaultValue
-}
-
-func getEnvDurationOrDefault(key string, defaultValue time.Duration) time.Duration {
- if value := os.Getenv(key); value != "" {
- if parsed, err := time.ParseDuration(value); err == nil {
- return parsed
- }
- }
- return defaultValue
-}
-
-func getEnvSliceOrDefault(key string, defaultValue []string) []string {
- // For now, return default. In production, could parse comma-separated values
- return defaultValue
-}
diff --git a/go/apps/builderd/internal/executor/docker.go b/go/apps/builderd/internal/executor/docker.go
deleted file mode 100644
index 8da099d5b3..0000000000
--- a/go/apps/builderd/internal/executor/docker.go
+++ /dev/null
@@ -1,1115 +0,0 @@
-package executor
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "log/slog"
- "os"
- "os/exec"
- "path/filepath"
- "regexp"
- "strings"
- "time"
-
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/trace"
-
- "github.com/unkeyed/unkey/go/apps/builderd/internal/config"
- "github.com/unkeyed/unkey/go/apps/builderd/internal/observability"
- builderv1 "github.com/unkeyed/unkey/go/gen/proto/builderd/v1"
-)
-
-// DockerExecutor handles Docker image extraction to rootfs
-type DockerExecutor struct {
- logger *slog.Logger
- config *config.Config
- buildMetrics *observability.BuildMetrics
-}
-
-// Ensure DockerExecutor implements Executor interface
-var _ Executor = (*DockerExecutor)(nil)
-
-// NewDockerExecutor creates a new Docker executor
-func NewDockerExecutor(logger *slog.Logger, cfg *config.Config, metrics *observability.BuildMetrics) *DockerExecutor {
- return &DockerExecutor{
- logger: logger,
- config: cfg,
- buildMetrics: metrics,
- }
-}
-
-// ExtractDockerImage pulls a Docker image and extracts it to a rootfs directory
-func (d *DockerExecutor) ExtractDockerImage(ctx context.Context, request *builderv1.CreateBuildRequest) (*BuildResult, error) {
- // Generate build ID for backward compatibility
- return d.ExtractDockerImageWithID(ctx, request, generateBuildID())
-}
-
-// ExtractDockerImageWithID pulls a Docker image and extracts it with a pre-assigned build ID
-func (d *DockerExecutor) ExtractDockerImageWithID(ctx context.Context, request *builderv1.CreateBuildRequest, buildID string) (*BuildResult, error) {
- start := time.Now()
-
- logger := d.logger.With(
- slog.String("image_uri", request.GetConfig().GetSource().GetDockerImage().GetImageUri()),
- )
-
- logger.InfoContext(ctx, "starting Docker image extraction")
-
- // Record build start metrics
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildStart(ctx, "docker", "docker")
- }
-
- defer func() {
- duration := time.Since(start)
- logger.InfoContext(ctx, "Docker image extraction completed", slog.Duration("duration", duration))
- }()
-
- dockerSource := request.GetConfig().GetSource().GetDockerImage()
- if dockerSource == nil {
- return nil, fmt.Errorf("docker image source is required")
- }
-
- // Use the provided build ID
- workspaceDir := filepath.Join(d.config.Builder.WorkspaceDir, buildID)
- rootfsDir := filepath.Join(d.config.Builder.RootfsOutputDir, buildID)
-
- logger = logger.With(
- slog.String("build_id", buildID),
- slog.String("workspace_dir", workspaceDir),
- slog.String("rootfs_dir", rootfsDir),
- )
-
- // Create directories
- if err := os.MkdirAll(workspaceDir, 0755); err != nil {
- logger.ErrorContext(ctx, "failed to create workspace directory", slog.String("error", err.Error()))
- return nil, fmt.Errorf("failed to create workspace directory: %w", err)
- }
-
- if err := os.MkdirAll(rootfsDir, 0755); err != nil {
- logger.ErrorContext(ctx, "failed to create rootfs directory", slog.String("error", err.Error()))
- return nil, fmt.Errorf("failed to create rootfs directory: %w", err)
- }
-
- // Use the full image URI directly
- fullImageName := dockerSource.GetImageUri()
- if fullImageName == "" {
- return nil, fmt.Errorf("docker image URI is required")
- }
-
- logger = logger.With(slog.String("full_image_name", fullImageName))
-
- // Step 1: Pull the Docker image
- if err := d.pullDockerImage(ctx, logger, fullImageName); err != nil {
- logger.ErrorContext(ctx, "failed to pull Docker image",
- slog.String("error", err.Error()),
- slog.String("image", fullImageName),
- )
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildComplete(ctx, "docker", "docker", time.Since(start), false)
- }
- return nil, fmt.Errorf("failed to pull Docker image: %w", err)
- }
-
- // Step 2: Create container from image (without running)
- containerID, err := d.createContainer(ctx, logger, fullImageName)
- if err != nil {
- logger.ErrorContext(ctx, "failed to create container",
- slog.String("error", err.Error()),
- slog.String("image", fullImageName),
- )
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildComplete(ctx, "docker", "docker", time.Since(start), false)
- }
- return nil, fmt.Errorf("failed to create container: %w", err)
- }
-
- // Ensure cleanup of container
- defer func() {
- if cleanupErr := d.removeContainer(ctx, logger, containerID); cleanupErr != nil {
- logger.WarnContext(ctx, "failed to cleanup container", slog.String("error", cleanupErr.Error()))
- }
- }()
-
- // Step 3: Extract container metadata (entrypoint, cmd, env, etc.)
- metadata, err := d.extractContainerMetadata(ctx, logger, fullImageName)
- if err != nil {
- logger.ErrorContext(ctx, "failed to extract container metadata",
- slog.String("error", err.Error()),
- slog.String("image", fullImageName),
- )
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildComplete(ctx, "docker", "docker", time.Since(start), false)
- }
- return nil, fmt.Errorf("failed to extract container metadata: %w", err)
- }
-
- // Step 4: Extract filesystem from container
- if err := d.extractFilesystem(ctx, logger, containerID, rootfsDir, metadata); err != nil {
- logger.ErrorContext(ctx, "failed to extract filesystem",
- slog.String("error", err.Error()),
- slog.String("container_id", containerID),
- slog.String("rootfs_dir", rootfsDir),
- )
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildComplete(ctx, "docker", "docker", time.Since(start), false)
- }
- return nil, fmt.Errorf("failed to extract filesystem: %w", err)
- }
-
- // Step 5: Optimize rootfs (remove unnecessary files, etc.)
- if err := d.optimizeRootfs(ctx, logger, rootfsDir); err != nil {
- logger.WarnContext(ctx, "failed to optimize rootfs", slog.String("error", err.Error()))
- // Don't fail the build for optimization errors
- }
-
- // Step 6: Create ext4 filesystem image
- ext4Path := filepath.Join(d.config.Builder.RootfsOutputDir, buildID+".ext4")
- if err := d.createExt4Image(ctx, logger, rootfsDir, ext4Path); err != nil {
- logger.ErrorContext(ctx, "failed to create ext4 image",
- slog.String("error", err.Error()),
- slog.String("build_id", buildID),
- )
- return nil, fmt.Errorf("failed to create ext4 image: %w", err)
- }
-
- // Step 7: Save container metadata alongside the rootfs
- metadataPath := filepath.Join(d.config.Builder.RootfsOutputDir, buildID+".metadata.json")
- if err := d.saveContainerMetadata(ctx, logger, metadata, metadataPath); err != nil {
- logger.ErrorContext(ctx, "failed to save container metadata",
- slog.String("error", err.Error()),
- slog.String("metadata_path", metadataPath),
- )
- return nil, fmt.Errorf("failed to save container metadata: %w", err)
- }
-
- // Create build result
- result := &BuildResult{ //nolint:exhaustruct // Error, Metadata, and Metrics fields are set after successful build
- BuildID: buildID,
- SourceType: "docker",
- SourceImage: fullImageName,
- RootfsPath: ext4Path, // Use the ext4 image path instead of directory
- WorkspaceDir: workspaceDir,
- StartTime: start,
- EndTime: time.Now(),
- Status: "completed",
- ImageMetadata: metadata, // Include the extracted metadata
- }
-
- // Record successful build
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildComplete(ctx, "docker", "docker", time.Since(start), true)
- }
-
- logger.InfoContext(ctx, "Docker image extraction successful",
- slog.String("rootfs_path", rootfsDir),
- slog.Duration("total_duration", time.Since(start)),
- )
-
- return result, nil
-}
-
-// pullDockerImage pulls the specified Docker image
-func (d *DockerExecutor) pullDockerImage(ctx context.Context, logger *slog.Logger, imageName string) error {
- // AIDEV-NOTE: Comprehensive observability for Docker pull step
- tracer := otel.Tracer("builderd/docker")
- stepStart := time.Now()
-
- // Start OpenTelemetry span for this build step
- ctx, span := tracer.Start(ctx, "builderd.docker.pull_image",
- trace.WithAttributes(
- attribute.String("step", "pull"),
- attribute.String("image", imageName),
- attribute.String("source_type", "docker"),
- ),
- )
- defer span.End()
-
- // Record step start metrics
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildStepStart(ctx, "pull", "docker")
- }
-
- logger.InfoContext(ctx, "pulling Docker image", slog.String("image", imageName))
-
- // Create context with timeout for docker pull
- pullCtx, cancel := context.WithTimeout(ctx, d.config.Docker.PullTimeout)
- defer cancel()
-
- cmd := exec.CommandContext(pullCtx, "docker", "pull", imageName)
-
- // Capture both stdout and stderr
- output, err := cmd.CombinedOutput()
-
- // Record step completion
- stepDuration := time.Since(stepStart)
- success := err == nil
-
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildStepComplete(ctx, "pull", "docker", stepDuration, success)
- if success {
- d.buildMetrics.RecordPullDuration(ctx, "docker", stepDuration)
- }
- }
-
- if err != nil {
- span.SetAttributes(
- attribute.String("error", err.Error()),
- attribute.String("output", string(output)),
- )
- logger.ErrorContext(ctx, "docker pull failed",
- slog.String("error", err.Error()),
- slog.String("output", string(output)),
- slog.Duration("duration", stepDuration),
- )
- return fmt.Errorf("docker pull failed: %w", err)
- }
-
- span.SetAttributes(attribute.String("status", "success"))
- logger.InfoContext(ctx, "docker pull completed",
- slog.String("image", imageName),
- slog.Duration("duration", stepDuration),
- )
- return nil
-}
-
-// createContainer creates a container from the image without running it
-func (d *DockerExecutor) createContainer(ctx context.Context, logger *slog.Logger, imageName string) (string, error) {
- // AIDEV-NOTE: Comprehensive observability for Docker create step
- tracer := otel.Tracer("builderd/docker")
- stepStart := time.Now()
-
- // Start OpenTelemetry span for this build step
- ctx, span := tracer.Start(ctx, "builderd.docker.create_container",
- trace.WithAttributes(
- attribute.String("step", "create"),
- attribute.String("image", imageName),
- attribute.String("source_type", "docker"),
- ),
- )
- defer span.End()
-
- // Record step start metrics
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildStepStart(ctx, "create", "docker")
- }
-
- logger.InfoContext(ctx, "creating container from image", slog.String("image", imageName))
-
- cmd := exec.CommandContext(ctx, "docker", "create", imageName)
- output, err := cmd.Output()
-
- // Record step completion
- stepDuration := time.Since(stepStart)
- success := err == nil
-
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildStepComplete(ctx, "create", "docker", stepDuration, success)
- }
-
- if err != nil {
- span.SetAttributes(attribute.String("error", err.Error()))
- logger.ErrorContext(ctx, "docker create failed",
- slog.String("error", err.Error()),
- slog.String("image", imageName),
- slog.Duration("duration", stepDuration),
- )
- return "", fmt.Errorf("docker create failed: %w", err)
- }
-
- containerID := strings.TrimSpace(string(output))
- span.SetAttributes(
- attribute.String("status", "success"),
- attribute.String("container_id", containerID),
- )
- logger.InfoContext(ctx, "container created",
- slog.String("container_id", containerID),
- slog.Duration("duration", stepDuration),
- )
-
- return containerID, nil
-}
-
-// extractFilesystem extracts the filesystem from the container to the rootfs directory
-func (d *DockerExecutor) extractFilesystem(ctx context.Context, logger *slog.Logger, containerID, rootfsDir string, metadata *builderv1.ImageMetadata) error {
- // AIDEV-NOTE: Comprehensive observability for filesystem extraction step
- tracer := otel.Tracer("builderd/docker")
- stepStart := time.Now()
-
- // Start OpenTelemetry span for this build step
- ctx, span := tracer.Start(ctx, "builderd.docker.extract_filesystem",
- trace.WithAttributes(
- attribute.String("step", "extract"),
- attribute.String("container_id", containerID),
- attribute.String("rootfs_dir", rootfsDir),
- attribute.String("source_type", "docker"),
- ),
- )
- defer span.End()
-
- // Record step start metrics
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildStepStart(ctx, "extract", "docker")
- }
-
- logger.InfoContext(ctx, "extracting filesystem from container",
- slog.String("container_id", containerID),
- slog.String("rootfs_dir", rootfsDir),
- )
-
- // Use docker export to get the full filesystem as a tar stream
- cmd := exec.CommandContext(ctx, "docker", "export", containerID)
-
- // Create tar extraction command
- tarCmd := exec.CommandContext(ctx, "tar", "-xf", "-", "-C", rootfsDir)
-
- // Connect docker export output to tar input
- pipe, err := cmd.StdoutPipe()
- if err != nil {
- stepDuration := time.Since(stepStart)
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildStepComplete(ctx, "extract", "docker", stepDuration, false)
- }
- span.SetAttributes(attribute.String("error", err.Error()))
- logger.ErrorContext(ctx, "failed to create pipe",
- slog.String("error", err.Error()),
- slog.Duration("duration", stepDuration),
- )
- return fmt.Errorf("failed to create pipe: %w", err)
- }
-
- tarCmd.Stdin = pipe
-
- // Start both commands
- if err := cmd.Start(); err != nil {
- stepDuration := time.Since(stepStart)
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildStepComplete(ctx, "extract", "docker", stepDuration, false)
- }
- span.SetAttributes(attribute.String("error", err.Error()))
- logger.ErrorContext(ctx, "failed to start docker export",
- slog.String("error", err.Error()),
- slog.Duration("duration", stepDuration),
- )
- return fmt.Errorf("failed to start docker export: %w", err)
- }
-
- if err := tarCmd.Start(); err != nil {
- _ = cmd.Process.Kill()
- stepDuration := time.Since(stepStart)
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildStepComplete(ctx, "extract", "docker", stepDuration, false)
- }
- span.SetAttributes(attribute.String("error", err.Error()))
- logger.ErrorContext(ctx, "failed to start tar extraction",
- slog.String("error", err.Error()),
- slog.Duration("duration", stepDuration),
- )
- return fmt.Errorf("failed to start tar extraction: %w", err)
- }
-
- // Wait for docker export to complete
- if err := cmd.Wait(); err != nil {
- _ = tarCmd.Process.Kill()
- stepDuration := time.Since(stepStart)
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildStepComplete(ctx, "extract", "docker", stepDuration, false)
- }
- span.SetAttributes(attribute.String("error", err.Error()))
- logger.ErrorContext(ctx, "docker export failed",
- slog.String("error", err.Error()),
- slog.Duration("duration", stepDuration),
- )
- return fmt.Errorf("docker export failed: %w", err)
- }
-
- // Close the pipe and wait for tar to complete
- pipe.Close()
- if err := tarCmd.Wait(); err != nil {
- stepDuration := time.Since(stepStart)
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildStepComplete(ctx, "extract", "docker", stepDuration, false)
- }
- span.SetAttributes(attribute.String("error", err.Error()))
- logger.ErrorContext(ctx, "tar extraction failed",
- slog.String("error", err.Error()),
- slog.Duration("duration", stepDuration),
- )
- return fmt.Errorf("tar extraction failed: %w", err)
- }
-
- // Record successful completion
- stepDuration := time.Since(stepStart)
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildStepComplete(ctx, "extract", "docker", stepDuration, true)
- d.buildMetrics.RecordExtractDuration(ctx, "docker", stepDuration)
- }
-
- span.SetAttributes(attribute.String("status", "success"))
- logger.InfoContext(ctx, "filesystem extraction completed",
- slog.Duration("duration", stepDuration),
- )
-
- // AIDEV-NOTE: CRITICAL FIX - Inject metald-init into rootfs after extraction
- // This ensures every container has the required init process for VM execution
- if err := d.injectMetaldInit(ctx, logger, rootfsDir); err != nil {
- logger.WarnContext(ctx, "failed to inject metald-init (non-fatal)",
- slog.String("error", err.Error()),
- slog.String("rootfs_dir", rootfsDir),
- )
- // Continue anyway - this is not fatal, VM might still work with container's original init
- }
-
- // AIDEV-NOTE: Create container command file for metald-init
- // This tells metald-init what command to run when the microVM starts
- if err := d.createContainerCmd(ctx, logger, rootfsDir, metadata); err != nil {
- logger.WarnContext(ctx, "failed to create container.cmd (non-fatal)",
- slog.String("error", err.Error()),
- slog.String("rootfs_dir", rootfsDir),
- )
- // Continue anyway - this is not fatal if there's a fallback command
- }
-
- // AIDEV-NOTE: Create container environment file for metald-init
- // This provides complete container runtime environment replication
- if err := d.createContainerEnv(ctx, logger, rootfsDir, metadata); err != nil {
- logger.WarnContext(ctx, "failed to create container.env (non-fatal)",
- slog.String("error", err.Error()),
- slog.String("rootfs_dir", rootfsDir),
- )
- // Continue anyway - basic environment will still work
- }
-
- return nil
-}
-
-// removeContainer removes the temporary container
-func (d *DockerExecutor) removeContainer(ctx context.Context, logger *slog.Logger, containerID string) error {
- logger.DebugContext(ctx, "removing container", slog.String("container_id", containerID))
-
- cmd := exec.CommandContext(ctx, "docker", "rm", containerID)
- if err := cmd.Run(); err != nil {
- logger.ErrorContext(ctx, "failed to remove container",
- slog.String("error", err.Error()),
- slog.String("container_id", containerID),
- )
- return fmt.Errorf("failed to remove container: %w", err)
- }
-
- logger.DebugContext(ctx, "container removed", slog.String("container_id", containerID))
- return nil
-}
-
-// optimizeRootfs removes unnecessary files and optimizes the rootfs
-func (d *DockerExecutor) optimizeRootfs(ctx context.Context, logger *slog.Logger, rootfsDir string) error {
- // AIDEV-NOTE: Comprehensive observability for rootfs optimization step
- tracer := otel.Tracer("builderd/docker")
- stepStart := time.Now()
-
- // Start OpenTelemetry span for this build step
- ctx, span := tracer.Start(ctx, "builderd.docker.optimize_rootfs",
- trace.WithAttributes(
- attribute.String("step", "optimize"),
- attribute.String("rootfs_dir", rootfsDir),
- attribute.String("source_type", "docker"),
- ),
- )
- defer span.End()
-
- // Record step start metrics
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildStepStart(ctx, "optimize", "docker")
- }
-
- logger.InfoContext(ctx, "optimizing rootfs", slog.String("rootfs_dir", rootfsDir))
-
- // List of directories/files to remove for optimization
- removePatterns := []string{
- "var/cache/*",
- "var/lib/apt/lists/*",
- "tmp/*",
- "var/tmp/*",
- "usr/share/doc/*",
- "usr/share/man/*",
- "usr/share/info/*",
- "var/log/*",
- }
-
- var lastError error
- removedPatterns := 0
-
- for _, pattern := range removePatterns {
- fullPattern := filepath.Join(rootfsDir, pattern)
-
- // Validate and sanitize the path before executing
- if err := validateAndSanitizePath(rootfsDir, fullPattern); err != nil {
- logger.WarnContext(ctx, "skipping unsafe pattern",
- slog.String("pattern", pattern),
- slog.String("error", err.Error()),
- )
- continue
- }
-
- // Use rm command to remove files matching pattern
- // Note: fullPattern is now validated and sanitized
- //nolint:gosec // G204: Path is validated and sanitized above to prevent injection
- cmd := exec.CommandContext(ctx, "sh", "-c", fmt.Sprintf("rm -rf %s", fullPattern))
- if err := cmd.Run(); err != nil {
- logger.DebugContext(ctx, "failed to remove pattern",
- slog.String("pattern", pattern),
- slog.String("error", err.Error()),
- )
- lastError = err
- // Continue with other patterns even if one fails
- } else {
- removedPatterns++
- }
- }
-
- // Get rootfs size after optimization
- var finalSize int64
- if size, err := d.getRootfsSize(rootfsDir); err == nil {
- finalSize = size
- if d.buildMetrics != nil {
- d.buildMetrics.RecordRootfsSize(ctx, size)
- }
- } else {
- logger.WarnContext(ctx, "failed to calculate rootfs size", slog.String("error", err.Error()))
- }
-
- // Record step completion
- stepDuration := time.Since(stepStart)
- success := lastError == nil
-
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildStepComplete(ctx, "optimize", "docker", stepDuration, success)
- d.buildMetrics.RecordOptimizeDuration(ctx, stepDuration)
- }
-
- if lastError != nil {
- span.SetAttributes(
- attribute.String("error", lastError.Error()),
- attribute.Int("patterns_removed", removedPatterns),
- attribute.Int("total_patterns", len(removePatterns)),
- )
- logger.WarnContext(ctx, "rootfs optimization completed with errors",
- slog.String("error", lastError.Error()),
- slog.Int("patterns_removed", removedPatterns),
- slog.Int("total_patterns", len(removePatterns)),
- slog.Int64("size_bytes", finalSize),
- slog.Duration("duration", stepDuration),
- )
- } else {
- span.SetAttributes(
- attribute.String("status", "success"),
- attribute.Int("patterns_removed", removedPatterns),
- )
- logger.InfoContext(ctx, "rootfs optimization completed",
- slog.Int64("size_bytes", finalSize),
- slog.Int("patterns_removed", removedPatterns),
- slog.Duration("duration", stepDuration),
- )
- }
-
- return lastError
-}
-
-// getRootfsSize calculates the total size of the rootfs directory
-func (d *DockerExecutor) getRootfsSize(rootfsDir string) (int64, error) {
- var totalSize int64
-
- err := filepath.Walk(rootfsDir, func(path string, info os.FileInfo, err error) error {
- if err != nil {
- d.logger.Debug("error walking rootfs path",
- slog.String("path", path),
- slog.String("error", err.Error()),
- )
- return err
- }
- if !info.IsDir() {
- totalSize += info.Size()
- }
- return nil
- })
-
- if err != nil {
- d.logger.Error("failed to calculate rootfs size",
- slog.String("error", err.Error()),
- slog.String("rootfs_dir", rootfsDir),
- )
- }
-
- return totalSize, err
-}
-
-// createExt4Image creates an ext4 filesystem image from the rootfs directory
-func (d *DockerExecutor) createExt4Image(ctx context.Context, logger *slog.Logger, rootfsDir, outputPath string) error {
- // AIDEV-NOTE: Create ext4 filesystem image for Firecracker VMs
- tracer := otel.Tracer("builderd/docker")
- stepStart := time.Now()
-
- // Start OpenTelemetry span for this build step
- ctx, span := tracer.Start(ctx, "builderd.docker.create_ext4_image",
- trace.WithAttributes(
- attribute.String("step", "create_ext4"),
- attribute.String("rootfs_dir", rootfsDir),
- attribute.String("output_path", outputPath),
- ),
- )
- defer span.End()
-
- logger.InfoContext(ctx, "creating ext4 filesystem image",
- slog.String("rootfs_dir", rootfsDir),
- slog.String("output_path", outputPath),
- )
-
- // Calculate size needed (rootfs size + 20% overhead)
- rootfsSize, err := d.getRootfsSize(rootfsDir)
- if err != nil {
- return fmt.Errorf("failed to calculate rootfs size: %w", err)
- }
-
- // Add 20% overhead for filesystem metadata and future growth
- imageSize := int64(float64(rootfsSize) * 1.2)
- // Minimum 100MB, round up to nearest MB
- minSize := int64(100 * 1024 * 1024)
- if imageSize < minSize {
- imageSize = minSize
- }
- imageSize = (imageSize + 1024*1024 - 1) / (1024 * 1024) * (1024 * 1024) // Round up to MB
-
- logger.InfoContext(ctx, "calculated image size",
- slog.Int64("rootfs_bytes", rootfsSize),
- slog.Int64("image_bytes", imageSize),
- )
-
- // Step 1: Create sparse file
- createCmd := exec.CommandContext(ctx, "truncate", "-s", fmt.Sprintf("%d", imageSize), outputPath)
- if output, err := createCmd.CombinedOutput(); err != nil {
- logger.ErrorContext(ctx, "failed to create sparse file",
- slog.String("error", err.Error()),
- slog.String("output", string(output)),
- )
- return fmt.Errorf("failed to create sparse file: %w", err)
- }
-
- // Step 2: Create ext4 filesystem
- mkfsCmd := exec.CommandContext(ctx, "mkfs.ext4", "-F", "-d", rootfsDir, outputPath)
- if output, err := mkfsCmd.CombinedOutput(); err != nil {
- logger.ErrorContext(ctx, "failed to create ext4 filesystem",
- slog.String("error", err.Error()),
- slog.String("output", string(output)),
- )
- // Clean up the sparse file
- _ = os.Remove(outputPath)
- return fmt.Errorf("failed to create ext4 filesystem: %w", err)
- }
-
- // Step 3: Optimize the filesystem (optional)
- e2fsckCmd := exec.CommandContext(ctx, "e2fsck", "-f", "-y", outputPath)
- if output, err := e2fsckCmd.CombinedOutput(); err != nil {
- // Log but don't fail - e2fsck returns non-zero for fixes
- logger.WarnContext(ctx, "e2fsck completed with warnings",
- slog.String("output", string(output)),
- )
- }
-
- // Get final file size
- fileInfo, err := os.Stat(outputPath)
- if err != nil {
- logger.ErrorContext(ctx, "failed to stat ext4 image",
- slog.String("error", err.Error()),
- )
- return fmt.Errorf("failed to stat ext4 image: %w", err)
- }
-
- // Set file permissions to be world-readable for other services
- // AIDEV-NOTE: Running as root, make files readable by other services
- if err := os.Chmod(outputPath, 0644); err != nil {
- logger.ErrorContext(ctx, "failed to set file permissions",
- slog.String("error", err.Error()),
- )
- return fmt.Errorf("failed to set file permissions: %w", err)
- }
-
- stepDuration := time.Since(stepStart)
- span.SetAttributes(
- attribute.String("status", "success"),
- attribute.Int64("final_size", fileInfo.Size()),
- )
-
- logger.InfoContext(ctx, "ext4 filesystem image created successfully",
- slog.String("path", outputPath),
- slog.Int64("size_bytes", fileInfo.Size()),
- slog.Duration("duration", stepDuration),
- )
-
- return nil
-}
-
-// Execute implements the Executor interface
-func (d *DockerExecutor) Execute(ctx context.Context, request *builderv1.CreateBuildRequest) (*BuildResult, error) {
- // Generate a new build ID for backward compatibility
- return d.ExecuteWithID(ctx, request, generateBuildID())
-}
-
-// ExecuteWithID implements the Executor interface for Docker builds with a pre-assigned ID
-func (d *DockerExecutor) ExecuteWithID(ctx context.Context, request *builderv1.CreateBuildRequest, buildID string) (*BuildResult, error) {
- return d.ExtractDockerImageWithID(ctx, request, buildID)
-}
-
-// GetSupportedSources implements the Executor interface
-func (d *DockerExecutor) GetSupportedSources() []string {
- return []string{"docker"}
-}
-
-// Cleanup implements the Executor interface
-func (d *DockerExecutor) Cleanup(ctx context.Context, buildID string) error {
- logger := d.logger.With(slog.String("build_id", buildID))
-
- // Clean up workspace directory
- workspaceDir := filepath.Join(d.config.Builder.WorkspaceDir, buildID)
- if err := os.RemoveAll(workspaceDir); err != nil {
- logger.ErrorContext(ctx, "failed to cleanup workspace directory",
- slog.String("workspace_dir", workspaceDir),
- slog.String("error", err.Error()),
- )
- return fmt.Errorf("failed to cleanup workspace: %w", err)
- }
-
- logger.InfoContext(ctx, "build cleanup completed", slog.String("workspace_dir", workspaceDir))
- return nil
-}
-
-// generateBuildID generates a unique build ID
-func generateBuildID() string {
- return fmt.Sprintf("build-%d", time.Now().UnixNano())
-}
-
-// validateAndSanitizePath validates that the target path is within the rootfs directory
-// and doesn't contain dangerous characters or path traversal attempts
-func validateAndSanitizePath(rootfsDir, targetPath string) error {
- // Clean and resolve paths to prevent directory traversal
- cleanRootfs := filepath.Clean(rootfsDir)
- cleanTarget := filepath.Clean(targetPath)
-
- // Ensure rootfs directory exists and is a directory
- if info, err := os.Stat(cleanRootfs); err != nil {
- return fmt.Errorf("rootfs directory does not exist: %w", err)
- } else if !info.IsDir() {
- return fmt.Errorf("rootfs path is not a directory: %s", cleanRootfs)
- }
-
- // Check that target path is within rootfs directory (prevent path traversal)
- relPath, err := filepath.Rel(cleanRootfs, cleanTarget)
- if err != nil {
- return fmt.Errorf("invalid path relationship: %w", err)
- }
-
- // Ensure the relative path doesn't start with ".." (path traversal attempt)
- if strings.HasPrefix(relPath, "..") || strings.Contains(relPath, "../") {
- return fmt.Errorf("path traversal attempt detected: %s", relPath)
- }
-
- // Additional security: check for dangerous characters and sequences
- dangerousPattern := regexp.MustCompile(`[;&|$\x60\\]|&&|\|\||>>|<<`)
- if dangerousPattern.MatchString(cleanTarget) {
- return fmt.Errorf("dangerous characters detected in path: %s", cleanTarget)
- }
-
- // Ensure path length is reasonable (prevent buffer overflow attacks)
- if len(cleanTarget) > 4096 {
- return fmt.Errorf("path too long: %d characters", len(cleanTarget))
- }
-
- return nil
-}
-
-// extractContainerMetadata extracts runtime configuration from a Docker image
-func (d *DockerExecutor) extractContainerMetadata(ctx context.Context, logger *slog.Logger, imageName string) (*builderv1.ImageMetadata, error) {
- // AIDEV-NOTE: Extract container metadata for microvm execution
- logger.InfoContext(ctx, "extracting container metadata", slog.String("image", imageName))
-
- // Use docker inspect to get image configuration
- cmd := exec.CommandContext(ctx, "docker", "inspect", "--type=image", imageName)
- output, err := cmd.Output()
- if err != nil {
- logger.ErrorContext(ctx, "docker inspect failed",
- slog.String("error", err.Error()),
- slog.String("image", imageName),
- )
- return nil, fmt.Errorf("docker inspect failed: %w", err)
- }
-
- // Parse the JSON output
- var inspectResults []map[string]interface{}
- if err := json.Unmarshal(output, &inspectResults); err != nil {
- logger.ErrorContext(ctx, "failed to parse docker inspect output",
- slog.String("error", err.Error()),
- )
- return nil, fmt.Errorf("failed to parse docker inspect output: %w", err)
- }
-
- if len(inspectResults) == 0 {
- return nil, fmt.Errorf("no image data returned from docker inspect")
- }
-
- imageData := inspectResults[0]
- config, ok := imageData["Config"].(map[string]interface{})
- if !ok {
- return nil, fmt.Errorf("missing Config in docker inspect output")
- }
-
- // Extract runtime configuration
- metadata := &builderv1.ImageMetadata{
- OriginalImage: imageName,
- }
-
- // Extract entrypoint
- if entrypoint, ok := config["Entrypoint"].([]interface{}); ok {
- for _, e := range entrypoint {
- if str, ok := e.(string); ok {
- metadata.Entrypoint = append(metadata.Entrypoint, str)
- }
- }
- }
-
- // Extract command
- if cmd, ok := config["Cmd"].([]interface{}); ok {
- for _, c := range cmd {
- if str, ok := c.(string); ok {
- metadata.Command = append(metadata.Command, str)
- }
- }
- }
-
- // Extract working directory
- if workingDir, ok := config["WorkingDir"].(string); ok {
- metadata.WorkingDir = workingDir
- }
-
- // Extract environment variables
- if env, ok := config["Env"].([]interface{}); ok {
- metadata.Env = make(map[string]string)
- for _, e := range env {
- if str, ok := e.(string); ok {
- parts := strings.SplitN(str, "=", 2)
- if len(parts) == 2 {
- metadata.Env[parts[0]] = parts[1]
- }
- }
- }
- }
-
- // Extract exposed ports
- if exposedPorts, ok := config["ExposedPorts"].(map[string]interface{}); ok {
- for port := range exposedPorts {
- // Docker format is "port/protocol", extract just the port number
- parts := strings.Split(port, "/")
- if len(parts) > 0 {
- metadata.ExposedPorts = append(metadata.ExposedPorts, parts[0])
- }
- }
- }
-
- logger.InfoContext(ctx, "extracted container metadata",
- slog.Int("entrypoint_len", len(metadata.Entrypoint)),
- slog.Int("cmd_len", len(metadata.Command)),
- slog.String("working_dir", metadata.WorkingDir),
- slog.Int("env_vars", len(metadata.Env)),
- slog.Int("exposed_ports", len(metadata.ExposedPorts)),
- )
-
- return metadata, nil
-}
-
-// saveContainerMetadata saves the container metadata to a JSON file
-func (d *DockerExecutor) saveContainerMetadata(ctx context.Context, logger *slog.Logger, metadata *builderv1.ImageMetadata, path string) error {
- // AIDEV-NOTE: Save metadata for metald to use when configuring microvm
- logger.InfoContext(ctx, "saving container metadata", slog.String("path", path))
-
- // Marshal metadata to JSON
- data, err := json.MarshalIndent(metadata, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal metadata: %w", err)
- }
-
- // Write to file
- if err := os.WriteFile(path, data, 0644); err != nil {
- return fmt.Errorf("failed to write metadata file: %w", err)
- }
-
- logger.InfoContext(ctx, "container metadata saved",
- slog.String("path", path),
- slog.Int("size", len(data)),
- )
-
- return nil
-}
-
-// injectMetaldInit copies the metald-init binary into the rootfs
-// AIDEV-NOTE: This function ensures every container rootfs has metald-init available at /usr/bin/metald-init
-// This is critical for VM boot as the kernel expects init=/usr/bin/metald-init
-func (d *DockerExecutor) injectMetaldInit(ctx context.Context, logger *slog.Logger, rootfsDir string) error {
- logger.InfoContext(ctx, "injecting metald-init into rootfs",
- slog.String("rootfs_dir", rootfsDir),
- )
-
- // Source path for metald-init binary
- // Try multiple possible locations for metald-init
- var srcPaths = []string{
- "/usr/bin/metald-init", // Standard installation location
- "./cmd/metald-init/metald-init", // Local build
- "../metald/cmd/metald-init/metald-init", // Relative from builderd
- "/usr/local/bin/metald-init", // Legacy location (fallback)
- "/opt/metald/bin/metald-init", // Custom location
- }
-
- var srcPath string
- for _, path := range srcPaths {
- if _, err := os.Stat(path); err == nil {
- srcPath = path
- break
- }
- }
-
- if srcPath == "" {
- return fmt.Errorf("metald-init binary not found in any expected location: %v", srcPaths)
- }
-
- // Destination paths in rootfs
- usrBinDir := filepath.Join(rootfsDir, "usr", "bin")
- dstPath := filepath.Join(usrBinDir, "metald-init")
-
- // Create /usr/bin directory if it doesn't exist
- if err := os.MkdirAll(usrBinDir, 0755); err != nil {
- return fmt.Errorf("failed to create /usr/bin directory: %w", err)
- }
-
- // Copy metald-init binary
- srcData, err := os.ReadFile(srcPath)
- if err != nil {
- return fmt.Errorf("failed to read metald-init source: %w", err)
- }
-
- if err := os.WriteFile(dstPath, srcData, 0755); err != nil {
- return fmt.Errorf("failed to write metald-init to rootfs: %w", err)
- }
-
- logger.InfoContext(ctx, "metald-init injection completed",
- slog.String("src_path", srcPath),
- slog.String("dst_path", dstPath),
- slog.Int("size_bytes", len(srcData)),
- )
-
- return nil
-}
-
-// createContainerCmd creates /container.cmd file with the container's command for metald-init
-// AIDEV-NOTE: This function creates the command file that metald-init reads to know what to execute
-// The file contains a JSON array of the full command (entrypoint + command)
-func (d *DockerExecutor) createContainerCmd(ctx context.Context, logger *slog.Logger, rootfsDir string, metadata *builderv1.ImageMetadata) error {
- logger.InfoContext(ctx, "creating container command file",
- slog.String("rootfs_dir", rootfsDir),
- )
-
- // Build the full command from entrypoint + command
- var fullCmd []string
-
- // Add entrypoint if present
- if len(metadata.Entrypoint) > 0 {
- fullCmd = append(fullCmd, metadata.Entrypoint...)
- }
-
- // Add command if present
- if len(metadata.Command) > 0 {
- fullCmd = append(fullCmd, metadata.Command...)
- }
-
- // If no command specified, provide a default
- if len(fullCmd) == 0 {
- logger.WarnContext(ctx, "no entrypoint or command found, using default shell")
- fullCmd = []string{"/bin/sh"}
- }
-
- // Create the command file path
- cmdPath := filepath.Join(rootfsDir, "container.cmd")
-
- // Marshal command to JSON
- cmdData, err := json.Marshal(fullCmd)
- if err != nil {
- return fmt.Errorf("failed to marshal container command: %w", err)
- }
-
- // Write to file
- if err := os.WriteFile(cmdPath, cmdData, 0644); err != nil {
- return fmt.Errorf("failed to write container.cmd file: %w", err)
- }
-
- logger.InfoContext(ctx, "container command file created",
- slog.String("path", cmdPath),
- slog.Any("command", fullCmd),
- slog.Int("size", len(cmdData)),
- )
-
- return nil
-}
-
-// createContainerEnv creates environment configuration file for complete container runtime replication
-// AIDEV-NOTE: This function creates a comprehensive environment setup that metald-init reads
-// to replicate the exact container runtime environment including working directory, env vars, etc.
-func (d *DockerExecutor) createContainerEnv(ctx context.Context, logger *slog.Logger, rootfsDir string, metadata *builderv1.ImageMetadata) error {
- logger.InfoContext(ctx, "creating container environment file",
- slog.String("rootfs_dir", rootfsDir),
- )
-
- // Create comprehensive environment configuration
- envConfig := struct {
- WorkingDir string `json:"working_dir,omitempty"`
- Env map[string]string `json:"env,omitempty"`
- ExposedPorts []string `json:"exposed_ports,omitempty"`
- }{
- WorkingDir: metadata.WorkingDir,
- Env: metadata.Env,
- ExposedPorts: metadata.ExposedPorts,
- }
-
- // Set default working directory if not specified
- if envConfig.WorkingDir == "" {
- envConfig.WorkingDir = "/"
- }
-
- // Ensure essential environment variables are set
- if envConfig.Env == nil {
- envConfig.Env = make(map[string]string)
- }
-
- // Set default PATH if not present
- if _, hasPath := envConfig.Env["PATH"]; !hasPath {
- envConfig.Env["PATH"] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
- }
-
- // Create the environment file path
- envPath := filepath.Join(rootfsDir, "container.env")
-
- // Marshal environment config to JSON
- envData, err := json.MarshalIndent(envConfig, "", " ")
- if err != nil {
- return fmt.Errorf("failed to marshal container environment: %w", err)
- }
-
- // Write to file
- if err := os.WriteFile(envPath, envData, 0644); err != nil {
- return fmt.Errorf("failed to write container.env file: %w", err)
- }
-
- logger.InfoContext(ctx, "container environment file created",
- slog.String("path", envPath),
- slog.String("working_dir", envConfig.WorkingDir),
- slog.Int("env_vars", len(envConfig.Env)),
- slog.Int("size", len(envData)),
- )
-
- return nil
-}
diff --git a/go/apps/builderd/internal/executor/docker_pipeline.go b/go/apps/builderd/internal/executor/docker_pipeline.go
deleted file mode 100644
index 2ef20c0c28..0000000000
--- a/go/apps/builderd/internal/executor/docker_pipeline.go
+++ /dev/null
@@ -1,185 +0,0 @@
-package executor
-
-import (
- "context"
- "fmt"
- "log/slog"
- "os"
- "path/filepath"
- "time"
-
- "github.com/unkeyed/unkey/go/apps/builderd/internal/config"
- "github.com/unkeyed/unkey/go/apps/builderd/internal/observability"
- builderv1 "github.com/unkeyed/unkey/go/gen/proto/builderd/v1"
-)
-
-// DockerPipelineExecutor wraps the existing DockerExecutor with step-based execution
-type DockerPipelineExecutor struct {
- dockerExecutor *DockerExecutor
- pipeline *BuildPipeline
- logger *slog.Logger
- config *config.Config
- buildMetrics *observability.BuildMetrics
-}
-
-// NewDockerPipelineExecutor creates a new pipeline-based Docker executor
-func NewDockerPipelineExecutor(logger *slog.Logger, cfg *config.Config, metrics *observability.BuildMetrics) *DockerPipelineExecutor {
- dockerExecutor := NewDockerExecutor(logger, cfg, metrics)
- pipeline := NewDockerBuildPipeline(dockerExecutor)
-
- return &DockerPipelineExecutor{
- dockerExecutor: dockerExecutor,
- pipeline: pipeline,
- logger: logger,
- config: cfg,
- buildMetrics: metrics,
- }
-}
-
-// ExtractDockerImageWithID executes the full Docker build pipeline
-func (d *DockerPipelineExecutor) ExtractDockerImageWithID(ctx context.Context, request *builderv1.CreateBuildRequest, buildID string) (*BuildResult, error) {
- start := time.Now()
-
- logger := d.logger.With(
- slog.String("build_id", buildID),
- slog.String("image_uri", request.GetConfig().GetSource().GetDockerImage().GetImageUri()),
- )
-
- logger.InfoContext(ctx, "starting Docker pipeline build")
-
- // Record build start metrics
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildStart(ctx, "docker", "docker")
- }
-
- defer func() {
- duration := time.Since(start)
- logger.InfoContext(ctx, "Docker pipeline build completed", slog.Duration("duration", duration))
- }()
-
- dockerSource := request.GetConfig().GetSource().GetDockerImage()
- if dockerSource == nil {
- return nil, fmt.Errorf("docker image source is required")
- }
-
- // Setup directories
- workspaceDir := filepath.Join(d.config.Builder.WorkspaceDir, buildID)
- rootfsDir := filepath.Join(d.config.Builder.RootfsOutputDir, buildID)
-
- logger = logger.With(
- slog.String("workspace_dir", workspaceDir),
- slog.String("rootfs_dir", rootfsDir),
- )
-
- // Create directories
- if err := os.MkdirAll(workspaceDir, 0755); err != nil {
- logger.ErrorContext(ctx, "failed to create workspace directory", slog.String("error", err.Error()))
- return nil, fmt.Errorf("failed to create workspace directory: %w", err)
- }
-
- if err := os.MkdirAll(rootfsDir, 0755); err != nil {
- logger.ErrorContext(ctx, "failed to create rootfs directory", slog.String("error", err.Error()))
- return nil, fmt.Errorf("failed to create rootfs directory: %w", err)
- }
-
- // Prepare initial step input
- input := StepInput{
- BuildID: buildID,
- Config: request.GetConfig(),
- WorkspaceDir: workspaceDir,
- RootfsDir: rootfsDir,
- Logger: logger,
- }
-
- // Execute the pipeline
- result, err := d.pipeline.Execute(ctx, input)
- if err != nil {
- logger.ErrorContext(ctx, "pipeline execution failed", slog.String("error", err.Error()))
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildComplete(ctx, "docker", "docker", time.Since(start), false)
- }
- return nil, err
- }
-
- // Record success metrics
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildComplete(ctx, "docker", "docker", time.Since(start), true)
- }
-
- return result, nil
-}
-
-// ResumeBuild resumes a build from a specific step
-func (d *DockerPipelineExecutor) ResumeBuild(ctx context.Context, request *builderv1.CreateBuildRequest, buildID string, lastCompletedStep int) (*BuildResult, error) {
- start := time.Now()
-
- logger := d.logger.With(
- slog.String("build_id", buildID),
- slog.String("image_uri", request.GetConfig().GetSource().GetDockerImage().GetImageUri()),
- slog.Int("resume_from_step", lastCompletedStep+1),
- )
-
- logger.InfoContext(ctx, "resuming Docker pipeline build")
-
- // Setup directories (should already exist)
- workspaceDir := filepath.Join(d.config.Builder.WorkspaceDir, buildID)
- rootfsDir := filepath.Join(d.config.Builder.RootfsOutputDir, buildID)
-
- // Prepare step input - we'd need to reconstruct state from previous steps here
- // For simplicity, we're starting fresh but skipping completed steps
- input := StepInput{
- BuildID: buildID,
- Config: request.GetConfig(),
- WorkspaceDir: workspaceDir,
- RootfsDir: rootfsDir,
- Logger: logger,
- // TODO: Restore ImageName, ContainerID, Metadata from previous execution
- }
-
- // Resume from the next step after the last completed one
- result, err := d.pipeline.Resume(ctx, input, lastCompletedStep+1)
- if err != nil {
- logger.ErrorContext(ctx, "pipeline resumption failed", slog.String("error", err.Error()))
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildComplete(ctx, "docker", "docker", time.Since(start), false)
- }
- return nil, err
- }
-
- // Record success metrics
- if d.buildMetrics != nil {
- d.buildMetrics.RecordBuildComplete(ctx, "docker", "docker", time.Since(start), true)
- }
-
- return result, nil
-}
-
-// GetStepNames returns the names of all steps in the pipeline
-func (d *DockerPipelineExecutor) GetStepNames() []string {
- steps := make([]string, len(d.pipeline.steps))
- for i, step := range d.pipeline.steps {
- steps[i] = step.Name()
- }
- return steps
-}
-
-// Execute implements the Executor interface (generates build ID)
-func (d *DockerPipelineExecutor) Execute(ctx context.Context, request *builderv1.CreateBuildRequest) (*BuildResult, error) {
- // Generate build ID for backward compatibility
- return d.ExtractDockerImageWithID(ctx, request, generateBuildID())
-}
-
-// ExecuteWithID implements the Executor interface (uses provided build ID)
-func (d *DockerPipelineExecutor) ExecuteWithID(ctx context.Context, request *builderv1.CreateBuildRequest, buildID string) (*BuildResult, error) {
- return d.ExtractDockerImageWithID(ctx, request, buildID)
-}
-
-// GetSupportedSources implements the Executor interface
-func (d *DockerPipelineExecutor) GetSupportedSources() []string {
- return []string{"docker"}
-}
-
-// Cleanup implements the Executor interface - delegates to underlying DockerExecutor
-func (d *DockerPipelineExecutor) Cleanup(ctx context.Context, buildID string) error {
- return d.dockerExecutor.Cleanup(ctx, buildID)
-}
diff --git a/go/apps/builderd/internal/executor/docker_steps.go b/go/apps/builderd/internal/executor/docker_steps.go
deleted file mode 100644
index 69c3be1992..0000000000
--- a/go/apps/builderd/internal/executor/docker_steps.go
+++ /dev/null
@@ -1,175 +0,0 @@
-package executor
-
-import (
- "context"
- "log/slog"
-)
-
-// PullImageStep pulls the Docker image
-type PullImageStep struct {
- executor *DockerExecutor
-}
-
-func (s *PullImageStep) Name() string {
- return "pull_image"
-}
-
-func (s *PullImageStep) Execute(ctx context.Context, input StepInput) (StepOutput, error) {
- imageName := input.Config.GetSource().GetDockerImage().GetImageUri()
-
- input.Logger.InfoContext(ctx, "pulling Docker image", slog.String("image", imageName))
-
- err := s.executor.pullDockerImage(ctx, input.Logger, imageName)
- if err != nil {
- return StepOutput{Success: false, Error: err}, err
- }
-
- return StepOutput{
- ImageName: imageName,
- Success: true,
- }, nil
-}
-
-// CreateContainerStep creates a container from the image
-type CreateContainerStep struct {
- executor *DockerExecutor
-}
-
-func (s *CreateContainerStep) Name() string {
- return "create_container"
-}
-
-func (s *CreateContainerStep) Execute(ctx context.Context, input StepInput) (StepOutput, error) {
- input.Logger.InfoContext(ctx, "creating container", slog.String("image", input.ImageName))
-
- containerID, err := s.executor.createContainer(ctx, input.Logger, input.ImageName)
- if err != nil {
- return StepOutput{Success: false, Error: err}, err
- }
-
- return StepOutput{
- ImageName: input.ImageName,
- ContainerID: containerID,
- Success: true,
- }, nil
-}
-
-// ExtractMetadataStep extracts container metadata
-type ExtractMetadataStep struct {
- executor *DockerExecutor
-}
-
-func (s *ExtractMetadataStep) Name() string {
- return "extract_metadata"
-}
-
-func (s *ExtractMetadataStep) Execute(ctx context.Context, input StepInput) (StepOutput, error) {
- input.Logger.InfoContext(ctx, "extracting container metadata", slog.String("image", input.ImageName))
-
- metadata, err := s.executor.extractContainerMetadata(ctx, input.Logger, input.ImageName)
- if err != nil {
- return StepOutput{Success: false, Error: err}, err
- }
-
- return StepOutput{
- ImageName: input.ImageName,
- ContainerID: input.ContainerID,
- Metadata: metadata,
- Success: true,
- }, nil
-}
-
-// ExtractFilesystemStep extracts the container filesystem
-type ExtractFilesystemStep struct {
- executor *DockerExecutor
-}
-
-func (s *ExtractFilesystemStep) Name() string {
- return "extract_filesystem"
-}
-
-func (s *ExtractFilesystemStep) Execute(ctx context.Context, input StepInput) (StepOutput, error) {
- input.Logger.InfoContext(ctx, "extracting container filesystem",
- slog.String("container_id", input.ContainerID),
- slog.String("rootfs_dir", input.RootfsDir))
-
- err := s.executor.extractFilesystem(ctx, input.Logger, input.ContainerID, input.RootfsDir, input.Metadata)
- if err != nil {
- return StepOutput{Success: false, Error: err}, err
- }
-
- return StepOutput{
- ImageName: input.ImageName,
- ContainerID: input.ContainerID,
- Metadata: input.Metadata,
- RootfsPath: input.RootfsDir,
- Success: true,
- }, nil
-}
-
-// OptimizeRootfsStep optimizes the extracted rootfs
-type OptimizeRootfsStep struct {
- executor *DockerExecutor
-}
-
-func (s *OptimizeRootfsStep) Name() string {
- return "optimize_rootfs"
-}
-
-func (s *OptimizeRootfsStep) Execute(ctx context.Context, input StepInput) (StepOutput, error) {
- input.Logger.InfoContext(ctx, "optimizing rootfs", slog.String("rootfs_dir", input.RootfsDir))
-
- // Call existing optimization logic from DockerExecutor
- // This would include: creating metald-init, container command/env files, etc.
- err := s.executor.injectMetaldInit(ctx, input.Logger, input.RootfsDir)
- if err != nil {
- return StepOutput{Success: false, Error: err}, err
- }
-
- err = s.executor.createContainerCmd(ctx, input.Logger, input.RootfsDir, input.Metadata)
- if err != nil {
- return StepOutput{Success: false, Error: err}, err
- }
-
- err = s.executor.createContainerEnv(ctx, input.Logger, input.RootfsDir, input.Metadata)
- if err != nil {
- return StepOutput{Success: false, Error: err}, err
- }
-
- return StepOutput{
- ImageName: input.ImageName,
- ContainerID: input.ContainerID,
- Metadata: input.Metadata,
- RootfsPath: input.RootfsDir,
- Success: true,
- }, nil
-}
-
-// CleanupStep cleans up temporary resources
-type CleanupStep struct {
- executor *DockerExecutor
-}
-
-func (s *CleanupStep) Name() string {
- return "cleanup"
-}
-
-func (s *CleanupStep) Execute(ctx context.Context, input StepInput) (StepOutput, error) {
- input.Logger.InfoContext(ctx, "cleaning up container", slog.String("container_id", input.ContainerID))
-
- if input.ContainerID != "" {
- err := s.executor.removeContainer(ctx, input.Logger, input.ContainerID)
- if err != nil {
- // Log warning but don't fail the build for cleanup errors
- input.Logger.WarnContext(ctx, "failed to cleanup container", slog.String("error", err.Error()))
- }
- }
-
- return StepOutput{
- ImageName: input.ImageName,
- ContainerID: "", // Container is now removed
- Metadata: input.Metadata,
- RootfsPath: input.RootfsDir,
- Success: true,
- }, nil
-}
diff --git a/go/apps/builderd/internal/executor/registry.go b/go/apps/builderd/internal/executor/registry.go
deleted file mode 100644
index 61bbf14a12..0000000000
--- a/go/apps/builderd/internal/executor/registry.go
+++ /dev/null
@@ -1,231 +0,0 @@
-package executor
-
-import (
- "context"
- "fmt"
- "log/slog"
- "sync"
-
- "github.com/unkeyed/unkey/go/apps/builderd/internal/config"
- "github.com/unkeyed/unkey/go/apps/builderd/internal/observability"
- builderv1 "github.com/unkeyed/unkey/go/gen/proto/builderd/v1"
-)
-
-// Registry manages different build executors
-type Registry struct {
- logger *slog.Logger
- config *config.Config
- executors map[string]Executor
- mutex sync.RWMutex
-}
-
-// NewRegistry creates a new executor registry
-func NewRegistry(logger *slog.Logger, cfg *config.Config, buildMetrics *observability.BuildMetrics) *Registry {
- registry := &Registry{ //nolint:exhaustruct // mutex is zero-value initialized and doesn't need explicit initialization
- logger: logger,
- config: cfg,
- executors: make(map[string]Executor),
- }
-
- // Register built-in executors
- registry.registerBuiltinExecutors(buildMetrics)
-
- return registry
-}
-
-// registerBuiltinExecutors registers the standard executors
-func (r *Registry) registerBuiltinExecutors(buildMetrics *observability.BuildMetrics) {
- // Register Docker executor based on feature flag
- if r.config.Builder.UsePipelineExecutor {
- r.logger.InfoContext(context.Background(), "using step-based pipeline executor for Docker builds")
- pipelineExecutor := NewDockerPipelineExecutor(r.logger, r.config, buildMetrics)
- r.RegisterExecutor("docker", pipelineExecutor)
- } else {
- r.logger.InfoContext(context.Background(), "using monolithic executor for Docker builds")
- dockerExecutor := NewDockerExecutor(r.logger, r.config, buildMetrics)
- r.RegisterExecutor("docker", dockerExecutor)
- }
-
- // TODO: Register other executors
- // gitExecutor := NewGitExecutor(r.logger, r.config, buildMetrics)
- // r.RegisterExecutor("git", gitExecutor)
-
- // archiveExecutor := NewArchiveExecutor(r.logger, r.config, buildMetrics)
- // r.RegisterExecutor("archive", archiveExecutor)
-
- r.logger.InfoContext(context.Background(), "registered built-in executors",
- slog.Int("executor_count", len(r.executors)),
- slog.Bool("pipeline_mode", r.config.Builder.UsePipelineExecutor),
- )
-}
-
-// RegisterExecutor registers a new executor for a source type
-func (r *Registry) RegisterExecutor(sourceType string, executor Executor) {
- r.mutex.Lock()
- defer r.mutex.Unlock()
-
- r.executors[sourceType] = executor
- r.logger.InfoContext(context.Background(), "registered executor", slog.String("source_type", sourceType))
-}
-
-// GetExecutor returns the executor for a given source type
-func (r *Registry) GetExecutor(sourceType string) (Executor, error) {
- r.mutex.RLock()
- defer r.mutex.RUnlock()
-
- executor, exists := r.executors[sourceType]
- if !exists {
- r.logger.ErrorContext(context.Background(), "no executor found for source type",
- slog.String("source_type", sourceType),
- slog.Any("available_types", r.GetSupportedSources()),
- )
- return nil, fmt.Errorf("no executor found for source type: %s", sourceType)
- }
-
- return executor, nil
-}
-
-// Execute processes a build request using the appropriate executor
-func (r *Registry) Execute(ctx context.Context, request *builderv1.CreateBuildRequest) (*BuildResult, error) {
- // Determine source type from request
- sourceType, err := r.getSourceTypeFromRequest(request)
- if err != nil {
- return nil, fmt.Errorf("failed to determine source type: %w", err)
- }
-
- // Get appropriate executor
- executor, err := r.GetExecutor(sourceType)
- if err != nil {
- return nil, fmt.Errorf("failed to get executor: %w", err)
- }
-
- r.logger.InfoContext(ctx, "executing build request",
- slog.String("source_type", sourceType),
- )
-
- // Execute the build
- result, err := executor.Execute(ctx, request)
- if err != nil {
- r.logger.ErrorContext(ctx, "build execution failed",
- slog.String("source_type", sourceType),
- slog.String("error", err.Error()),
- )
- return nil, fmt.Errorf("build execution failed: %w", err)
- }
-
- r.logger.InfoContext(ctx, "build execution completed",
- slog.String("source_type", sourceType),
- slog.String("build_id", result.BuildID),
- slog.String("status", result.Status),
- )
-
- return result, nil
-}
-
-// ExecuteWithID processes a build request with a pre-assigned build ID
-func (r *Registry) ExecuteWithID(ctx context.Context, request *builderv1.CreateBuildRequest, buildID string) (*BuildResult, error) {
- // Determine source type from request
- sourceType, err := r.getSourceTypeFromRequest(request)
- if err != nil {
- return nil, fmt.Errorf("failed to determine source type: %w", err)
- }
-
- // Get appropriate executor
- executor, err := r.GetExecutor(sourceType)
- if err != nil {
- return nil, fmt.Errorf("failed to get executor: %w", err)
- }
-
- r.logger.InfoContext(ctx, "executing build request with ID",
- slog.String("source_type", sourceType),
- slog.String("build_id", buildID),
- )
-
- // Execute the build with the provided ID
- result, err := executor.ExecuteWithID(ctx, request, buildID)
- if err != nil {
- r.logger.ErrorContext(ctx, "build execution failed",
- slog.String("source_type", sourceType),
- slog.String("build_id", buildID),
- slog.String("error", err.Error()),
- )
- return nil, fmt.Errorf("build execution failed: %w", err)
- }
-
- r.logger.InfoContext(ctx, "build execution completed",
- slog.String("source_type", sourceType),
- slog.String("build_id", result.BuildID),
- slog.String("status", result.Status),
- )
-
- return result, nil
-}
-
-// getSourceTypeFromRequest determines the source type from the build request
-func (r *Registry) getSourceTypeFromRequest(request *builderv1.CreateBuildRequest) (string, error) {
- if request.GetConfig() == nil || request.GetConfig().GetSource() == nil {
- r.logger.ErrorContext(context.Background(), "build source is required but missing")
- return "", fmt.Errorf("build source is required")
- }
-
- switch source := request.GetConfig().GetSource().GetSourceType().(type) {
- case *builderv1.BuildSource_DockerImage:
- return "docker", nil
- case *builderv1.BuildSource_GitRepository:
- return "git", nil
- case *builderv1.BuildSource_Archive:
- return "archive", nil
- default:
- r.logger.ErrorContext(context.Background(), "unsupported source type",
- slog.String("type", fmt.Sprintf("%T", source)),
- )
- return "", fmt.Errorf("unsupported source type: %T", source)
- }
-}
-
-// ListExecutors returns a list of registered executors
-func (r *Registry) ListExecutors() []string {
- r.mutex.RLock()
- defer r.mutex.RUnlock()
-
- executors := make([]string, 0, len(r.executors))
- for sourceType := range r.executors {
- executors = append(executors, sourceType)
- }
-
- return executors
-}
-
-// Cleanup removes temporary resources for all executors
-func (r *Registry) Cleanup(ctx context.Context, buildID string) error {
- r.mutex.RLock()
- defer r.mutex.RUnlock()
-
- var lastError error
-
- for sourceType, executor := range r.executors {
- if err := executor.Cleanup(ctx, buildID); err != nil {
- r.logger.WarnContext(ctx, "executor cleanup failed",
- slog.String("source_type", sourceType),
- slog.String("build_id", buildID),
- slog.String("error", err.Error()),
- )
- lastError = err
- }
- }
-
- return lastError
-}
-
-// GetSupportedSources returns all supported source types
-func (r *Registry) GetSupportedSources() []string {
- r.mutex.RLock()
- defer r.mutex.RUnlock()
-
- sources := make([]string, 0, len(r.executors))
- for sourceType := range r.executors {
- sources = append(sources, sourceType)
- }
-
- return sources
-}
diff --git a/go/apps/builderd/internal/executor/steps.go b/go/apps/builderd/internal/executor/steps.go
deleted file mode 100644
index 88e962bc68..0000000000
--- a/go/apps/builderd/internal/executor/steps.go
+++ /dev/null
@@ -1,121 +0,0 @@
-package executor
-
-import (
- "context"
- "log/slog"
-
- builderv1 "github.com/unkeyed/unkey/go/gen/proto/builderd/v1"
-)
-
-// StepInput contains the input data for a build step
-type StepInput struct {
- BuildID string
- Config *builderv1.BuildConfig
- WorkspaceDir string
- RootfsDir string
- Logger *slog.Logger
-
- // Output from previous steps
- ImageName string
- ContainerID string
- Metadata *builderv1.ImageMetadata
-}
-
-// StepOutput contains the output data from a build step
-type StepOutput struct {
- // Data to pass to next step
- ImageName string
- ContainerID string
- Metadata *builderv1.ImageMetadata
- RootfsPath string
-
- // Step completion info
- Success bool
- Error error
-}
-
-// StepExecutor represents a single build step
-type StepExecutor interface {
- Execute(ctx context.Context, input StepInput) (StepOutput, error)
- Name() string
-}
-
-// BuildPipeline represents a sequence of build steps
-type BuildPipeline struct {
- steps []StepExecutor
-}
-
-// NewDockerBuildPipeline creates a pipeline for Docker image builds
-func NewDockerBuildPipeline(dockerExecutor *DockerExecutor) *BuildPipeline {
- return &BuildPipeline{
- steps: []StepExecutor{
- &PullImageStep{executor: dockerExecutor},
- &CreateContainerStep{executor: dockerExecutor},
- &ExtractMetadataStep{executor: dockerExecutor},
- &ExtractFilesystemStep{executor: dockerExecutor},
- &OptimizeRootfsStep{executor: dockerExecutor},
- &CleanupStep{executor: dockerExecutor},
- },
- }
-}
-
-// Execute runs the entire pipeline
-func (p *BuildPipeline) Execute(ctx context.Context, initialInput StepInput) (*BuildResult, error) {
- input := initialInput
-
- for i, step := range p.steps {
- initialInput.Logger.InfoContext(ctx, "executing build step",
- slog.String("step", step.Name()),
- slog.Int("step_index", i),
- slog.Int("total_steps", len(p.steps)),
- )
-
- output, err := step.Execute(ctx, input)
- if err != nil {
- return nil, err
- }
-
- // Prepare input for next step
- input.ImageName = output.ImageName
- input.ContainerID = output.ContainerID
- input.Metadata = output.Metadata
- }
-
- // Return final build result
- return &BuildResult{
- RootfsPath: input.RootfsDir,
- ImageMetadata: input.Metadata,
- }, nil
-}
-
-// Resume executes the pipeline starting from a specific step
-func (p *BuildPipeline) Resume(ctx context.Context, input StepInput, startStepIndex int) (*BuildResult, error) {
- input.Logger.InfoContext(ctx, "resuming build pipeline",
- slog.Int("start_step", startStepIndex),
- slog.Int("total_steps", len(p.steps)),
- )
-
- for i := startStepIndex; i < len(p.steps); i++ {
- step := p.steps[i]
-
- input.Logger.InfoContext(ctx, "executing build step (resumed)",
- slog.String("step", step.Name()),
- slog.Int("step_index", i),
- )
-
- output, err := step.Execute(ctx, input)
- if err != nil {
- return nil, err
- }
-
- // Prepare input for next step
- input.ImageName = output.ImageName
- input.ContainerID = output.ContainerID
- input.Metadata = output.Metadata
- }
-
- return &BuildResult{
- RootfsPath: input.RootfsDir,
- ImageMetadata: input.Metadata,
- }, nil
-}
diff --git a/go/apps/builderd/internal/executor/types.go b/go/apps/builderd/internal/executor/types.go
deleted file mode 100644
index 17769e6afc..0000000000
--- a/go/apps/builderd/internal/executor/types.go
+++ /dev/null
@@ -1,128 +0,0 @@
-package executor
-
-import (
- "context"
- "time"
-
- builderv1 "github.com/unkeyed/unkey/go/gen/proto/builderd/v1"
-)
-
-// Executor defines the interface for build executors
-type Executor interface {
- // Execute processes a build request and returns the result
- Execute(ctx context.Context, request *builderv1.CreateBuildRequest) (*BuildResult, error)
-
- // ExecuteWithID processes a build request with a pre-assigned build ID
- ExecuteWithID(ctx context.Context, request *builderv1.CreateBuildRequest, buildID string) (*BuildResult, error)
-
- // GetSupportedSources returns the source types this executor supports
- GetSupportedSources() []string
-
- // Cleanup removes any temporary resources for the given build
- Cleanup(ctx context.Context, buildID string) error
-}
-
-// BuildResult represents the result of a build operation
-type BuildResult struct {
- // BuildID is the unique identifier for this build
- BuildID string
-
- // SourceType indicates the type of source (docker, git, archive)
- SourceType string
-
- // SourceImage/URL is the original source reference
- SourceImage string
-
- // RootfsPath is the path to the extracted rootfs
- RootfsPath string
-
- // WorkspaceDir is the temporary workspace directory
- WorkspaceDir string
-
- // StartTime when the build began
- StartTime time.Time
-
- // EndTime when the build completed
- EndTime time.Time
-
- // Status of the build (completed, failed, in_progress)
- Status string
-
- // Error message if the build failed
- Error string
-
- // Metadata contains additional build information
- Metadata map[string]string
-
- // ImageMetadata contains container runtime configuration
- ImageMetadata *builderv1.ImageMetadata
-
- // Metrics contains build performance metrics
- Metrics BuildMetrics
-}
-
-// BuildMetrics contains performance and resource metrics for a build
-type BuildMetrics struct {
- // DurationMs is the total build time in milliseconds
- DurationMs int64
-
- // RootfsSizeBytes is the final size of the rootfs in bytes
- RootfsSizeBytes int64
-
- // SourceSizeBytes is the original source size in bytes
- SourceSizeBytes int64
-
- // CompressionRatio is the compression ratio achieved (if applicable)
- CompressionRatio float64
-
- // FilesCount is the number of files in the rootfs
- FilesCount int64
-
- // CacheHit indicates if the build used cached results
- CacheHit bool
-}
-
-// BuildStatus represents the possible states of a build
-type BuildStatus string
-
-const (
- BuildStatusPending BuildStatus = "pending"
- BuildStatusInProgress BuildStatus = "in_progress"
- BuildStatusCompleted BuildStatus = "completed"
- BuildStatusFailed BuildStatus = "failed"
- BuildStatusCancelled BuildStatus = "cancelled"
-)
-
-// BuildError represents different types of build errors
-type BuildError struct {
- Type string `json:"type"`
- Message string `json:"message"`
- Details string `json:"details,omitempty"`
-}
-
-// Common build error types
-const (
- ErrorTypeSourceNotFound = "source_not_found"
- ErrorTypeSourceTooLarge = "source_too_large"
- ErrorTypeExtractionFailed = "extraction_failed"
- ErrorTypePermissionDenied = "permission_denied"
- ErrorTypeQuotaExceeded = "quota_exceeded"
- ErrorTypeTimeout = "timeout"
- ErrorTypeInternalError = "internal_error"
-)
-
-// NewBuildError creates a new build error
-func NewBuildError(errorType, message string) *BuildError {
- return &BuildError{ //nolint:exhaustruct // Details field is optional and can be added via WithDetails() method
- Type: errorType,
- Message: message,
- }
-}
-
-// Error implements the error interface
-func (e *BuildError) Error() string {
- if e.Details != "" {
- return e.Message + ": " + e.Details
- }
- return e.Message
-}
diff --git a/go/apps/builderd/internal/observability/metrics.go b/go/apps/builderd/internal/observability/metrics.go
deleted file mode 100644
index b0048172ef..0000000000
--- a/go/apps/builderd/internal/observability/metrics.go
+++ /dev/null
@@ -1,446 +0,0 @@
-package observability
-
-import (
- "context"
- "log/slog"
- "time"
-
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/metric"
-)
-
-// BuildMetrics provides instrumentation for build operations
-type BuildMetrics struct {
- // Counters
- buildsTotal metric.Int64Counter
- buildErrorsTotal metric.Int64Counter
- buildCancellations metric.Int64Counter
-
- // Histograms
- buildDuration metric.Float64Histogram
- pullDuration metric.Float64Histogram
- extractDuration metric.Float64Histogram
- optimizeDuration metric.Float64Histogram
-
- // Gauges
- activeBuilds metric.Int64UpDownCounter
- queuedBuilds metric.Int64UpDownCounter
-
- // Size metrics
- imageSizeBytes metric.Int64Histogram
- rootfsSizeBytes metric.Int64Histogram
- compressionRatio metric.Float64Histogram
-
- // Resource usage
- buildMemoryUsage metric.Int64Histogram
- buildDiskUsage metric.Int64Histogram
- buildCPUUsage metric.Float64Histogram
-
- // Build step counters
- buildStepsTotal metric.Int64Counter
- buildStepErrors metric.Int64Counter
- buildStepDuration metric.Float64Histogram
-
- // Base asset initialization metrics
- baseAssetInitRetries metric.Int64Counter
- baseAssetInitFailures metric.Int64Counter
-
- highCardinalityEnabled bool
- logger *slog.Logger
-}
-
-// NewBuildMetrics creates a new BuildMetrics instance
-func NewBuildMetrics(logger *slog.Logger, highCardinalityEnabled bool) (*BuildMetrics, error) {
- meter := otel.Meter("builderd")
-
- metrics := &BuildMetrics{ //nolint:exhaustruct // Metric fields are initialized individually below after error checking
- highCardinalityEnabled: highCardinalityEnabled,
- logger: logger,
- }
-
- var err error
-
- // Build counters
- metrics.buildsTotal, err = meter.Int64Counter(
- "builderd_builds_total",
- metric.WithDescription("Total number of builds started"),
- metric.WithUnit("1"),
- )
- if err != nil {
- return nil, err
- }
-
- metrics.buildErrorsTotal, err = meter.Int64Counter(
- "builderd_build_errors_total",
- metric.WithDescription("Total number of build failures"),
- metric.WithUnit("1"),
- )
- if err != nil {
- return nil, err
- }
-
- metrics.buildCancellations, err = meter.Int64Counter(
- "builderd_build_cancellations_total",
- metric.WithDescription("Total number of build cancellations"),
- metric.WithUnit("1"),
- )
- if err != nil {
- return nil, err
- }
-
- // Duration histograms
- metrics.buildDuration, err = meter.Float64Histogram(
- "builderd_build_duration_seconds",
- metric.WithDescription("Time taken to complete builds"),
- metric.WithUnit("s"),
- metric.WithExplicitBucketBoundaries(
- 1, 5, 10, 30, 60, 120, 300, 600, 900, 1800, 3600,
- ),
- )
- if err != nil {
- return nil, err
- }
-
- metrics.pullDuration, err = meter.Float64Histogram(
- "builderd_pull_duration_seconds",
- metric.WithDescription("Time taken to pull images/sources"),
- metric.WithUnit("s"),
- metric.WithExplicitBucketBoundaries(
- 1, 5, 10, 30, 60, 120, 300, 600,
- ),
- )
- if err != nil {
- return nil, err
- }
-
- metrics.extractDuration, err = meter.Float64Histogram(
- "builderd_extract_duration_seconds",
- metric.WithDescription("Time taken to extract image layers"),
- metric.WithUnit("s"),
- metric.WithExplicitBucketBoundaries(
- 0.1, 0.5, 1, 5, 10, 30, 60, 120,
- ),
- )
- if err != nil {
- return nil, err
- }
-
- metrics.optimizeDuration, err = meter.Float64Histogram(
- "builderd_optimize_duration_seconds",
- metric.WithDescription("Time taken to optimize rootfs"),
- metric.WithUnit("s"),
- metric.WithExplicitBucketBoundaries(
- 0.1, 0.5, 1, 5, 10, 30, 60,
- ),
- )
- if err != nil {
- return nil, err
- }
-
- // Gauges
- metrics.activeBuilds, err = meter.Int64UpDownCounter(
- "builderd_active_builds",
- metric.WithDescription("Number of currently active builds"),
- metric.WithUnit("1"),
- )
- if err != nil {
- return nil, err
- }
-
- metrics.queuedBuilds, err = meter.Int64UpDownCounter(
- "builderd_queued_builds",
- metric.WithDescription("Number of queued builds"),
- metric.WithUnit("1"),
- )
- if err != nil {
- return nil, err
- }
-
- // Size metrics
- metrics.imageSizeBytes, err = meter.Int64Histogram(
- "builderd_image_size_bytes",
- metric.WithDescription("Size of source images in bytes"),
- metric.WithUnit("By"),
- metric.WithExplicitBucketBoundaries(
- 1<<20, 10<<20, 50<<20, 100<<20, 500<<20, // 1MB to 500MB
- 1<<30, 2<<30, 5<<30, 10<<30, // 1GB to 10GB
- ),
- )
- if err != nil {
- return nil, err
- }
-
- metrics.rootfsSizeBytes, err = meter.Int64Histogram(
- "builderd_rootfs_size_bytes",
- metric.WithDescription("Size of generated rootfs in bytes"),
- metric.WithUnit("By"),
- metric.WithExplicitBucketBoundaries(
- 1<<20, 10<<20, 50<<20, 100<<20, 500<<20, // 1MB to 500MB
- 1<<30, 2<<30, 5<<30, // 1GB to 5GB
- ),
- )
- if err != nil {
- return nil, err
- }
-
- metrics.compressionRatio, err = meter.Float64Histogram(
- "builderd_compression_ratio",
- metric.WithDescription("Compression ratio (original/final size)"),
- metric.WithUnit("1"),
- metric.WithExplicitBucketBoundaries(
- 0.1, 0.2, 0.5, 0.7, 0.8, 0.9, 1.0, 1.2, 1.5, 2.0,
- ),
- )
- if err != nil {
- return nil, err
- }
-
- // Resource usage
- metrics.buildMemoryUsage, err = meter.Int64Histogram(
- "builderd_build_memory_usage_bytes",
- metric.WithDescription("Peak memory usage during builds"),
- metric.WithUnit("By"),
- metric.WithExplicitBucketBoundaries(
- 100<<20, 500<<20, // 100MB, 500MB
- 1<<30, 2<<30, 4<<30, 8<<30, // 1GB, 2GB, 4GB, 8GB
- ),
- )
- if err != nil {
- return nil, err
- }
-
- metrics.buildDiskUsage, err = meter.Int64Histogram(
- "builderd_build_disk_usage_bytes",
- metric.WithDescription("Peak disk usage during builds"),
- metric.WithUnit("By"),
- metric.WithExplicitBucketBoundaries(
- 100<<20, 500<<20, // 100MB, 500MB
- 1<<30, 5<<30, 10<<30, 50<<30, // 1GB, 5GB, 10GB, 50GB
- ),
- )
- if err != nil {
- return nil, err
- }
-
- metrics.buildCPUUsage, err = meter.Float64Histogram(
- "builderd_build_cpu_usage_cores",
- metric.WithDescription("CPU cores utilized during builds"),
- metric.WithUnit("1"),
- metric.WithExplicitBucketBoundaries(
- 0.1, 0.5, 1.0, 2.0, 4.0, 8.0, 16.0,
- ),
- )
- if err != nil {
- return nil, err
- }
-
- // Build step metrics
- metrics.buildStepsTotal, err = meter.Int64Counter(
- "builderd_build_steps_total",
- metric.WithDescription("Total number of build steps executed"),
- metric.WithUnit("1"),
- )
- if err != nil {
- return nil, err
- }
-
- metrics.buildStepErrors, err = meter.Int64Counter(
- "builderd_build_step_errors_total",
- metric.WithDescription("Total number of build step errors"),
- metric.WithUnit("1"),
- )
- if err != nil {
- return nil, err
- }
-
- metrics.buildStepDuration, err = meter.Float64Histogram(
- "builderd_build_step_duration_seconds",
- metric.WithDescription("Duration of individual build steps"),
- metric.WithUnit("s"),
- metric.WithExplicitBucketBoundaries(
- 0.1, 0.5, 1, 5, 10, 30, 60, 120, 300,
- ),
- )
- if err != nil {
- return nil, err
- }
-
- // Base asset initialization metrics
- metrics.baseAssetInitRetries, err = meter.Int64Counter(
- "builderd_base_asset_init_retries_total",
- metric.WithDescription("Total number of base asset initialization retries"),
- metric.WithUnit("1"),
- )
- if err != nil {
- return nil, err
- }
-
- metrics.baseAssetInitFailures, err = meter.Int64Counter(
- "builderd_base_asset_init_failures_total",
- metric.WithDescription("Total number of base asset initialization final failures"),
- metric.WithUnit("1"),
- )
- if err != nil {
- return nil, err
- }
-
- logger.Info("build metrics initialized",
- slog.Bool("high_cardinality_enabled", highCardinalityEnabled),
- )
-
- return metrics, nil
-}
-
-// RecordBuildStart records the start of a build
-func (m *BuildMetrics) RecordBuildStart(ctx context.Context, buildType, sourceType string) {
-
- attrs := []attribute.KeyValue{
- attribute.String("build_type", buildType),
- attribute.String("source_type", sourceType),
- }
-
- m.buildsTotal.Add(ctx, 1, metric.WithAttributes(attrs...))
- m.activeBuilds.Add(ctx, 1, metric.WithAttributes(attrs...))
-}
-
-// RecordBuildComplete records the completion of a build
-func (m *BuildMetrics) RecordBuildComplete(ctx context.Context, buildType, sourceType string, duration time.Duration, success bool) {
- attrs := []attribute.KeyValue{
- attribute.String("build_type", buildType),
- attribute.String("source_type", sourceType),
- attribute.String("status", func() string {
- if success {
- return "success"
- }
- return "failure"
- }()),
- }
-
- m.buildDuration.Record(ctx, duration.Seconds(), metric.WithAttributes(attrs...))
- m.activeBuilds.Add(ctx, -1, metric.WithAttributes(attrs...))
-
- if !success {
- m.buildErrorsTotal.Add(ctx, 1, metric.WithAttributes(attrs...))
- }
-}
-
-// RecordBuildCancellation records a build cancellation
-func (m *BuildMetrics) RecordBuildCancellation(ctx context.Context, buildType, sourceType string) {
- attrs := []attribute.KeyValue{
- attribute.String("build_type", buildType),
- attribute.String("source_type", sourceType),
- }
-
- m.buildCancellations.Add(ctx, 1, metric.WithAttributes(attrs...))
- m.activeBuilds.Add(ctx, -1, metric.WithAttributes(attrs...))
-}
-
-// RecordPullDuration records the time taken to pull source
-func (m *BuildMetrics) RecordPullDuration(ctx context.Context, sourceType string, duration time.Duration) {
- attrs := []attribute.KeyValue{
- attribute.String("source_type", sourceType),
- }
-
- m.pullDuration.Record(ctx, duration.Seconds(), metric.WithAttributes(attrs...))
-}
-
-// RecordExtractDuration records the time taken to extract
-func (m *BuildMetrics) RecordExtractDuration(ctx context.Context, sourceType string, duration time.Duration) {
- attrs := []attribute.KeyValue{
- attribute.String("source_type", sourceType),
- }
-
- m.extractDuration.Record(ctx, duration.Seconds(), metric.WithAttributes(attrs...))
-}
-
-// RecordOptimizeDuration records the time taken to optimize
-func (m *BuildMetrics) RecordOptimizeDuration(ctx context.Context, duration time.Duration) {
- m.optimizeDuration.Record(ctx, duration.Seconds())
-}
-
-// RecordImageSize records the size of source images
-func (m *BuildMetrics) RecordImageSize(ctx context.Context, sourceType string, sizeBytes int64) {
- attrs := []attribute.KeyValue{
- attribute.String("source_type", sourceType),
- }
-
- m.imageSizeBytes.Record(ctx, sizeBytes, metric.WithAttributes(attrs...))
-}
-
-// RecordRootfsSize records the size of generated rootfs
-func (m *BuildMetrics) RecordRootfsSize(ctx context.Context, sizeBytes int64) {
- m.rootfsSizeBytes.Record(ctx, sizeBytes)
-}
-
-// RecordCompressionRatio records the compression ratio achieved
-func (m *BuildMetrics) RecordCompressionRatio(ctx context.Context, ratio float64) {
- m.compressionRatio.Record(ctx, ratio)
-}
-
-// RecordResourceUsage records peak resource usage during build
-func (m *BuildMetrics) RecordResourceUsage(ctx context.Context, memoryBytes, diskBytes int64, cpuCores float64) {
- m.buildMemoryUsage.Record(ctx, memoryBytes)
- m.buildDiskUsage.Record(ctx, diskBytes)
- m.buildCPUUsage.Record(ctx, cpuCores)
-}
-
-// RecordQueuedBuild records a build being queued
-func (m *BuildMetrics) RecordQueuedBuild(ctx context.Context) {
- m.queuedBuilds.Add(ctx, 1)
-}
-
-// RecordDequeuedBuild records a build being dequeued
-func (m *BuildMetrics) RecordDequeuedBuild(ctx context.Context) {
- m.queuedBuilds.Add(ctx, -1)
-}
-
-// RecordBuildStepStart records the start of a build step
-func (m *BuildMetrics) RecordBuildStepStart(ctx context.Context, stepName, sourceType string) {
- attrs := []attribute.KeyValue{
- attribute.String("step", stepName),
- attribute.String("source_type", sourceType),
- }
-
- m.buildStepsTotal.Add(ctx, 1, metric.WithAttributes(attrs...))
-}
-
-// RecordBuildStepComplete records the completion of a build step
-func (m *BuildMetrics) RecordBuildStepComplete(ctx context.Context, stepName, sourceType string, duration time.Duration, success bool) {
- attrs := []attribute.KeyValue{
- attribute.String("step", stepName),
- attribute.String("source_type", sourceType),
- attribute.String("status", func() string {
- if success {
- return "success"
- }
- return "failure"
- }()),
- }
-
- m.buildStepDuration.Record(ctx, duration.Seconds(), metric.WithAttributes(attrs...))
-
- if !success {
- m.buildStepErrors.Add(ctx, 1, metric.WithAttributes(attrs...))
- }
-}
-
-// RecordBaseAssetInitRetry records a retry attempt for base asset initialization
-func (m *BuildMetrics) RecordBaseAssetInitRetry(ctx context.Context, attempt int, reason string) {
- attrs := []attribute.KeyValue{
- attribute.Int("attempt", attempt),
- attribute.String("reason", reason),
- }
-
- m.baseAssetInitRetries.Add(ctx, 1, metric.WithAttributes(attrs...))
-}
-
-// RecordBaseAssetInitFailure records a final failure of base asset initialization after all retries
-func (m *BuildMetrics) RecordBaseAssetInitFailure(ctx context.Context, totalAttempts int, finalError string) {
- attrs := []attribute.KeyValue{
- attribute.Int("total_attempts", totalAttempts),
- attribute.String("final_error", finalError),
- }
-
- m.baseAssetInitFailures.Add(ctx, 1, metric.WithAttributes(attrs...))
-}
diff --git a/go/apps/builderd/internal/observability/otel.go b/go/apps/builderd/internal/observability/otel.go
deleted file mode 100644
index b5eee4085b..0000000000
--- a/go/apps/builderd/internal/observability/otel.go
+++ /dev/null
@@ -1,224 +0,0 @@
-package observability
-
-import (
- "context"
- "errors"
- "fmt"
- "net/http"
- "time"
-
- "github.com/unkeyed/unkey/go/apps/builderd/internal/config"
-
- "github.com/prometheus/client_golang/prometheus/promhttp"
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/codes"
- "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
- "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
- "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
- "go.opentelemetry.io/otel/exporters/prometheus"
- "go.opentelemetry.io/otel/metric"
- "go.opentelemetry.io/otel/propagation"
- sdkmetric "go.opentelemetry.io/otel/sdk/metric"
- "go.opentelemetry.io/otel/sdk/resource"
- sdktrace "go.opentelemetry.io/otel/sdk/trace"
- semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
- "go.opentelemetry.io/otel/trace"
- "go.opentelemetry.io/otel/trace/noop"
-)
-
-// Providers holds the OpenTelemetry providers
-type Providers struct {
- TracerProvider trace.TracerProvider
- MeterProvider metric.MeterProvider
- PrometheusHTTP http.Handler
- Shutdown func(context.Context) error
-}
-
-// InitProviders initializes OpenTelemetry providers
-func InitProviders(ctx context.Context, cfg *config.Config, version string) (*Providers, error) {
- if !cfg.OpenTelemetry.Enabled {
- // Return no-op providers
- return &Providers{
- TracerProvider: noop.NewTracerProvider(),
- MeterProvider: nil,
- PrometheusHTTP: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusNotFound)
- _, _ = w.Write([]byte("OpenTelemetry is disabled"))
- }),
- Shutdown: func(context.Context) error { return nil },
- }, nil
- }
-
- // Schema conflict fix - Using semconv v1.24.0 with OTEL v1.36.0
- res, err := resource.New(ctx,
- resource.WithAttributes(
- semconv.ServiceNamespace("unkey"),
- semconv.ServiceName(cfg.OpenTelemetry.ServiceName),
- semconv.ServiceVersion(version),
- ),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create OTEL resource: %w", err)
- }
-
- // Initialize trace provider
- tracerProvider, tracerShutdown, err := initTracerProvider(ctx, cfg, res)
- if err != nil {
- return nil, fmt.Errorf("failed to initialize tracer provider: %w", err)
- }
-
- // Initialize meter provider
- meterProvider, promHandler, meterShutdown, err := initMeterProvider(ctx, cfg, res)
- if err != nil {
- _ = tracerShutdown(ctx)
- return nil, fmt.Errorf("failed to initialize meter provider: %w", err)
- }
-
- // Set global providers
- otel.SetTracerProvider(tracerProvider)
- otel.SetMeterProvider(meterProvider)
- otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
- propagation.TraceContext{},
- propagation.Baggage{},
- ))
-
- // Combined shutdown function
- shutdown := func(ctx context.Context) error {
- var errs []error
-
- if err := tracerShutdown(ctx); err != nil {
- errs = append(errs, fmt.Errorf("tracer shutdown error: %w", err))
- }
-
- if err := meterShutdown(ctx); err != nil {
- errs = append(errs, fmt.Errorf("meter shutdown error: %w", err))
- }
-
- if len(errs) > 0 {
- return errors.Join(errs...)
- }
-
- return nil
- }
-
- return &Providers{
- TracerProvider: tracerProvider,
- MeterProvider: meterProvider,
- PrometheusHTTP: promHandler,
- Shutdown: shutdown,
- }, nil
-}
-
-// initTracerProvider initializes the tracer provider
-func initTracerProvider(ctx context.Context, cfg *config.Config, res *resource.Resource) (trace.TracerProvider, func(context.Context) error, error) {
- // Create OTLP trace exporter
- // AIDEV-NOTE: Export timeout must be less than shutdown timeout to prevent races
- exportTimeout := 10 * time.Second // Well under the 15s shutdown timeout
- traceExporter, err := otlptrace.New(ctx,
- otlptracehttp.NewClient(
- otlptracehttp.WithEndpoint(cfg.OpenTelemetry.OTLPEndpoint),
- otlptracehttp.WithInsecure(), // For local development
- otlptracehttp.WithTimeout(exportTimeout),
- ),
- )
- if err != nil {
- return nil, nil, fmt.Errorf("failed to create trace exporter: %w", err)
- }
-
- // Create sampler with parent-based + ratio
- ratioSampler := sdktrace.TraceIDRatioBased(cfg.OpenTelemetry.TracingSamplingRate)
- parentBasedSampler := sdktrace.ParentBased(ratioSampler)
-
- // Create tracer provider
- tp := sdktrace.NewTracerProvider(
- sdktrace.WithBatcher(traceExporter),
- sdktrace.WithResource(res),
- sdktrace.WithSampler(parentBasedSampler),
- )
-
- return tp, tp.Shutdown, nil
-}
-
-// initMeterProvider initializes the meter provider
-func initMeterProvider(ctx context.Context, cfg *config.Config, res *resource.Resource) (metric.MeterProvider, http.Handler, func(context.Context) error, error) {
- var readers []sdkmetric.Reader
-
- // OTLP metric exporter
- // AIDEV-NOTE: Export timeout must be less than shutdown timeout to prevent races
- exportTimeout := 10 * time.Second // Well under the 15s shutdown timeout
- metricExporter, err := otlpmetrichttp.New(ctx,
- otlpmetrichttp.WithEndpoint(cfg.OpenTelemetry.OTLPEndpoint),
- otlpmetrichttp.WithInsecure(), // For local development
- otlpmetrichttp.WithTimeout(exportTimeout),
- )
- if err != nil {
- return nil, nil, nil, fmt.Errorf("failed to create metric exporter: %w", err)
- }
-
- readers = append(readers, sdkmetric.NewPeriodicReader(
- metricExporter,
- sdkmetric.WithInterval(30*time.Second),
- ))
-
- // Prometheus exporter
- var promHandler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusNotFound)
- _, _ = w.Write([]byte("Prometheus metrics disabled"))
- })
-
- if cfg.OpenTelemetry.PrometheusEnabled {
- promExporter, err := prometheus.New()
- if err != nil {
- return nil, nil, nil, fmt.Errorf("failed to create prometheus exporter: %w", err)
- }
- readers = append(readers, promExporter)
- promHandler = promhttp.Handler()
- }
-
- // Create meter provider with readers
- mpOpts := []sdkmetric.Option{
- sdkmetric.WithResource(res),
- }
- for _, reader := range readers {
- mpOpts = append(mpOpts, sdkmetric.WithReader(reader))
- }
- mp := sdkmetric.NewMeterProvider(mpOpts...)
-
- return mp, promHandler, mp.Shutdown, nil
-}
-
-// RecordError records an error in the current span and sets the status
-func RecordError(span trace.Span, err error) {
- if err != nil {
- span.RecordError(err)
- span.SetStatus(codes.Error, err.Error())
- }
-}
-
-// HTTPStatusCode returns the appropriate trace status code for an HTTP status
-func HTTPStatusCode(httpStatus int) codes.Code {
- if httpStatus >= 200 && httpStatus < 400 {
- return codes.Ok
- }
- return codes.Error
-}
-
-// SpanKindFromMethod returns the appropriate span kind for a method
-func SpanKindFromMethod(method string) trace.SpanKind {
- switch method {
- case "GET", "HEAD", "OPTIONS":
- return trace.SpanKindClient
- default:
- return trace.SpanKindInternal
- }
-}
-
-// ServiceAttributes returns common service attributes
-func ServiceAttributes(cfg *config.Config, version string) []attribute.KeyValue {
- return []attribute.KeyValue{
- semconv.ServiceName(cfg.OpenTelemetry.ServiceName),
- semconv.ServiceVersion(version),
- semconv.ServiceNamespace("unkey"),
- }
-}
diff --git a/go/apps/builderd/internal/service/builder.go b/go/apps/builderd/internal/service/builder.go
deleted file mode 100644
index 4c019dfa68..0000000000
--- a/go/apps/builderd/internal/service/builder.go
+++ /dev/null
@@ -1,540 +0,0 @@
-package service
-
-import (
- "context"
- "fmt"
- "log/slog"
- "os"
- "path/filepath"
- "strings"
- "sync"
- "time"
-
- "connectrpc.com/connect"
- "github.com/unkeyed/unkey/go/apps/builderd/internal/assetmanager"
- "github.com/unkeyed/unkey/go/apps/builderd/internal/config"
- "github.com/unkeyed/unkey/go/apps/builderd/internal/executor"
- "github.com/unkeyed/unkey/go/apps/builderd/internal/observability"
- assetv1 "github.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1"
- builderv1 "github.com/unkeyed/unkey/go/gen/proto/builderd/v1"
- "google.golang.org/protobuf/types/known/timestamppb"
-)
-
-// BuilderService implements the BuilderService ConnectRPC service
-type BuilderService struct {
- logger *slog.Logger
- buildMetrics *observability.BuildMetrics
- config *config.Config
- executors *executor.Registry
- assetClient *assetmanager.Client
-
- builds map[string]*builderv1.BuildJob
- buildsMutex sync.RWMutex
-
- shutdownCtx context.Context
- shutdownCancel context.CancelFunc
- buildWg sync.WaitGroup
-}
-
-// NewBuilderService creates a new BuilderService instance
-func NewBuilderService(
- logger *slog.Logger,
- buildMetrics *observability.BuildMetrics,
- cfg *config.Config,
- assetClient *assetmanager.Client,
-) *BuilderService {
- // Create executor registry
- executors := executor.NewRegistry(logger, cfg, buildMetrics)
-
- shutdownCtx, shutdownCancel := context.WithCancel(context.Background())
-
- return &BuilderService{
- logger: logger,
- buildMetrics: buildMetrics,
- config: cfg,
- executors: executors,
- assetClient: assetClient,
- builds: make(map[string]*builderv1.BuildJob),
- shutdownCtx: shutdownCtx,
- shutdownCancel: shutdownCancel,
- }
-}
-
-// generateBuildID generates a unique build ID
-func generateBuildID() string {
- return fmt.Sprintf("build-%d", time.Now().UnixNano())
-}
-
-// selectKernelForImage determines which bundled kernel to use for a given Docker image
-func (s *BuilderService) selectKernelForImage(imageName string) (kernelPath, kernelName string, err error) {
- // AIDEV-NOTE: Maps base images to appropriate bundled kernels
- // This ensures kernel/rootfs compatibility
-
- imageLower := strings.ToLower(imageName)
- kernelsDir := "/opt/builderd/kernels"
-
- // Determine kernel based on base image
- var kernelFile string
- var kernelDisplayName string
-
- switch {
- case strings.Contains(imageLower, "alpine"):
- kernelFile = "alpine-kernel"
- kernelDisplayName = "Alpine Linux Kernel"
- case strings.Contains(imageLower, "ubuntu"):
- kernelFile = "ubuntu-kernel"
- kernelDisplayName = "Ubuntu Linux Kernel"
- case strings.Contains(imageLower, "debian"):
- kernelFile = "ubuntu-kernel" // Use Ubuntu kernel for Debian (compatible)
- kernelDisplayName = "Ubuntu Linux Kernel (Debian compatible)"
- default:
- // Default to Alpine kernel for unknown images
- kernelFile = "alpine-kernel"
- kernelDisplayName = "Alpine Linux Kernel (default)"
- }
-
- kernelPath = filepath.Join(kernelsDir, kernelFile)
-
- // Verify kernel exists
- if _, err := os.Stat(kernelPath); err != nil {
- return "", "", fmt.Errorf("bundled kernel not found: %s", kernelPath)
- }
-
- return kernelPath, kernelDisplayName, nil
-}
-
-// extractOSFromImage extracts the OS type from a Docker image name
-func extractOSFromImage(imageName string) string {
- imageLower := strings.ToLower(imageName)
-
- switch {
- case strings.Contains(imageLower, "alpine"):
- return "alpine"
- case strings.Contains(imageLower, "ubuntu"):
- return "ubuntu"
- case strings.Contains(imageLower, "debian"):
- return "debian"
- default:
- return "unknown"
- }
-}
-
-// CreateBuild creates a new build job
-func (s *BuilderService) CreateBuild(
- ctx context.Context,
- req *connect.Request[builderv1.CreateBuildRequest],
-) (*connect.Response[builderv1.CreateBuildResponse], error) {
-
- s.logger.InfoContext(ctx, "create build request received")
-
- // Validate build configuration first to prevent nil pointer dereference
- if err := s.validateBuildConfig(req.Msg.GetConfig()); err != nil {
- s.logger.WarnContext(ctx, "invalid build configuration",
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInvalidArgument, err)
- }
-
- // Create build job record
- buildJob := &builderv1.BuildJob{
- BuildId: generateBuildID(),
- Config: req.Msg.GetConfig(),
- State: builderv1.BuildState_BUILD_STATE_BUILDING,
- CreatedAt: timestamppb.Now(),
- StartedAt: timestamppb.Now(),
- }
-
- // Store build job in memory
- s.buildsMutex.Lock()
- s.builds[buildJob.BuildId] = buildJob
- s.buildsMutex.Unlock()
-
- // Execute the build asynchronously
- // AIDEV-NOTE: Launch build in a goroutine to avoid blocking the RPC call
- // AIDEV-BUSINESS_RULE: Use shutdown-aware context to prevent races during service shutdown
- s.buildWg.Add(1)
- go func() {
- defer s.buildWg.Done()
-
- // AIDEV-NOTE: Use shutdown context to coordinate with service lifecycle
- // This prevents builds from running indefinitely during shutdown
- buildCtx := s.shutdownCtx
-
- s.logger.InfoContext(buildCtx, "starting async build execution",
- slog.String("build_id", buildJob.BuildId),
- )
-
- // AIDEV-NOTE: Check for shutdown signal before starting expensive build operation
- select {
- case <-buildCtx.Done():
- s.logger.InfoContext(buildCtx, "build cancelled due to shutdown",
- slog.String("build_id", buildJob.BuildId),
- )
- // Update build job with cancelled state
- s.buildsMutex.Lock()
- buildJob.State = builderv1.BuildState_BUILD_STATE_CANCELLED
- buildJob.CompletedAt = timestamppb.Now()
- buildJob.ErrorMessage = "Build cancelled due to service shutdown"
- s.buildsMutex.Unlock()
- return
- default:
- }
-
- buildResult, err := s.executors.ExecuteWithID(buildCtx, req.Msg, buildJob.BuildId)
- if err != nil {
- // Update build job with error state
- s.buildsMutex.Lock()
- buildJob.State = builderv1.BuildState_BUILD_STATE_FAILED
- buildJob.CompletedAt = timestamppb.Now()
- buildJob.ErrorMessage = err.Error()
- s.buildsMutex.Unlock()
-
- s.logger.ErrorContext(buildCtx, "build execution failed",
- slog.String("error", err.Error()),
- slog.String("build_id", buildJob.BuildId),
- )
- return
- }
-
- s.logger.InfoContext(buildCtx, "build job completed successfully",
- slog.String("build_id", buildJob.BuildId),
- slog.String("source_type", buildResult.SourceType),
- slog.String("rootfs_path", buildResult.RootfsPath),
- slog.Duration("duration", buildResult.EndTime.Sub(buildResult.StartTime)),
- )
-
- // Build state - since we executed immediately, it's either completed or failed
- buildState := builderv1.BuildState_BUILD_STATE_COMPLETED
- if buildResult.Status == "failed" {
- buildState = builderv1.BuildState_BUILD_STATE_FAILED
- }
-
- // Update build job with completion info
- s.buildsMutex.Lock()
- buildJob.State = buildState
- buildJob.CompletedAt = timestamppb.Now()
- buildJob.RootfsPath = buildResult.RootfsPath
- buildJob.ImageMetadata = buildResult.ImageMetadata
- // TODO: Add checksum and size when available
- s.buildsMutex.Unlock()
-
- // Register the build artifact with assetmanagerd if build succeeded
- // AIDEV-NOTE: This enables the built rootfs to be used for VM creation
- if buildState == builderv1.BuildState_BUILD_STATE_COMPLETED && s.assetClient.IsEnabled() {
- labels := map[string]string{
- "source_type": buildResult.SourceType,
- }
-
- // Add docker image label if it's a Docker source
- // AIDEV-NOTE: Must use "docker_image" label to match metald's query expectations
- if dockerSource := req.Msg.GetConfig().GetSource().GetDockerImage(); dockerSource != nil {
- labels["docker_image"] = dockerSource.GetImageUri()
- }
-
- // Determine asset type based on target
- assetType := assetv1.AssetType_ASSET_TYPE_ROOTFS
- if req.Msg.GetConfig().GetTarget().GetMicrovmRootfs() != nil {
- assetType = assetv1.AssetType_ASSET_TYPE_ROOTFS
- }
-
- // Use suggested asset ID if provided in the build config
- suggestedAssetID := req.Msg.GetConfig().GetSuggestedAssetId()
-
- s.logger.InfoContext(buildCtx, "registering build artifact with asset ID",
- slog.String("suggested_asset_id", suggestedAssetID),
- slog.String("build_id", buildJob.BuildId),
- slog.Any("labels", labels),
- )
-
- // First upload the rootfs
- assetID, err := s.assetClient.RegisterBuildArtifactWithID(buildCtx, buildJob.BuildId, buildResult.RootfsPath, assetType, labels, suggestedAssetID)
- if err != nil {
- // Log error but don't fail the build
- s.logger.ErrorContext(buildCtx, "failed to register rootfs with assetmanagerd",
- slog.String("error", err.Error()),
- slog.String("build_id", buildJob.BuildId),
- slog.String("rootfs_path", buildResult.RootfsPath),
- )
- } else {
- s.logger.InfoContext(buildCtx, "registered rootfs with assetmanagerd",
- slog.String("asset_id", assetID),
- slog.String("build_id", buildJob.BuildId),
- )
- }
-
- // Extract the source image from build config
- var sourceImage string
- if buildJob.Config != nil && buildJob.Config.Source != nil {
- if dockerSource := buildJob.Config.Source.GetDockerImage(); dockerSource != nil {
- sourceImage = dockerSource.ImageUri
- }
- }
-
- if sourceImage == "" {
- s.logger.WarnContext(buildCtx, "no Docker image source found, skipping kernel upload")
- } else {
- // Now upload the appropriate kernel
- kernelPath, kernelName, err := s.selectKernelForImage(sourceImage)
- if err != nil {
- s.logger.ErrorContext(buildCtx, "failed to select kernel for image",
- slog.String("error", err.Error()),
- slog.String("image", sourceImage),
- )
- } else {
- // Create kernel labels
-
- kernelLabels := map[string]string{
- "kernel_type": "bundled",
- "compatible_os": extractOSFromImage(sourceImage),
- "build_id": buildJob.BuildId,
- "created_by": "builderd",
- }
-
- kernelAssetID, err := s.assetClient.RegisterBuildArtifactWithID(
- buildCtx,
- buildJob.BuildId+"-kernel",
- kernelPath,
- assetv1.AssetType_ASSET_TYPE_KERNEL,
- kernelLabels,
- "", // Let assetmanagerd generate kernel asset ID
- )
- if err != nil {
- s.logger.ErrorContext(buildCtx, "failed to register kernel with assetmanagerd",
- slog.String("error", err.Error()),
- slog.String("kernel_path", kernelPath),
- slog.String("kernel_name", kernelName),
- )
- } else {
- s.logger.InfoContext(buildCtx, "registered kernel with assetmanagerd",
- slog.String("kernel_asset_id", kernelAssetID),
- slog.String("kernel_name", kernelName),
- slog.String("build_id", buildJob.BuildId),
- )
- }
- }
- }
- }
- }()
-
- // Return immediately with the build ID and "building" state
- resp := &builderv1.CreateBuildResponse{
- BuildId: buildJob.BuildId,
- State: builderv1.BuildState_BUILD_STATE_BUILDING,
- CreatedAt: timestamppb.Now(),
- RootfsPath: "", // Not available yet
- // AIDEV-TODO: Add AssetId field to CreateBuildResponse proto to return registered asset ID
- }
-
- return connect.NewResponse(resp), nil
-}
-
-// GetBuild retrieves build status and information
-func (s *BuilderService) GetBuild(
- ctx context.Context,
- req *connect.Request[builderv1.GetBuildRequest],
-) (*connect.Response[builderv1.GetBuildResponse], error) {
- s.logger.InfoContext(ctx, "get build request received",
- slog.String("build_id", req.Msg.GetBuildId()),
- )
-
- // Retrieve build from memory storage
- s.buildsMutex.RLock()
- build, exists := s.builds[req.Msg.GetBuildId()]
- s.buildsMutex.RUnlock()
-
- if !exists {
- return nil, connect.NewError(connect.CodeNotFound,
- fmt.Errorf("build not found: %s", req.Msg.GetBuildId()))
- }
-
- resp := &builderv1.GetBuildResponse{
- Build: build,
- }
-
- return connect.NewResponse(resp), nil
-}
-
-// ListBuilds lists builds
-func (s *BuilderService) ListBuilds(
- ctx context.Context,
- req *connect.Request[builderv1.ListBuildsRequest],
-) (*connect.Response[builderv1.ListBuildsResponse], error) {
- s.logger.InfoContext(ctx, "list builds request received",
- slog.Int("page_size", int(req.Msg.GetPageSize())),
- )
-
- // TODO: Retrieve builds from database
- // TODO: Apply state filters
- // TODO: Implement pagination
-
- // For now, return empty list
- resp := &builderv1.ListBuildsResponse{
- Builds: []*builderv1.BuildJob{},
- NextPageToken: "",
- TotalCount: 0,
- }
-
- return connect.NewResponse(resp), nil
-}
-
-// CancelBuild cancels a running build
-func (s *BuilderService) CancelBuild(
- ctx context.Context,
- req *connect.Request[builderv1.CancelBuildRequest],
-) (*connect.Response[builderv1.CancelBuildResponse], error) {
- s.logger.InfoContext(ctx, "cancel build request received",
- slog.String("build_id", req.Msg.GetBuildId()),
- )
-
- // TODO: Cancel the running build process
- // TODO: Update build state in database
-
- // Record cancellation metrics
- if s.buildMetrics != nil {
- s.buildMetrics.RecordBuildCancellation(ctx, "unknown", "unknown")
- }
-
- resp := &builderv1.CancelBuildResponse{
- Success: true,
- State: builderv1.BuildState_BUILD_STATE_CANCELLED,
- }
-
- return connect.NewResponse(resp), nil
-}
-
-// DeleteBuild deletes a build and its artifacts
-func (s *BuilderService) DeleteBuild(
- ctx context.Context,
- req *connect.Request[builderv1.DeleteBuildRequest],
-) (*connect.Response[builderv1.DeleteBuildResponse], error) {
- s.logger.InfoContext(ctx, "delete build request received",
- slog.String("build_id", req.Msg.GetBuildId()),
- slog.Bool("force", req.Msg.GetForce()),
- )
-
- // TODO: Check if build is running (and force flag)
- // TODO: Delete build from database
- // TODO: Delete build artifacts from storage
-
- resp := &builderv1.DeleteBuildResponse{
- Success: true,
- }
-
- return connect.NewResponse(resp), nil
-}
-
-// StreamBuildLogs streams build logs in real-time
-func (s *BuilderService) StreamBuildLogs(
- ctx context.Context,
- req *connect.Request[builderv1.StreamBuildLogsRequest],
- stream *connect.ServerStream[builderv1.StreamBuildLogsResponse],
-) error {
- s.logger.InfoContext(ctx, "stream build logs request received",
- slog.String("build_id", req.Msg.GetBuildId()),
- slog.Bool("follow", req.Msg.GetFollow()),
- )
-
- // TODO: Stream existing logs
- // TODO: If follow=true, stream new logs as they arrive
-
- // For now, send a placeholder log entry
- logEntry := &builderv1.StreamBuildLogsResponse{
- Timestamp: timestamppb.New(time.Now()),
- Level: "info",
- Message: "Build logs streaming started",
- Component: "builder",
- Metadata: make(map[string]string),
- }
-
- if err := stream.Send(logEntry); err != nil {
- return connect.NewError(connect.CodeInternal, err)
- }
-
- return nil
-}
-
-// GetBuildStats retrieves build statistics
-func (s *BuilderService) GetBuildStats(
- ctx context.Context,
- req *connect.Request[builderv1.GetBuildStatsRequest],
-) (*connect.Response[builderv1.GetBuildStatsResponse], error) {
- s.logger.InfoContext(ctx, "get build stats request received")
-
- // TODO: Calculate actual statistics from database
-
- resp := &builderv1.GetBuildStatsResponse{
- TotalBuilds: 0,
- SuccessfulBuilds: 0,
- FailedBuilds: 0,
- AvgBuildTimeMs: 0,
- TotalStorageBytes: 0,
- TotalComputeMinutes: 0,
- RecentBuilds: []*builderv1.BuildJob{},
- }
-
- return connect.NewResponse(resp), nil
-}
-
-// validateBuildConfig validates the build configuration
-func (s *BuilderService) validateBuildConfig(config *builderv1.BuildConfig) error {
- if config == nil {
- return fmt.Errorf("build config is required")
- }
-
- if config.GetSource() == nil {
- return fmt.Errorf("build source is required")
- }
-
- if config.GetTarget() == nil {
- return fmt.Errorf("build target is required")
- }
-
- if config.GetStrategy() == nil {
- return fmt.Errorf("build strategy is required")
- }
-
- // Validate source-specific requirements
- switch source := config.GetSource().GetSourceType().(type) {
- case *builderv1.BuildSource_DockerImage:
- if source.DockerImage.GetImageUri() == "" {
- return fmt.Errorf("docker image URI is required")
- }
- case *builderv1.BuildSource_GitRepository:
- if source.GitRepository.GetRepositoryUrl() == "" {
- return fmt.Errorf("git repository URL is required")
- }
- case *builderv1.BuildSource_Archive:
- if source.Archive.GetArchiveUrl() == "" {
- return fmt.Errorf("archive URL is required")
- }
- default:
- return fmt.Errorf("unsupported source type")
- }
-
- return nil
-}
-
-// Shutdown gracefully shuts down the BuilderService
-// AIDEV-NOTE: This method coordinates shutdown of all running builds to prevent races
-func (s *BuilderService) Shutdown(ctx context.Context) error {
- s.logger.InfoContext(ctx, "starting BuilderService shutdown")
-
- // Cancel all running builds
- s.shutdownCancel()
-
- // Wait for all builds to complete with timeout
- done := make(chan struct{})
- go func() {
- s.buildWg.Wait()
- close(done)
- }()
-
- select {
- case <-done:
- s.logger.InfoContext(ctx, "all builds completed during shutdown")
- return nil
- case <-ctx.Done():
- s.logger.WarnContext(ctx, "shutdown timeout reached, some builds may have been terminated")
- return ctx.Err()
- }
-}
diff --git a/go/apps/ctrl/config.go b/go/apps/ctrl/config.go
index e533c7a24e..51f58c7ae2 100644
--- a/go/apps/ctrl/config.go
+++ b/go/apps/ctrl/config.go
@@ -64,13 +64,13 @@ type Config struct {
// AuthToken is the authentication token for control plane API access
AuthToken string
+ // KraneAddress is the full URL of the krane service for deployment operations (e.g., "https://krane.example.com:8080")
+ KraneAddress string
+
// APIKey is the API key for simple authentication (demo purposes only)
// TODO: Replace with JWT authentication when moving to private IP
APIKey string
- // MetaldAddress is the full URL of the metald service for VM operations (e.g., "https://metald.example.com:8080")
- MetaldAddress string
-
// SPIFFESocketPath is the path to the SPIFFE agent socket for mTLS authentication
SPIFFESocketPath string
diff --git a/go/apps/ctrl/middleware/auth.go b/go/apps/ctrl/middleware/auth.go
index 8dd444041e..bec08d3ba2 100644
--- a/go/apps/ctrl/middleware/auth.go
+++ b/go/apps/ctrl/middleware/auth.go
@@ -84,4 +84,4 @@ func (m *AuthMiddleware) ConnectInterceptor() connect.UnaryInterceptorFunc {
return next(ctx, req)
}
}
-}
\ No newline at end of file
+}
diff --git a/go/apps/ctrl/run.go b/go/apps/ctrl/run.go
index 0bf4bf08e3..9733e2d7ee 100644
--- a/go/apps/ctrl/run.go
+++ b/go/apps/ctrl/run.go
@@ -17,7 +17,7 @@ import (
"github.com/unkeyed/unkey/go/apps/ctrl/services/openapi"
deployTLS "github.com/unkeyed/unkey/go/deploy/pkg/tls"
"github.com/unkeyed/unkey/go/gen/proto/ctrl/v1/ctrlv1connect"
- "github.com/unkeyed/unkey/go/gen/proto/metald/v1/metaldv1connect"
+ "github.com/unkeyed/unkey/go/gen/proto/krane/v1/kranev1connect"
"github.com/unkeyed/unkey/go/pkg/db"
"github.com/unkeyed/unkey/go/pkg/hydra"
"github.com/unkeyed/unkey/go/pkg/otel"
@@ -142,7 +142,7 @@ func Run(ctx context.Context, cfg Config) error {
return fmt.Errorf("unable to create hydra worker: %w", err)
}
- // Create metald client for VM operations
+ // Create krane client for VM operations
var httpClient *http.Client
var authMode string
@@ -161,7 +161,7 @@ func Run(ctx context.Context, cfg Config) error {
tlsProvider, tlsErr := deployTLS.NewProvider(ctx, tlsConfig)
if tlsErr != nil {
- return fmt.Errorf("failed to create TLS provider for metald: %w", tlsErr)
+ return fmt.Errorf("failed to create TLS provider for krane: %w", tlsErr)
}
httpClient = tlsProvider.HTTPClient()
@@ -174,26 +174,24 @@ func Run(ctx context.Context, cfg Config) error {
httpClient.Timeout = 30 * time.Second
- metaldClient := metaldv1connect.NewVmServiceClient(
+ kraneClient := kranev1connect.NewDeploymentServiceClient(
httpClient,
- cfg.MetaldAddress,
+ cfg.KraneAddress,
connect.WithInterceptors(connect.UnaryInterceptorFunc(func(next connect.UnaryFunc) connect.UnaryFunc {
return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
- logger.Info("Adding auth headers to metald request", "procedure", req.Spec().Procedure)
req.Header().Set("Authorization", "Bearer dev_user_ctrl")
- req.Header().Set("X-Tenant-ID", "ctrl-tenant")
return next(ctx, req)
}
})),
)
- logger.Info("metald client configured", "address", cfg.MetaldAddress, "auth_mode", authMode)
+ logger.Info("krane client configured", "address", cfg.KraneAddress, "auth_mode", authMode)
// Register deployment workflow with Hydra worker
deployWorkflow := deployment.NewDeployWorkflow(deployment.DeployWorkflowConfig{
Logger: logger,
DB: database,
PartitionDB: partitionDB,
- MetalD: metaldClient,
+ Krane: kraneClient,
DefaultDomain: cfg.DefaultDomain,
})
err = hydra.RegisterWorkflow(hydraWorker, deployWorkflow)
diff --git a/go/apps/ctrl/services/deployment/deploy_workflow.go b/go/apps/ctrl/services/deployment/deploy_workflow.go
index 76f94e0cc1..94a7b9a518 100644
--- a/go/apps/ctrl/services/deployment/deploy_workflow.go
+++ b/go/apps/ctrl/services/deployment/deploy_workflow.go
@@ -9,8 +9,8 @@ import (
"connectrpc.com/connect"
ctrlv1 "github.com/unkeyed/unkey/go/gen/proto/ctrl/v1"
- metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1"
- "github.com/unkeyed/unkey/go/gen/proto/metald/v1/metaldv1connect"
+ kranev1 "github.com/unkeyed/unkey/go/gen/proto/krane/v1"
+ "github.com/unkeyed/unkey/go/gen/proto/krane/v1/kranev1connect"
partitionv1 "github.com/unkeyed/unkey/go/gen/proto/partition/v1"
"github.com/unkeyed/unkey/go/pkg/db"
"github.com/unkeyed/unkey/go/pkg/hydra"
@@ -20,12 +20,14 @@ import (
"google.golang.org/protobuf/encoding/protojson"
)
+const hardcodedNamespace = "unkey" // TODO change to workspace scope
+
// DeployWorkflow orchestrates the complete build and deployment process using Hydra
type DeployWorkflow struct {
db db.Database
partitionDB db.Database
logger logging.Logger
- metaldClient metaldv1connect.VmServiceClient
+ krane kranev1connect.DeploymentServiceClient
defaultDomain string
}
@@ -33,7 +35,7 @@ type DeployWorkflowConfig struct {
Logger logging.Logger
DB db.Database
PartitionDB db.Database
- MetalD metaldv1connect.VmServiceClient
+ Krane kranev1connect.DeploymentServiceClient
DefaultDomain string
}
@@ -43,7 +45,7 @@ func NewDeployWorkflow(cfg DeployWorkflowConfig) *DeployWorkflow {
db: cfg.DB,
partitionDB: cfg.PartitionDB,
logger: cfg.Logger,
- metaldClient: cfg.MetalD,
+ krane: cfg.Krane,
defaultDomain: cfg.DefaultDomain,
}
}
@@ -135,30 +137,31 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro
return err
}
- metaldDeployment, err := hydra.Step(ctx, "create-deployment", func(stepCtx context.Context) (*metaldv1.CreateDeploymentResponse, error) {
+ err = hydra.StepVoid(ctx, "create-deployment", func(stepCtx context.Context) error {
// Create deployment request
- deploymentReq := &metaldv1.CreateDeploymentRequest{
- Deployment: &metaldv1.DeploymentRequest{
+ deploymentReq := &kranev1.CreateDeploymentRequest{
+ Deployment: &kranev1.DeploymentRequest{
+ Namespace: hardcodedNamespace,
DeploymentId: req.DeploymentID,
Image: req.DockerImage,
- VmCount: 1,
- Cpu: 1,
+ Replicas: 3,
+ CpuMillicores: 1000,
MemorySizeMib: 1024,
},
}
- resp, err := w.metaldClient.CreateDeployment(stepCtx, connect.NewRequest(deploymentReq))
+ _, err := w.krane.CreateDeployment(stepCtx, connect.NewRequest(deploymentReq))
if err != nil {
- return nil, fmt.Errorf("metald CreateDeployment failed for image %s: %w", req.DockerImage, err)
+ return fmt.Errorf("krane CreateDeployment failed for image %s: %w", req.DockerImage, err)
}
- return resp.Msg, nil
+ return nil
})
if err != nil {
return err
}
- w.logger.Info("deployment created", "deployment_id", req.DeploymentID, "vm_count", len(metaldDeployment.GetVmIds()))
+ w.logger.Info("deployment created", "deployment_id", req.DeploymentID)
// Update version status to deploying
_, err = hydra.Step(ctx, "update-version-deploying", func(stepCtx context.Context) (*struct{}, error) {
@@ -176,8 +179,8 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro
return err
}
- createdVMs, err := hydra.Step(ctx, "polling deployment prepare", func(stepCtx context.Context) ([]*metaldv1.GetDeploymentResponse_Vm, error) {
- instances := make(map[string]*metaldv1.GetDeploymentResponse_Vm)
+ createdInstances, err := hydra.Step(ctx, "polling deployment prepare", func(stepCtx context.Context) ([]*kranev1.Instance, error) {
+ // prevent updating the db unnecessarily
for i := range 300 {
time.Sleep(time.Second)
@@ -185,69 +188,66 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro
w.logger.Info("polling deployment status", "deployment_id", req.DeploymentID, "iteration", i)
}
- resp, err := w.metaldClient.GetDeployment(stepCtx, connect.NewRequest(&metaldv1.GetDeploymentRequest{
+ resp, err := w.krane.GetDeployment(stepCtx, connect.NewRequest(&kranev1.GetDeploymentRequest{
+ Namespace: hardcodedNamespace,
DeploymentId: req.DeploymentID,
}))
if err != nil {
- return nil, fmt.Errorf("metald GetDeployment failed for deployment %s: %w", req.DeploymentID, err)
+ return nil, fmt.Errorf("krane GetDeployment failed for deployment %s: %w", req.DeploymentID, err)
}
- vms := resp.Msg.GetVms()
+ w.logger.Info("deployment status",
+ "deployment_id", req.DeploymentID,
+ "status", resp.Msg,
+ )
allReady := true
- for _, instance := range vms {
- known, ok := instances[instance.Id]
- if !ok || known.State != instance.State {
- upsertParams := partitiondb.UpsertVMParams{
- ID: instance.Id,
- DeploymentID: req.DeploymentID,
- Address: sql.NullString{Valid: true, String: fmt.Sprintf("%s:%d", instance.Host, instance.Port)},
- CpuMillicores: 1000, // TODO derive from spec
- MemoryMb: 1024, // TODO derive from spec
- Status: partitiondb.VmsStatusRunning, // TODO
- }
+ for _, instance := range resp.Msg.GetInstances() {
+ if instance.Status != kranev1.DeploymentStatus_DEPLOYMENT_STATUS_RUNNING {
+ allReady = false
+ }
- w.logger.Info("upserting VM to database",
- "vm_id", instance.Id,
- "deployment_id", req.DeploymentID,
- "address", fmt.Sprintf("%s:%d", instance.Host, instance.Port),
- "status", "running")
+ var status partitiondb.VmsStatus
+ switch instance.Status {
+ case kranev1.DeploymentStatus_DEPLOYMENT_STATUS_PENDING:
+ status = partitiondb.VmsStatusProvisioning
+ case kranev1.DeploymentStatus_DEPLOYMENT_STATUS_RUNNING:
+ status = partitiondb.VmsStatusRunning
+
+ case kranev1.DeploymentStatus_DEPLOYMENT_STATUS_TERMINATING:
+ status = partitiondb.VmsStatusStopping
+ case kranev1.DeploymentStatus_DEPLOYMENT_STATUS_UNSPECIFIED:
+ status = partitiondb.VmsStatusAllocated
+ }
- if err := partitiondb.Query.UpsertVM(stepCtx, w.partitionDB.RW(), upsertParams); err != nil {
- return nil, fmt.Errorf("failed to upsert VM %s: %w", instance.Id, err)
- }
+ upsertParams := partitiondb.UpsertVMParams{
+ ID: instance.Id,
+ DeploymentID: req.DeploymentID,
+ Address: sql.NullString{Valid: true, String: instance.Address},
+ CpuMillicores: 1000, // TODO derive from spec
+ MemoryMb: 1024, // TODO derive from spec
+ Status: status, // TODO
+ }
- w.logger.Info("successfully upserted VM to database", "vm_id", instance.Id)
+ w.logger.Info("upserting VM to database",
+ "vm_id", instance.Id,
+ "deployment_id", req.DeploymentID,
+ "address", instance.Address,
+ "status", "running")
- instances[instance.Id] = instance
+ if err := partitiondb.Query.UpsertVM(stepCtx, w.partitionDB.RW(), upsertParams); err != nil {
+ return nil, fmt.Errorf("failed to upsert VM %s: %w", instance.Id, err)
}
- w.logger.Debug("checking VM readiness", "vm_id", instance.Id, "state", instance.State.String())
- if instance.State != metaldv1.VmState_VM_STATE_RUNNING {
- allReady = false
- w.logger.Debug("vm not ready", "vm_id", instance.Id, "state", instance.State.String())
- }
+ w.logger.Info("successfully upserted VM to database", "vm_id", instance.Id)
+
}
if allReady {
- w.logger.Info("all VMs ready, deployment complete",
- "deployment_id", req.DeploymentID,
- "vm_count", len(vms),
- "vms", func() []string {
- var ids []string
- for _, vm := range vms {
- ids = append(ids, vm.Id)
- }
- return ids
- }())
-
- return vms, nil
+ return resp.Msg.GetInstances(), nil
}
+ // next loop
- w.logger.Debug("deployment not ready yet, continuing to poll",
- "deployment_id", req.DeploymentID,
- "iteration", i,
- "all_ready", allReady)
}
return nil, fmt.Errorf("deployment never became ready")
@@ -349,7 +349,7 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro
}
// Create gateway config for this domain
- gatewayConfig, err := createGatewayConfig(req.DeploymentID, req.KeyspaceID, createdVMs)
+ gatewayConfig, err := createGatewayConfig(req.DeploymentID, req.KeyspaceID, createdInstances)
if err != nil {
w.logger.Error("failed to create gateway config for domain",
"domain", domain,
@@ -589,17 +589,17 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro
// and readability during development/demo. This makes it simpler to inspect and
// modify configs directly in the database.
// IMPORTANT: Always use protojson.Marshal for writes and protojson.Unmarshal for reads.
-func createGatewayConfig(deploymentID, keyspaceID string, vms []*metaldv1.GetDeploymentResponse_Vm) (*partitionv1.GatewayConfig, error) {
+func createGatewayConfig(deploymentID, keyspaceID string, instances []*kranev1.Instance) (*partitionv1.GatewayConfig, error) {
// Create VM protobuf objects for gateway config
gatewayConfig := &partitionv1.GatewayConfig{
Deployment: &partitionv1.Deployment{
Id: deploymentID,
IsEnabled: true,
},
- Vms: make([]*partitionv1.VM, len(vms)),
+ Vms: make([]*partitionv1.VM, len(instances)),
}
- for i, vm := range vms {
+ for i, vm := range instances {
gatewayConfig.Vms[i] = &partitionv1.VM{
Id: vm.Id,
}
diff --git a/go/apps/krane/backend/docker/create_deployment.go b/go/apps/krane/backend/docker/create_deployment.go
new file mode 100644
index 0000000000..b3e33644fe
--- /dev/null
+++ b/go/apps/krane/backend/docker/create_deployment.go
@@ -0,0 +1,100 @@
+package docker
+
+import (
+ "context"
+ "fmt"
+
+ "connectrpc.com/connect"
+ "github.com/docker/docker/api/types/container"
+ "github.com/docker/docker/api/types/network"
+ "github.com/docker/go-connections/nat"
+ kranev1 "github.com/unkeyed/unkey/go/gen/proto/krane/v1"
+)
+
+// CreateDeployment creates containers for a deployment with the specified replica count.
+//
+// Creates multiple containers with shared labels, dynamic port mapping to port 8080,
+// and resource limits. Returns DEPLOYMENT_STATUS_PENDING as containers may not be
+// immediately ready.
+func (d *docker) CreateDeployment(ctx context.Context, req *connect.Request[kranev1.CreateDeploymentRequest]) (*connect.Response[kranev1.CreateDeploymentResponse], error) {
+ deployment := req.Msg.GetDeployment()
+
+ d.logger.Info("creating deployment",
+ "deployment_id", deployment.GetDeploymentId(),
+ "image", deployment.GetImage(),
+ )
+
+ // Configure port mapping
+ exposedPorts := nat.PortSet{
+ "8080/tcp": struct{}{},
+ }
+
+ portBindings := nat.PortMap{
+ "8080/tcp": []nat.PortBinding{
+ {
+ HostIP: "0.0.0.0",
+ HostPort: "0", // Docker will assign a random available port
+ },
+ },
+ }
+
+ // Configure resource limits
+ cpuNanos := int64(deployment.GetCpuMillicores()) * 1_000_000 // Convert millicores to nanoseconds
+ memoryBytes := int64(deployment.GetMemorySizeMib()) * 1024 * 1024
+
+ // Container configuration
+ containerConfig := &container.Config{
+
+ Image: deployment.GetImage(),
+ Labels: map[string]string{
+ "unkey.deployment.id": deployment.GetDeploymentId(),
+ "unkey.managed.by": "krane",
+ },
+ ExposedPorts: exposedPorts,
+ Env: []string{
+ fmt.Sprintf("DEPLOYMENT_ID=%s", deployment.GetDeploymentId()),
+ },
+ }
+
+ // Host configuration
+ hostConfig := &container.HostConfig{
+
+ PortBindings: portBindings,
+ RestartPolicy: container.RestartPolicy{
+ Name: "unless-stopped",
+ },
+ Resources: container.Resources{
+ NanoCPUs: cpuNanos,
+ Memory: memoryBytes,
+ },
+ }
+
+ // Network configuration
+ networkConfig := &network.NetworkingConfig{}
+
+ // Create container
+
+ for i := range req.Msg.Deployment.Replicas {
+ resp, err := d.client.ContainerCreate(
+ ctx,
+ containerConfig,
+ hostConfig,
+ networkConfig,
+ nil,
+ fmt.Sprintf("%s-%d", deployment.GetDeploymentId(), i),
+ )
+ if err != nil {
+ return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to create container: %w", err))
+ }
+
+ // Start container
+ err = d.client.ContainerStart(ctx, resp.ID, container.StartOptions{})
+ if err != nil {
+ return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to start container: %w", err))
+ }
+ }
+
+ return connect.NewResponse(&kranev1.CreateDeploymentResponse{
+ Status: kranev1.DeploymentStatus_DEPLOYMENT_STATUS_PENDING,
+ }), nil
+}
diff --git a/go/apps/krane/backend/docker/delete_deployment.go b/go/apps/krane/backend/docker/delete_deployment.go
new file mode 100644
index 0000000000..3f11f31930
--- /dev/null
+++ b/go/apps/krane/backend/docker/delete_deployment.go
@@ -0,0 +1,43 @@
+package docker
+
+import (
+ "context"
+ "fmt"
+
+ "connectrpc.com/connect"
+ "github.com/docker/docker/api/types/container"
+ "github.com/docker/docker/api/types/filters"
+ kranev1 "github.com/unkeyed/unkey/go/gen/proto/krane/v1"
+)
+
+// DeleteDeployment removes all containers for a deployment.
+//
+// Finds containers by deployment ID label and forcibly removes them with
+// volumes and network links to ensure complete cleanup.
+func (d *docker) DeleteDeployment(ctx context.Context, req *connect.Request[kranev1.DeleteDeploymentRequest]) (*connect.Response[kranev1.DeleteDeploymentResponse], error) {
+ deploymentID := req.Msg.GetDeploymentId()
+
+ d.logger.Info("getting deployment", "deployment_id", deploymentID)
+
+ containers, err := d.client.ContainerList(ctx, container.ListOptions{
+ All: true,
+ Filters: filters.NewArgs(
+ filters.Arg("label", fmt.Sprintf("unkey.deployment.id=%s", deploymentID)),
+ ),
+ })
+ if err != nil {
+ return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to list containers: %w", err))
+ }
+
+ for _, c := range containers {
+ err := d.client.ContainerRemove(ctx, c.ID, container.RemoveOptions{
+ RemoveVolumes: true,
+ RemoveLinks: true,
+ Force: true,
+ })
+ if err != nil {
+ return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to remove container: %w", err))
+ }
+ }
+ return connect.NewResponse(&kranev1.DeleteDeploymentResponse{}), nil
+}
diff --git a/go/apps/krane/backend/docker/get_deployment.go b/go/apps/krane/backend/docker/get_deployment.go
new file mode 100644
index 0000000000..9439fec132
--- /dev/null
+++ b/go/apps/krane/backend/docker/get_deployment.go
@@ -0,0 +1,64 @@
+package docker
+
+import (
+ "context"
+ "fmt"
+
+ "connectrpc.com/connect"
+ "github.com/docker/docker/api/types/container"
+ "github.com/docker/docker/api/types/filters"
+ kranev1 "github.com/unkeyed/unkey/go/gen/proto/krane/v1"
+)
+
+// GetDeployment retrieves container status and addresses for a deployment.
+//
+// Finds containers by deployment ID label and returns instance information
+// with host.docker.internal addresses using dynamically assigned ports.
+func (d *docker) GetDeployment(ctx context.Context, req *connect.Request[kranev1.GetDeploymentRequest]) (*connect.Response[kranev1.GetDeploymentResponse], error) {
+ deploymentID := req.Msg.GetDeploymentId()
+
+ d.logger.Info("getting deployment", "deployment_id", deploymentID)
+ containers, err := d.client.ContainerList(ctx, container.ListOptions{
+ All: true,
+ Filters: filters.NewArgs(
+ filters.Arg("label", fmt.Sprintf("unkey.deployment.id=%s", deploymentID)),
+ ),
+ })
+ if err != nil {
+ return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to list containers: %w", err))
+ }
+
+ res := &kranev1.GetDeploymentResponse{
+ Instances: []*kranev1.Instance{},
+ }
+
+ for _, c := range containers {
+
+ d.logger.Info("container found", "container", c)
+
+ // Determine container status
+ var status kranev1.DeploymentStatus = kranev1.DeploymentStatus_DEPLOYMENT_STATUS_UNSPECIFIED
+ switch c.State {
+ case container.StateRunning:
+ status = kranev1.DeploymentStatus_DEPLOYMENT_STATUS_RUNNING
+ case container.StateExited:
+ status = kranev1.DeploymentStatus_DEPLOYMENT_STATUS_TERMINATING
+ case container.StateCreated:
+ status = kranev1.DeploymentStatus_DEPLOYMENT_STATUS_PENDING
+ }
+
+ d.logger.Info("deployment found",
+ "deployment_id", deploymentID,
+ "container_id", c.ID,
+ "status", status.String(),
+ "port", c.Ports[0].PublicPort,
+ )
+
+ res.Instances = append(res.Instances, &kranev1.Instance{
+ Id: c.ID,
+ Address: fmt.Sprintf("host.docker.internal:%d", c.Ports[0].PublicPort),
+ Status: status,
+ })
+ }
+ return connect.NewResponse(res), nil
+}
diff --git a/go/apps/krane/backend/docker/service.go b/go/apps/krane/backend/docker/service.go
new file mode 100644
index 0000000000..e618370a8a
--- /dev/null
+++ b/go/apps/krane/backend/docker/service.go
@@ -0,0 +1,47 @@
+package docker
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/docker/docker/client"
+ "github.com/unkeyed/unkey/go/gen/proto/krane/v1/kranev1connect"
+ "github.com/unkeyed/unkey/go/pkg/otel/logging"
+)
+
+// docker implements kranev1connect.DeploymentServiceHandler using Docker Engine API.
+type docker struct {
+ logger logging.Logger
+ client *client.Client
+ kranev1connect.UnimplementedDeploymentServiceHandler
+}
+
+var _ kranev1connect.DeploymentServiceHandler = (*docker)(nil)
+
+// New creates a Docker backend instance and validates daemon connectivity.
+func New(logger logging.Logger, socketPath string) (*docker, error) {
+ // Create Docker client with configurable socket path
+ // socketPath must not include protocol (e.g., "var/run/docker.sock")
+ dockerClient, err := client.NewClientWithOpts(
+ client.FromEnv,
+ client.WithAPIVersionNegotiation(),
+ client.WithHost(fmt.Sprintf("unix:///%s", socketPath)),
+ )
+ if err != nil {
+ return nil, fmt.Errorf("failed to create Docker client: %w", err)
+ }
+
+ // Test connection to Docker daemon
+ ctx := context.Background()
+ _, err = dockerClient.Ping(ctx)
+ if err != nil {
+ return nil, fmt.Errorf("failed to ping Docker daemon at %s (ensure Docker socket is accessible): %w", socketPath, err)
+ }
+
+ logger.Info("Docker client initialized successfully", "socket", socketPath)
+
+ return &docker{
+ logger: logger,
+ client: dockerClient,
+ }, nil
+}
diff --git a/go/apps/krane/backend/kubernetes/create_deployment.go b/go/apps/krane/backend/kubernetes/create_deployment.go
new file mode 100644
index 0000000000..72bd4daa6a
--- /dev/null
+++ b/go/apps/krane/backend/kubernetes/create_deployment.go
@@ -0,0 +1,217 @@
+package kubernetes
+
+import (
+ "context"
+ "fmt"
+
+ "connectrpc.com/connect"
+ kranev1 "github.com/unkeyed/unkey/go/gen/proto/krane/v1"
+ "github.com/unkeyed/unkey/go/pkg/ptr"
+ appsv1 "k8s.io/api/apps/v1"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/resource"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/util/intstr"
+)
+
+// CreateDeployment creates a new deployment using Kubernetes StatefulSets and Services.
+//
+// This method implements deployment creation using a two-resource approach:
+// a headless Service for DNS-based service discovery and a StatefulSet for
+// managing pod replicas with stable network identities. This design choice
+// supports the existing microVM abstraction where each instance requires
+// predictable DNS names for database connections and gateway routing.
+//
+// Resource Creation Sequence:
+// 1. Create headless Service (ClusterIP: None) for stable DNS resolution
+// 2. Create StatefulSet with pod template and resource specifications
+// 3. Set Service ownership to StatefulSet for automatic cleanup
+//
+// StatefulSet vs Deployment Choice:
+//
+// The implementation uses StatefulSets instead of standard Deployments
+// because krane requires stable network identities for each replica.
+// This represents a departure from typical stateless application patterns
+// but aligns with the existing system architecture expectations.
+//
+// Note: This design decision may be reconsidered in future versions as
+// it may not align with cloud-native best practices for stateless services.
+//
+// Resource Configuration:
+// - Containers: Single container per pod with specified image and resources
+// - CPU/Memory: Both requests and limits set to same values for predictable scheduling
+// - Networking: Container port 8080 exposed, Service provides cluster-wide access
+// - Labels: Applied for krane management tracking and resource grouping
+// - Restart Policy: Always restart containers on failure
+//
+// Service Discovery:
+//
+// Each pod receives a stable DNS name following the pattern:
+// {pod-name}-{index}.{service-name}.{namespace}.svc.cluster.local
+// Example: myapp-0.myapp.unkey.svc.cluster.local
+//
+// Resource Ownership:
+//
+// The Service is configured as owned by the StatefulSet through Kubernetes
+// owner references, ensuring automatic cleanup when the StatefulSet is deleted.
+// This prevents orphaned Services from accumulating in the cluster.
+//
+// Error Handling:
+//
+// Service or StatefulSet creation failures return CodeInternal errors.
+// The method ensures transactional behavior - if StatefulSet creation fails
+// after Service creation, the Service remains (and will be cleaned up by
+// Kubernetes garbage collection if properly configured).
+//
+// Returns DEPLOYMENT_STATUS_PENDING as pods may not be immediately scheduled
+// and ready for traffic after creation.
+func (k *k8s) CreateDeployment(ctx context.Context, req *connect.Request[kranev1.CreateDeploymentRequest]) (*connect.Response[kranev1.CreateDeploymentResponse], error) {
+ k8sDeploymentID := safeIDForK8s(req.Msg.GetDeployment().GetDeploymentId())
+ k.logger.Info("creating deployment",
+ "namespace", req.Msg.GetDeployment().GetNamespace(),
+ "deployment_id", k8sDeploymentID,
+ )
+
+ service, err := k.clientset.CoreV1().
+ Services(req.Msg.GetDeployment().GetNamespace()).
+ Create(ctx,
+ // This implementation of using stateful sets is very likely not what we want to
+ // use in v1.
+ //
+ // It's simply what fits our existing abstraction of microVMs best, because it gives us
+ // stable dns addresses for each pod and that's what our database and gatway expect.
+ //
+ // I believe going forward we need to re-evaluate that cause it's the wrong abstraction.
+ &corev1.Service{
+
+ ObjectMeta: metav1.ObjectMeta{
+ Name: k8sDeploymentID,
+ Namespace: req.Msg.GetDeployment().GetNamespace(),
+ Labels: map[string]string{
+ "unkey.deployment.id": k8sDeploymentID,
+ "unkey.managed.by": "krane",
+ },
+
+ Annotations: map[string]string{
+ "unkey.deployment.id": k8sDeploymentID,
+ },
+ },
+
+ Spec: corev1.ServiceSpec{
+ Type: corev1.ServiceTypeClusterIP, // Use ClusterIP for internal communication
+ Selector: map[string]string{
+ "unkey.deployment.id": k8sDeploymentID,
+ },
+ ClusterIP: "None",
+ PublishNotReadyAddresses: true,
+ Ports: []corev1.ServicePort{
+ {
+ Port: 8080,
+ TargetPort: intstr.FromInt(8080),
+ Protocol: corev1.ProtocolTCP,
+ },
+ },
+ },
+ },
+ metav1.CreateOptions{},
+ )
+
+ if err != nil {
+ return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to create service: %w", err))
+ }
+
+ sfs, err := k.clientset.AppsV1().StatefulSets(req.Msg.GetDeployment().GetNamespace()).Create(ctx,
+ &appsv1.StatefulSet{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: k8sDeploymentID,
+ Namespace: req.Msg.GetDeployment().GetNamespace(),
+ Labels: map[string]string{
+ "unkey.deployment.id": k8sDeploymentID,
+ "unkey.managed.by": "krane",
+ },
+ },
+
+ Spec: appsv1.StatefulSetSpec{
+ ServiceName: service.Name,
+ Replicas: ptr.P(int32(req.Msg.GetDeployment().GetReplicas())),
+ Selector: &metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "unkey.deployment.id": k8sDeploymentID,
+ },
+ },
+ Template: corev1.PodTemplateSpec{
+ ObjectMeta: metav1.ObjectMeta{
+ Labels: map[string]string{
+ "unkey.deployment.id": k8sDeploymentID,
+ "unkey.managed.by": "krane",
+ },
+ Annotations: map[string]string{},
+ },
+ Spec: corev1.PodSpec{
+ RestartPolicy: corev1.RestartPolicyAlways,
+ Containers: []corev1.Container{
+
+ corev1.Container{
+ Name: "todo",
+ Image: req.Msg.GetDeployment().GetImage(),
+ Ports: []corev1.ContainerPort{
+ {
+ ContainerPort: 8080,
+ Protocol: corev1.ProtocolTCP,
+ },
+ },
+ Resources: corev1.ResourceRequirements{
+ Requests: corev1.ResourceList{
+ corev1.ResourceCPU: *resource.NewMilliQuantity(int64(req.Msg.GetDeployment().GetCpuMillicores()), resource.DecimalSI),
+ corev1.ResourceMemory: *resource.NewQuantity(int64(req.Msg.GetDeployment().GetMemorySizeMib())*1024*1024, resource.DecimalSI),
+ },
+ Limits: corev1.ResourceList{
+ corev1.ResourceCPU: *resource.NewMilliQuantity(int64(req.Msg.GetDeployment().GetCpuMillicores()), resource.DecimalSI),
+ corev1.ResourceMemory: *resource.NewQuantity(int64(req.Msg.GetDeployment().GetMemorySizeMib())*1024*1024, resource.DecimalSI),
+ },
+ },
+ },
+ },
+ },
+ },
+ PersistentVolumeClaimRetentionPolicy: &appsv1.StatefulSetPersistentVolumeClaimRetentionPolicy{
+ WhenDeleted: appsv1.DeletePersistentVolumeClaimRetentionPolicyType,
+ WhenScaled: appsv1.DeletePersistentVolumeClaimRetentionPolicyType,
+ },
+ },
+ }, metav1.CreateOptions{})
+
+ if err != nil {
+ k.logger.Info("Deleting service, because deployment creation failed")
+ // Delete service
+ if rollbackErr := k.clientset.CoreV1().Services(req.Msg.GetDeployment().GetNamespace()).Delete(ctx, service.Name, metav1.DeleteOptions{}); rollbackErr != nil {
+ k.logger.Error("Failed to delete service", "error", rollbackErr.Error())
+ }
+
+ return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to create deployment: %w", err))
+ }
+
+ service.OwnerReferences = []metav1.OwnerReference{
+ // Automatically clean up the service, when the deployment gets deleted
+ {
+ APIVersion: "apps/v1",
+ Kind: "StatefulSet",
+ Name: k8sDeploymentID,
+ UID: sfs.UID,
+ },
+ }
+
+ _, err = k.clientset.CoreV1().Services(req.Msg.GetDeployment().GetNamespace()).Update(ctx, service, metav1.UpdateOptions{})
+ if err != nil {
+ return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to update deployment: %w", err))
+ }
+
+ k.logger.Info("Deployment created successfully",
+ "deployment", sfs.String(),
+ "service", service.String(),
+ )
+
+ return connect.NewResponse(&kranev1.CreateDeploymentResponse{
+ Status: kranev1.DeploymentStatus_DEPLOYMENT_STATUS_PENDING,
+ }), nil
+}
diff --git a/go/apps/krane/backend/kubernetes/delete_deployment.go b/go/apps/krane/backend/kubernetes/delete_deployment.go
new file mode 100644
index 0000000000..bbf729e200
--- /dev/null
+++ b/go/apps/krane/backend/kubernetes/delete_deployment.go
@@ -0,0 +1,44 @@
+package kubernetes
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "connectrpc.com/connect"
+ kranev1 "github.com/unkeyed/unkey/go/gen/proto/krane/v1"
+ "github.com/unkeyed/unkey/go/pkg/ptr"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// DeleteDeployment removes a deployment and all associated Kubernetes resources.
+//
+// This method performs a complete cleanup of a deployment by removing both
+// the Service and StatefulSet resources. The cleanup follows Kubernetes
+// best practices for resource deletion with background propagation to
+// ensure associated pods and other resources are properly terminated.
+func (k *k8s) DeleteDeployment(ctx context.Context, req *connect.Request[kranev1.DeleteDeploymentRequest]) (*connect.Response[kranev1.DeleteDeploymentResponse], error) {
+ k8sDeploymentID := strings.ReplaceAll(req.Msg.GetDeploymentId(), "_", "-")
+
+ k.logger.Info("deleting deployment",
+ "namespace", req.Msg.GetNamespace(),
+ "deployment_id", k8sDeploymentID,
+ )
+
+ err := k.clientset.CoreV1().Services(req.Msg.GetNamespace()).Delete(ctx, k8sDeploymentID, metav1.DeleteOptions{
+ PropagationPolicy: ptr.P(metav1.DeletePropagationBackground),
+ })
+ if err != nil && !apierrors.IsNotFound(err) {
+ return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to delete service: %w", err))
+
+ }
+
+ err = k.clientset.AppsV1().StatefulSets(req.Msg.GetNamespace()).Delete(ctx, k8sDeploymentID, metav1.DeleteOptions{
+ PropagationPolicy: ptr.P(metav1.DeletePropagationBackground),
+ })
+ if err != nil && !apierrors.IsNotFound(err) {
+ return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to delete deployment: %w", err))
+ }
+ return connect.NewResponse(&kranev1.DeleteDeploymentResponse{}), nil
+}
diff --git a/go/apps/krane/backend/kubernetes/eviction.go b/go/apps/krane/backend/kubernetes/eviction.go
new file mode 100644
index 0000000000..8430c38b76
--- /dev/null
+++ b/go/apps/krane/backend/kubernetes/eviction.go
@@ -0,0 +1,56 @@
+package kubernetes
+
+import (
+ "context"
+ "time"
+
+ "github.com/unkeyed/unkey/go/pkg/ptr"
+ "github.com/unkeyed/unkey/go/pkg/repeat"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// autoEvictDeployments removes deployments that are older than the specified TTL.
+//
+// This function implements automatic cleanup of old deployments to prevent
+// resource accumulation in development and testing environments. It runs
+// continuously in a background goroutine, scanning for deployments that
+// exceed the configured time-to-live threshold.
+func (k *k8s) autoEvictDeployments(ttl time.Duration) {
+
+ repeat.Every(time.Minute, func() {
+ ctx := context.Background()
+ deployments, err := k.clientset.AppsV1().StatefulSets("unkey").List(ctx, metav1.ListOptions{
+ LabelSelector: "unkey.managed.by=krane",
+ })
+
+ if err != nil {
+ k.logger.Error("failed to list deployments",
+ "error", err.Error(),
+ )
+
+ return
+ }
+
+ for _, deployment := range deployments.Items {
+
+ if time.Since(deployment.GetCreationTimestamp().Time) > ttl {
+ k.logger.Info("deployment is old and will be deleted",
+ "name", deployment.Name,
+ )
+
+ err = k.clientset.AppsV1().StatefulSets("unkey").Delete(ctx, deployment.Name, metav1.DeleteOptions{
+
+ PropagationPolicy: ptr.P(metav1.DeletePropagationBackground),
+ })
+ if err != nil {
+ k.logger.Error("failed to delete deployment",
+ "error", err.Error(),
+ "uid", string(deployment.GetUID()),
+ "name", deployment.Name,
+ )
+ }
+ }
+ }
+
+ })
+}
diff --git a/go/apps/krane/backend/kubernetes/get_deployment.go b/go/apps/krane/backend/kubernetes/get_deployment.go
new file mode 100644
index 0000000000..3de3bc7ec3
--- /dev/null
+++ b/go/apps/krane/backend/kubernetes/get_deployment.go
@@ -0,0 +1,119 @@
+package kubernetes
+
+import (
+ "context"
+ "fmt"
+
+ "connectrpc.com/connect"
+ kranev1 "github.com/unkeyed/unkey/go/gen/proto/krane/v1"
+ "github.com/unkeyed/unkey/go/pkg/assert"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// GetDeployment retrieves the current status and instance information for a Kubernetes deployment.
+//
+// This method queries the Kubernetes API for StatefulSet status and associated
+// pod information to provide a comprehensive view of the deployment state.
+// It returns detailed information about each pod instance including stable
+// DNS addresses, current status, and resource allocation.
+func (k *k8s) GetDeployment(ctx context.Context, req *connect.Request[kranev1.GetDeploymentRequest]) (*connect.Response[kranev1.GetDeploymentResponse], error) {
+
+ err := assert.All(
+ assert.NotEmpty(req.Msg.Namespace),
+ assert.NotEmpty(req.Msg.DeploymentId),
+ )
+ if err != nil {
+ return nil, connect.NewError(connect.CodeInvalidArgument, err)
+ }
+
+ k8sDeploymentID := safeIDForK8s(req.Msg.GetDeploymentId())
+
+ k.logger.Info("getting deployment", "deployment_id", k8sDeploymentID)
+
+ // Get the Job by name (deployment_id)
+ sfs, err := k.clientset.AppsV1().StatefulSets(req.Msg.Namespace).Get(ctx, k8sDeploymentID, metav1.GetOptions{})
+ if err != nil {
+ if errors.IsNotFound(err) {
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("deployment not found: %s", k8sDeploymentID))
+ }
+ return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to get deployment: %w", err))
+ }
+
+ k.logger.Info("deployment retrieved", "deployment", sfs.String())
+
+ // Check if this job is managed by Krane
+ managedBy, exists := sfs.Labels["unkey.managed.by"]
+ if !exists || managedBy != "krane" {
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("deployment not found: %s", k8sDeploymentID))
+ }
+
+ // Determine job status
+ var status kranev1.DeploymentStatus
+ if sfs.Status.AvailableReplicas == sfs.Status.Replicas {
+ status = kranev1.DeploymentStatus_DEPLOYMENT_STATUS_RUNNING
+ } else {
+ status = kranev1.DeploymentStatus_DEPLOYMENT_STATUS_PENDING
+ }
+
+ // Get the service to retrieve port info
+ service, err := k.clientset.CoreV1().Services(req.Msg.GetNamespace()).Get(ctx, k8sDeploymentID, metav1.GetOptions{})
+ if err != nil {
+ return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("could not load service: %s", k8sDeploymentID))
+ }
+ var port int32 = 8080 // default
+ if err == nil && len(service.Spec.Ports) > 0 {
+ port = service.Spec.Ports[0].Port
+ }
+
+ // Get all pods belonging to this stateful set
+ labelSelector := metav1.FormatLabelSelector(&metav1.LabelSelector{
+ MatchLabels: sfs.Spec.Selector.MatchLabels,
+ })
+
+ pods, err := k.clientset.CoreV1().Pods(req.Msg.Namespace).List(ctx, metav1.ListOptions{
+ LabelSelector: labelSelector,
+ })
+ if err != nil {
+ return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to list pods: %w", err))
+ }
+
+ // Build instances from pods
+ var instances []*kranev1.Instance
+ for _, pod := range pods.Items {
+ // Determine pod status
+ var podStatus kranev1.DeploymentStatus
+ switch pod.Status.Phase {
+ case corev1.PodPending:
+ podStatus = kranev1.DeploymentStatus_DEPLOYMENT_STATUS_PENDING
+ case corev1.PodRunning:
+ podStatus = kranev1.DeploymentStatus_DEPLOYMENT_STATUS_RUNNING
+ case corev1.PodFailed:
+ podStatus = kranev1.DeploymentStatus_DEPLOYMENT_STATUS_TERMINATING
+ default:
+ podStatus = kranev1.DeploymentStatus_DEPLOYMENT_STATUS_UNSPECIFIED
+ }
+ //pod-1.my-headless-service.default.svc.cluster.local
+
+ // Create DNS entry for the pod
+ // For StatefulSets, pods have predictable DNS names: ...svc.cluster.local
+ podDNS := fmt.Sprintf("%s.%s.%s.svc.cluster.local:%d", pod.Name, service.Name, pod.Namespace, port)
+ instances = append(instances, &kranev1.Instance{
+ Id: pod.Name,
+ Address: podDNS,
+ Status: podStatus,
+ })
+ }
+
+ k.logger.Info("deployment found",
+ "deployment_id", k8sDeploymentID,
+ "status", status.String(),
+ "port", port,
+ "pod_count", len(instances),
+ )
+
+ return connect.NewResponse(&kranev1.GetDeploymentResponse{
+ Instances: instances,
+ }), nil
+}
diff --git a/go/apps/krane/backend/kubernetes/id.go b/go/apps/krane/backend/kubernetes/id.go
new file mode 100644
index 0000000000..a3445acea1
--- /dev/null
+++ b/go/apps/krane/backend/kubernetes/id.go
@@ -0,0 +1,11 @@
+package kubernetes
+
+import "strings"
+
+// safeIDForK8s converts deployment IDs to Kubernetes-safe resource names.
+//
+// Replaces underscores with hyphens and converts to lowercase to comply
+// with DNS-1123 label requirements.
+func safeIDForK8s(id string) string {
+ return strings.ToLower(strings.ReplaceAll(id, "_", "-"))
+}
diff --git a/go/apps/krane/backend/kubernetes/service.go b/go/apps/krane/backend/kubernetes/service.go
new file mode 100644
index 0000000000..6219e79587
--- /dev/null
+++ b/go/apps/krane/backend/kubernetes/service.go
@@ -0,0 +1,59 @@
+package kubernetes
+
+import (
+ "fmt"
+ "time"
+
+ "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/rest"
+
+ "github.com/unkeyed/unkey/go/gen/proto/krane/v1/kranev1connect"
+ "github.com/unkeyed/unkey/go/pkg/otel/logging"
+)
+
+// k8s implements kranev1connect.DeploymentServiceHandler using the Kubernetes API.
+type k8s struct {
+ logger logging.Logger
+ clientset *kubernetes.Clientset
+ kranev1connect.UnimplementedDeploymentServiceHandler
+}
+
+var _ kranev1connect.DeploymentServiceHandler = (*k8s)(nil)
+
+// Config holds configuration for the Kubernetes backend.
+type Config struct {
+ // Logger for Kubernetes operations.
+ Logger logging.Logger
+
+ // DeploymentEvictionTTL for automatic cleanup of old deployments.
+ // Set to 0 to to disable automatic eviction.
+ DeploymentEvictionTTL time.Duration
+}
+
+// New creates a Kubernetes backend using in-cluster configuration.
+//
+// Requires RBAC permissions for StatefulSets, Services, and Pods.
+// Starts automatic eviction if DeploymentEvictionTTL > 0.
+func New(cfg Config) (*k8s, error) {
+ // Create in-cluster config
+ inClusterConfig, err := rest.InClusterConfig()
+ if err != nil {
+ return nil, fmt.Errorf("failed to create in-cluster config: %w", err)
+ }
+
+ // Create clientset
+ clientset, err := kubernetes.NewForConfig(inClusterConfig)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create Kubernetes clientset: %w", err)
+ }
+ k := &k8s{
+ logger: cfg.Logger,
+ clientset: clientset,
+ }
+
+ if cfg.DeploymentEvictionTTL > 0 {
+ k.autoEvictDeployments(cfg.DeploymentEvictionTTL)
+ }
+
+ return k, nil
+}
diff --git a/go/apps/krane/config.go b/go/apps/krane/config.go
new file mode 100644
index 0000000000..90aba173bb
--- /dev/null
+++ b/go/apps/krane/config.go
@@ -0,0 +1,94 @@
+package krane
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/unkeyed/unkey/go/pkg/clock"
+)
+
+// Backend represents the container orchestration backend type.
+type Backend string
+
+const (
+ // Docker backend uses Docker Engine API for container management.
+ // Suitable for local development and single-node deployments.
+ Docker Backend = "docker"
+
+ // Kubernetes backend uses Kubernetes API for container orchestration.
+ // Designed for production multi-node cluster deployments.
+ Kubernetes Backend = "kubernetes"
+)
+
+// Config holds krane server configuration for Docker or Kubernetes backends.
+type Config struct {
+ // InstanceID is the unique identifier for this instance of the API server.
+ // Used for distributed tracing, logging correlation, and cluster coordination.
+ // Should be unique across all running krane instances.
+ InstanceID string
+
+ // Platform identifies the cloud platform where the node is running.
+ // Examples: "aws", "gcp", "azure", "hetzner", "bare-metal"
+ // Used for observability tagging and platform-specific optimizations.
+ Platform string
+
+ // Image specifies the container image identifier including repository and tag.
+ // This field may be deprecated in future versions as images should be
+ // specified per-deployment rather than globally.
+ Image string
+
+ // HttpPort defines the HTTP port for the API server to listen on.
+ // Default is 7070. The server uses HTTP/2 with h2c for Connect protocol.
+ // Must be available and not conflicting with other services on the host.
+ HttpPort int
+
+ // Region identifies the geographic region where this node is deployed.
+ // Used for observability, latency optimization, and compliance requirements.
+ // Should match the region identifier used by the underlying cloud platform.
+ Region string
+
+ // Backend specifies the container orchestration system to use.
+ // Must be either Docker or Kubernetes. Determines which backend
+ // implementation is instantiated and which configuration fields are required.
+ Backend Backend
+
+ // DockerSocketPath specifies the Docker daemon socket path.
+ // Required when Backend is Docker. Common values:
+ // - "/var/run/docker.sock" (Linux)
+ // - "/var/run/docker.sock" (macOS with Docker Desktop)
+ // The path must be accessible by the krane process with appropriate permissions.
+ DockerSocketPath string
+
+ // OtelEnabled controls whether OpenTelemetry observability data is collected
+ // and sent to configured collectors. When enabled, the service exports
+ // distributed traces, metrics, and structured logs.
+ OtelEnabled bool
+
+ // OtelTraceSamplingRate determines the fraction of traces to sample.
+ // Range: 0.0 (no sampling) to 1.0 (sample all traces).
+ // Recommended values: 0.1 for high-traffic production, 1.0 for development.
+ OtelTraceSamplingRate float64
+
+ // DeploymentEvictionTTL specifies the duration after which idle deployments
+ // are automatically removed from the system. This prevents resource accumulation
+ // in development and testing environments.
+ //
+ // Set to 0 or negative values to disable automatic eviction.
+ // Recommended values:
+ // - Development: 1-4 hours
+ // - Staging: 24 hours
+ // - Production: disabled (0)
+ DeploymentEvictionTTL time.Duration
+
+ // Clock provides time operations for testing and time zone handling.
+ // Use clock.RealClock{} for production, mock clocks for testing.
+ Clock clock.Clock
+}
+
+func (c Config) Validate() error {
+ if c.Backend == Docker && c.DockerSocketPath == "" {
+ return fmt.Errorf("--docker-socket is required when backend is docker")
+ }
+
+ return nil
+}
diff --git a/go/apps/krane/doc.go b/go/apps/krane/doc.go
new file mode 100644
index 0000000000..e9827d1ff1
--- /dev/null
+++ b/go/apps/krane/doc.go
@@ -0,0 +1,28 @@
+// Package krane provides container orchestration with pluggable backends.
+//
+// Krane abstracts Docker and Kubernetes behind a unified gRPC API, enabling
+// the same deployment logic to work in local development (Docker) and
+// production (Kubernetes) environments.
+//
+// # Backends
+//
+// Docker: Direct container management via Docker Engine API. Suitable for
+// development and single-node deployments.
+//
+// Kubernetes: StatefulSet and Service management via client-go. Designed
+// for production clusters with stable DNS names and automatic eviction.
+//
+// # Usage
+//
+// cfg := krane.Config{
+// Backend: krane.Kubernetes,
+// HttpPort: 7070,
+// OtelEnabled: true,
+// }
+// err := krane.Run(context.Background(), cfg)
+//
+// # Service Discovery
+//
+// - Docker: Dynamic port mapping on host.docker.internal
+// - Kubernetes: StatefulSet DNS names (pod.service.namespace.svc.cluster.local)
+package krane
diff --git a/go/apps/krane/run.go b/go/apps/krane/run.go
new file mode 100644
index 0000000000..3e91b1acb3
--- /dev/null
+++ b/go/apps/krane/run.go
@@ -0,0 +1,150 @@
+package krane
+
+import (
+ "context"
+ "fmt"
+ "log/slog"
+ "net/http"
+ "time"
+
+ "github.com/unkeyed/unkey/go/apps/krane/backend/docker"
+ "github.com/unkeyed/unkey/go/apps/krane/backend/kubernetes"
+ "github.com/unkeyed/unkey/go/gen/proto/krane/v1/kranev1connect"
+ "github.com/unkeyed/unkey/go/pkg/otel"
+ "github.com/unkeyed/unkey/go/pkg/otel/logging"
+ "github.com/unkeyed/unkey/go/pkg/shutdown"
+ pkgversion "github.com/unkeyed/unkey/go/pkg/version"
+ "golang.org/x/net/http2"
+ "golang.org/x/net/http2/h2c"
+)
+
+// Run starts the krane server with the provided configuration.
+//
+// Initializes the selected backend (Docker or Kubernetes), sets up HTTP/2
+// server with Connect protocol, and handles graceful shutdown on context
+// cancellation.
+//
+// When cfg.OtelEnabled is true, initializes OpenTelemetry tracing, metrics,
+// and logging integration.
+func Run(ctx context.Context, cfg Config) error {
+ err := cfg.Validate()
+ if err != nil {
+ return fmt.Errorf("bad config: %w", err)
+ }
+
+ shutdowns := shutdown.New()
+
+ if cfg.OtelEnabled {
+ grafanaErr := otel.InitGrafana(ctx, otel.Config{
+ Application: "krane",
+ Version: pkgversion.Version,
+ InstanceID: cfg.InstanceID,
+ CloudRegion: cfg.Region,
+ TraceSampleRate: cfg.OtelTraceSamplingRate,
+ },
+ shutdowns,
+ )
+ if grafanaErr != nil {
+ return fmt.Errorf("unable to init grafana: %w", grafanaErr)
+ }
+ }
+
+ logger := logging.New()
+ if cfg.InstanceID != "" {
+ logger = logger.With(slog.String("instanceID", cfg.InstanceID))
+ }
+ if cfg.Platform != "" {
+ logger = logger.With(slog.String("platform", cfg.Platform))
+ }
+ if cfg.Region != "" {
+ logger = logger.With(slog.String("region", cfg.Region))
+ }
+ if pkgversion.Version != "" {
+ logger = logger.With(slog.String("version", pkgversion.Version))
+ }
+
+ // Create the connect handler
+ mux := http.NewServeMux()
+
+ var svc kranev1connect.DeploymentServiceHandler
+ switch cfg.Backend {
+ case Kubernetes:
+ {
+
+ svc, err = kubernetes.New(kubernetes.Config{
+ Logger: logger,
+ DeploymentEvictionTTL: cfg.DeploymentEvictionTTL,
+ })
+ if err != nil {
+ return fmt.Errorf("unable to init kubernetes backend: %w", err)
+ }
+ break
+ }
+ case Docker:
+ {
+ svc, err = docker.New(logger, cfg.DockerSocketPath)
+ if err != nil {
+ return fmt.Errorf("unable to init docker backend: %w", err)
+ }
+ }
+ default:
+ return fmt.Errorf("unsupported backend: %s", cfg.Backend)
+ }
+
+ // Create the service handlers with interceptors
+ //
+ mux.Handle(kranev1connect.NewDeploymentServiceHandler(svc))
+
+ // Configure server
+ addr := fmt.Sprintf(":%d", cfg.HttpPort)
+
+ // Use h2c for HTTP/2 without TLS (for development)
+ h2cHandler := h2c.NewHandler(mux, &http2.Server{
+ MaxHandlers: 0,
+ MaxConcurrentStreams: 0,
+ MaxDecoderHeaderTableSize: 0,
+ MaxEncoderHeaderTableSize: 0,
+ MaxReadFrameSize: 0,
+ PermitProhibitedCipherSuites: false,
+ IdleTimeout: 0,
+ ReadIdleTimeout: 0,
+ PingTimeout: 0,
+ WriteByteTimeout: 0,
+ MaxUploadBufferPerConnection: 0,
+ MaxUploadBufferPerStream: 0,
+ NewWriteScheduler: nil,
+ CountError: nil,
+ })
+
+ server := &http.Server{
+ Addr: addr,
+ Handler: h2cHandler,
+ ReadTimeout: 10 * time.Second,
+ WriteTimeout: 10 * time.Second,
+ IdleTimeout: 120 * time.Second,
+ }
+
+ // Register server shutdown
+ shutdowns.RegisterCtx(server.Shutdown)
+
+ // Start server
+ go func() {
+ logger.Info("Starting krane server", "addr", addr)
+
+ listenErr := server.ListenAndServe()
+
+ if listenErr != nil && listenErr != http.ErrServerClosed {
+ logger.Error("Server failed", "error", listenErr.Error())
+ }
+ }()
+
+ // Wait for signal and handle shutdown
+ logger.Info("Krane server started successfully")
+ if err := shutdowns.WaitForSignal(ctx); err != nil {
+ logger.Error("Shutdown failed", "error", err)
+ return err
+ }
+
+ logger.Info("krane server shut down successfully")
+ return nil
+}
diff --git a/go/apps/metald/.gitignore b/go/apps/metald/.gitignore
deleted file mode 100644
index 804760d3e5..0000000000
--- a/go/apps/metald/.gitignore
+++ /dev/null
@@ -1,87 +0,0 @@
-# Compiled binaries (SECURITY: Never commit compiled binaries)
-build/
-
-# Test binaries, built with `go test -c`
-*.test
-
-# Output of the go coverage tool
-*.out
-
-# Dependency directories (remove the comment below to include it)
-vendor/
-
-# Go workspace file
-go.work
-go.work.sum
-
-# IDE files
-.vscode/
-.idea/
-*.swp
-*.swo
-*~
-
-# OS files
-.DS_Store
-Thumbs.db
-
-# Local development files
-.env
-.env.local
-.env.development
-.env.test
-.env.production
-
-# Temporary files
-tmp/
-temp/
-*.tmp
-
-# Logs
-*.log
-logs/
-
-# Database files
-*.db
-*.sqlite
-*.sqlite3
-
-# Build artifacts and cache
-dist/
-cache/
-.cache/
-
-# Coverage reports
-coverage.html
-coverage.out
-profile.out
-
-# Backup files
-*.bak
-*.backup
-
-# Docker build context (if using dockerignore isn't sufficient)
-.dockerignore
-
-# Certificate files (never commit certificates or keys)
-*.pem
-*.key
-*.crt
-*.p12
-*.pfx
-
-# Secret files
-secrets.yaml
-secrets.json
-.secrets
-
-# Local storage directories for development
-data/
-scratch/
-rootfs/
-workspace/
-
-# binaries from make etc
-client/metald-cli
-cmd/metald-init/metald-init
-cmd/metald/metald
diff --git a/go/apps/metald/CHANGELOG.md b/go/apps/metald/CHANGELOG.md
deleted file mode 100644
index 87cc541e79..0000000000
--- a/go/apps/metald/CHANGELOG.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# Changelog
-
-All notable changes to metald will be documented in this file.
-
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-
-## [0.5.2] - 2025-07-02
-
-### Changed
-- Update main.go
-
diff --git a/go/apps/metald/Makefile b/go/apps/metald/Makefile
deleted file mode 100644
index e59636e4a0..0000000000
--- a/go/apps/metald/Makefile
+++ /dev/null
@@ -1,111 +0,0 @@
-# Metald VM Management Service Makefile
-
-.DEFAULT_GOAL := help
-
-# Variables
-BINARY_NAME := metald
-BUILD_DIR := build
-VERSION ?= 0.5.2
-GOOS ?= $(shell go env GOOS)
-GOARCH ?= $(shell go env GOARCH)
-LDFLAGS := -ldflags "-s=false -w=false -X main.version=$(VERSION)"
-
-# Colors for output
-CYAN := \033[36m
-RESET := \033[0m
-
-# Targets (alphabetically ordered)
-.PHONY: build build-linux check ci clean deps dev fmt help install install-bridge-8 install-bridge-32 lint release run service-logs service-logs-full service-restart service-start service-status service-stop test test-coverage test-short uninstall uninstall-bridge-systemd version vet apply-bridge-8-config apply-bridge-32-config
-
-build: deps ## Build the binary
- @mkdir -p $(BUILD_DIR)
- go build $(LDFLAGS) -gcflags="all=-N -l" -o $(BUILD_DIR)/$(BINARY_NAME) ./cmd/metald
-
-build-linux: ## Build Linux binary for deployment
- @mkdir -p $(BUILD_DIR)
- @GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-linux ./cmd/metald
-
-check: fmt vet lint test ## Run all checks (fmt, vet, lint with proto, test)
-
-ci: deps lint vet test build ## Run CI pipeline locally
-
-clean: ## Clean build artifacts
- @rm -rf $(BUILD_DIR)
- @rm -f coverage.out coverage.html
-
-deps: ## Download and tidy dependencies
- @go mod download
- @go mod tidy
-
-fmt: ## Format Go code
- @goimports -w .
- @gofumpt -w .
-
-help: ## Display this help message
- @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make $(CYAN)$(RESET)\n"} /^[a-zA-Z0-9_-]+:.*##/ { printf " $(CYAN)%-20s$(RESET) %s\n", $$1, $$2 } /^##@/ { printf "\n%s\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
-
-install: build ## Install metald binary and systemd service
- @sudo systemctl stop metald 2>/dev/null || true
- @sudo cp $(BUILD_DIR)/$(BINARY_NAME) /usr/local/bin/$(BINARY_NAME)
- @sudo chmod +x /usr/local/bin/$(BINARY_NAME)
- @sudo cp contrib/systemd/metald.service /etc/systemd/system/metald.service
- @sudo systemctl daemon-reload
- @sudo systemctl start metald 2>/dev/null || true
- @echo "✓ metald installed and started"
-
-lint: ## Run linting tools
- @which golangci-lint >/dev/null || (echo "golangci-lint not found, install from https://golangci-lint.run/usage/install/" && exit 1)
- @golangci-lint run --disable=godox
-
-release: clean ci build-linux ## Prepare release build
- @echo "✓ Release build: $(BUILD_DIR)/$(BINARY_NAME)-linux"
-
-run: build ## Build and run the service
- @./$(BUILD_DIR)/$(BINARY_NAME)
-
-service-logs: ## Follow metald service logs
- @sudo journalctl -u metald -f
-
-service-logs-full: ## Show all metald service logs
- @sudo journalctl -u metald --no-pager
-
-service-restart: ## Restart metald service
- @sudo systemctl restart metald
- @echo "✓ metald restarted"
-
-service-start: ## Start metald service
- @sudo systemctl start metald
- @echo "✓ metald started"
-
-service-status: ## Show metald service status
- @sudo systemctl status metald
-
-service-stop: ## Stop metald service
- @sudo systemctl stop metald
- @echo "✓ metald stopped"
-
-test: ## Run all tests
- @go test -json -failfast -v ./... | go run github.com/mfridman/tparse@ba2512e7be150bfcbd6f6220d517d3741f8f2f75 -all -smallscreen
-
-test-coverage: ## Run tests with coverage report
- @go test ./... -coverprofile=coverage.out
- @go tool cover -html=coverage.out -o coverage.html
- @echo "✓ Coverage report: coverage.html"
-
-test-short: ## Run tests in short mode
- @go test ./... -short
-
-uninstall: ## Uninstall metald service and binary
- @sudo systemctl stop metald 2>/dev/null || true
- @sudo systemctl disable metald 2>/dev/null || true
- @sudo rm -f /etc/systemd/system/metald.service
- @sudo rm -f /usr/local/bin/$(BINARY_NAME)
- @sudo rm -f /etc/sudoers.d/metald
- @sudo systemctl daemon-reload
- @echo "✓ metald uninstalled"
-
-version: ## Show version information
- @echo "$(BINARY_NAME) version: $(VERSION)"
-
-vet: ## Run go vet
- @go vet ./...
diff --git a/go/apps/metald/README.md b/go/apps/metald/README.md
deleted file mode 100644
index 5e267b45e1..0000000000
--- a/go/apps/metald/README.md
+++ /dev/null
@@ -1,90 +0,0 @@
-# Metald - VM Lifecycle Management Service
-
-High-performance VM lifecycle management with integrated security isolation and real-time billing.
-
-## Overview
-
-Metald is the central control plane for virtual machine lifecycle management in the Unkey Deploy platform. It provides a unified API for creating, managing, and monitoring microVMs using Firecracker.
-
-**Key Features:**
-- **Integrated jailer** for security isolation (no external jailer binary needed)
-- **Real-time billing** integration with 100ms precision
-- **Dual-stack networking** with IPv4/IPv6 support and multi-tenant isolation
-- **Asset management** integration for dynamic VM image distribution
-- **Production-ready** with comprehensive observability and monitoring
-
-## Documentation
-
-For comprehensive documentation, see [**📚 Full Documentation**](./docs/README.md)
-
-**Quick Links:**
-- [API Reference](./docs/api/README.md) - Complete API documentation with examples
-- [Architecture Guide](./docs/architecture/README.md) - System design and service interactions
-- [Operations Manual](./docs/operations/README.md) - Production deployment and monitoring
-- [Development Setup](./docs/development/README.md) - Build instructions and contributing guide
-
-## Quick Start
-
-```bash
-# Build from source
-make build
-
-# Install with systemd
-sudo make install
-
-# Run development server
-export UNKEY_METALD_BILLING_MOCK_MODE=true
-export UNKEY_METALD_ASSETMANAGER_ENABLED=false
-./build/metald
-```
-
-### Create Your First VM
-
-```bash
-# Using the example client
-cd contrib/example-client
-go run main.go -action create-and-boot
-
-# Or via direct API call
-curl -X POST http://localhost:8080/vmprovisioner.v1.VmService/CreateVm \
- -H "Content-Type: application/json" \
- -H "Authorization: Bearer dev_customer_test123" \
- -d '{
- "config": {
- "cpu": {"vcpu_count": 2},
- "memory": {"size_bytes": 1073741824},
- "boot": {
- "kernel_path": "/opt/vm-assets/vmlinux",
- "kernel_args": "console=ttyS0 reboot=k panic=1"
- }
- }
- }'
-```
-
-## Service Dependencies
-
-Metald integrates with other Unkey Deploy services:
-- **[assetmanagerd](../assetmanagerd/docs/README.md)** - VM asset preparation and distribution
-- **[billaged](../billaged/docs/README.md)** - Usage tracking and billing
-- **builderd** - Indirect integration through assetmanagerd
-
-## Requirements
-
-- Linux with KVM support
-- Firecracker binary installed
-- Go 1.24+ (for building)
-- systemd (for production deployment)
-
-## Security
-
-Metald runs as root to manage network namespaces, interfaces, and iptables operations. This is acceptable as metald is designed to be the sole application on dedicated VM hosts. The integrated jailer still drops privileges to specified UID/GID for individual VM processes, ensuring proper isolation.
-
-The `make install` command configures the service with appropriate permissions automatically.
-
-## Contributing
-
-See [Development Setup](./docs/development/README.md) for contribution guidelines.
-
-## Version
-
-v0.2.0 (Integrated Jailer)
\ No newline at end of file
diff --git a/go/apps/metald/backend.go b/go/apps/metald/backend.go
deleted file mode 100644
index acd7f86bdb..0000000000
--- a/go/apps/metald/backend.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package metald
-
-import (
- "context"
- "fmt"
- "log/slog"
-
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/docker"
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/kubernetes"
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/types"
- "github.com/unkeyed/unkey/go/apps/metald/internal/config"
- "github.com/unkeyed/unkey/go/pkg/otel/logging"
-)
-
-// convertToInternalConfig converts the external config to internal config format
-func convertToInternalConfig(cfg *Config) *config.Config {
- return &config.Config{
- Server: config.ServerConfig{
- Address: cfg.Server.Address,
- Port: cfg.Server.Port,
- },
- Backend: config.BackendConfig{
- Type: types.BackendType(cfg.Backend.Type),
- Jailer: config.JailerConfig{
- UID: cfg.Backend.Jailer.UID,
- GID: cfg.Backend.Jailer.GID,
- ChrootBaseDir: cfg.Backend.Jailer.ChrootBaseDir,
- },
- },
- Database: config.DatabaseConfig{
- DataDir: cfg.Database.DataDir,
- },
- AssetManager: config.AssetManagerConfig{
- Enabled: cfg.AssetManager.Enabled,
- Endpoint: cfg.AssetManager.Endpoint,
- },
- Billing: config.BillingConfig{
- Enabled: cfg.Billing.Enabled,
- Endpoint: cfg.Billing.Endpoint,
- MockMode: cfg.Billing.MockMode,
- },
- TLS: &config.TLSConfig{
- Mode: cfg.TLS.Mode,
- CertFile: cfg.TLS.CertFile,
- KeyFile: cfg.TLS.KeyFile,
- CAFile: cfg.TLS.CAFile,
- SPIFFESocketPath: cfg.TLS.SPIFFESocketPath,
- EnableCertCaching: cfg.TLS.EnableCertCaching,
- CertCacheTTL: cfg.TLS.CertCacheTTL,
- },
- OpenTelemetry: config.OpenTelemetryConfig{
- Enabled: cfg.OpenTelemetry.Enabled,
- ServiceName: cfg.OpenTelemetry.ServiceName,
- ServiceVersion: cfg.OpenTelemetry.ServiceVersion,
- TracingSamplingRate: cfg.OpenTelemetry.TracingSamplingRate,
- OTLPEndpoint: cfg.OpenTelemetry.OTLPEndpoint,
- PrometheusEnabled: cfg.OpenTelemetry.PrometheusEnabled,
- PrometheusPort: cfg.OpenTelemetry.PrometheusPort,
- PrometheusInterface: cfg.OpenTelemetry.PrometheusInterface,
- HighCardinalityLabelsEnabled: cfg.OpenTelemetry.HighCardinalityLabelsEnabled,
- },
- }
-}
-
-// initializeK8sBackend creates a Kubernetes backend
-func initializeK8sBackend(ctx context.Context, cfg *Config, logger *slog.Logger) (types.Backend, error) {
- // Create logging.Logger from slog.Logger
- loggingLogger := logging.New().With("backend", "kubernetes")
-
- backend, err := kubernetes.New(loggingLogger)
- if err != nil {
- return nil, fmt.Errorf("failed to initialize Kubernetes backend: %w", err)
- }
-
- return backend, nil
-}
-
-// initializeDockerBackend creates a docker backend
-func initializeDockerBackend(ctx context.Context, cfg *Config, logger *slog.Logger) (types.Backend, error) {
- // Create logging.Logger from slog.Logger
- loggingLogger := logging.New().With("backend", "docker")
-
- backend, err := docker.New(loggingLogger)
- if err != nil {
- return nil, fmt.Errorf("failed to initialize Docker backend: %w", err)
- }
-
- return backend, nil
-}
diff --git a/go/apps/metald/backend_linux.go b/go/apps/metald/backend_linux.go
deleted file mode 100644
index 879ab918a0..0000000000
--- a/go/apps/metald/backend_linux.go
+++ /dev/null
@@ -1,73 +0,0 @@
-//go:build linux
-// +build linux
-
-package metald
-
-import (
- "context"
- "fmt"
- "log/slog"
-
- "github.com/unkeyed/unkey/go/apps/metald/internal/assetmanager"
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/firecracker"
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/types"
- "github.com/unkeyed/unkey/go/apps/metald/internal/config"
- tlspkg "github.com/unkeyed/unkey/go/deploy/pkg/tls"
-)
-
-// initializeFirecrackerBackend creates a firecracker backend (Linux only)
-func initializeFirecrackerBackend(ctx context.Context, cfg *Config, logger *slog.Logger, tlsProvider tlspkg.Provider) (types.Backend, error) {
- // Base directory for VM data
- baseDir := "/opt/metald/vms"
-
- // Create AssetManager client for asset preparation
- var assetClient assetmanager.Client
- var err error
-
- if cfg.AssetManager.Enabled {
- // Use TLS-enabled HTTP client
- httpClient := tlsProvider.HTTPClient()
-
- // Convert to internal AssetManager config
- assetCfg := &config.AssetManagerConfig{
- Enabled: cfg.AssetManager.Enabled,
- Endpoint: cfg.AssetManager.Endpoint,
- }
-
- assetClient, err = assetmanager.NewClientWithHTTP(assetCfg, logger, httpClient)
- if err != nil {
- return nil, fmt.Errorf("failed to create assetmanager client: %w", err)
- }
- logger.Info("initialized assetmanager client",
- slog.String("endpoint", cfg.AssetManager.Endpoint),
- )
- } else {
- // Use noop client if assetmanager is disabled
- assetCfg := &config.AssetManagerConfig{
- Enabled: false,
- }
- assetClient, _ = assetmanager.NewClient(assetCfg, logger)
- logger.Info("assetmanager disabled, using noop client")
- }
-
- // Convert to internal Jailer config
- jailerCfg := &config.JailerConfig{
- UID: cfg.Backend.Jailer.UID,
- GID: cfg.Backend.Jailer.GID,
- ChrootBaseDir: cfg.Backend.Jailer.ChrootBaseDir,
- }
-
- sdkClient, err := firecracker.NewClient(logger, assetClient, jailerCfg, baseDir)
- if err != nil {
- return nil, fmt.Errorf("failed to create firecracker client: %w", err)
- }
-
- logger.Info("initialized firecracker backend",
- slog.String("firecracker_binary", "/usr/local/bin/firecracker"),
- slog.Uint64("uid", uint64(cfg.Backend.Jailer.UID)),
- slog.Uint64("gid", uint64(cfg.Backend.Jailer.GID)),
- slog.String("chroot_base", cfg.Backend.Jailer.ChrootBaseDir),
- )
-
- return sdkClient, nil
-}
diff --git a/go/apps/metald/backend_stub.go b/go/apps/metald/backend_stub.go
deleted file mode 100644
index fd172a1c1d..0000000000
--- a/go/apps/metald/backend_stub.go
+++ /dev/null
@@ -1,18 +0,0 @@
-//go:build !linux
-// +build !linux
-
-package metald
-
-import (
- "context"
- "fmt"
- "log/slog"
-
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/types"
- tlspkg "github.com/unkeyed/unkey/go/deploy/pkg/tls"
-)
-
-// initializeFirecrackerBackend returns an error on non-Linux platforms
-func initializeFirecrackerBackend(ctx context.Context, cfg *Config, logger *slog.Logger, tlsProvider tlspkg.Provider) (types.Backend, error) {
- return nil, fmt.Errorf("firecracker backend is only supported on Linux")
-}
diff --git a/go/apps/metald/client/Makefile b/go/apps/metald/client/Makefile
deleted file mode 100644
index 0e229d7304..0000000000
--- a/go/apps/metald/client/Makefile
+++ /dev/null
@@ -1,38 +0,0 @@
-# Makefile for metald CLI client
-
-# Variables
-BINARY_NAME := metald-cli
-BUILD_DIR := build
-VERSION ?= 0.5.2
-
-# Default target
-.DEFAULT_GOAL := help
-
-# Targets (alphabetically ordered)
-
-.PHONY: build
-build: ## Build the metald CLI client
- @echo "Building $(BINARY_NAME)..."
- @mkdir -p $(BUILD_DIR)
- @go build -o $(BUILD_DIR)/$(BINARY_NAME) cmd/metald-cli/main.go
- @echo "Build complete: $(BUILD_DIR)/$(BINARY_NAME)"
-
-.PHONY: clean
-clean: ## Clean build artifacts
- @echo "Cleaning..."
- @rm -rf $(BUILD_DIR)
-
-.PHONY: help
-help: ## Show this help message
- @echo "Available targets:"
- @echo " build - Build the metald CLI client"
- @echo " clean - Clean build artifacts"
- @echo " install - Install the CLI client to /usr/local/bin"
- @echo " help - Show this help message"
-
-.PHONY: install
-install: build ## Install the CLI client to /usr/local/bin
- @echo "Installing $(BINARY_NAME) to /usr/local/bin..."
- @sudo mv $(BUILD_DIR)/$(BINARY_NAME) /usr/local/bin/$(BINARY_NAME)
- @sudo chmod +x /usr/local/bin/$(BINARY_NAME)
- @echo "Installation complete"
\ No newline at end of file
diff --git a/go/apps/metald/client/README.md b/go/apps/metald/client/README.md
deleted file mode 100644
index 8c97df31d7..0000000000
--- a/go/apps/metald/client/README.md
+++ /dev/null
@@ -1,670 +0,0 @@
-# Metald Client
-
-A Go client library for the metald VM provisioning service with built-in SPIFFE/SPIRE socket integration and tenant isolation.
-
-## Features
-
-- **SPIFFE/SPIRE Integration**: Automatic mTLS authentication using SPIFFE workload API
-- **Tenant Isolation**: Customer ID authentication for multi-tenant environments
-- **Complete VM Lifecycle**: Create, boot, pause, resume, reboot, shutdown, delete operations
-- **TLS Modes**: Support for SPIFFE, file-based, and disabled TLS modes
-- **High-Level Interface**: Clean Go API wrapping ConnectRPC/protobuf internals
-- **Connection Management**: Automatic certificate rotation and connection pooling
-
-## Quick Start
-
-### Basic Usage
-
-```go
-package main
-
-import (
- "context"
- "log"
- "time"
-
- "github.com/unkeyed/unkey/go/deploy/metald/client"
-)
-
-func main() {
- ctx := context.Background()
-
- // Create client with SPIFFE authentication
- config := client.Config{
- ServerAddress: "https://metald:8080",
- UserID: "my-user-123",
- TenantID: "my-tenant-456",
- TLSMode: "spiffe",
- SPIFFESocketPath: "/var/lib/spire/agent/agent.sock",
- Timeout: 30 * time.Second,
- }
-
- metaldClient, err := client.New(ctx, config)
- if err != nil {
- log.Fatalf("Failed to create client: %v", err)
- }
- defer metaldClient.Close()
-
- // Create VM using a template
- vmConfig := client.NewVMConfigFromTemplate(client.TemplateStandard).
- WithCPU(4, 8).
- WithMemoryGB(4, 16, true).
- AddRootStorage("/opt/vm-assets/rootfs.ext4").
- AddDefaultNetwork().
- AddMetadata("purpose", "web-server").
- Build()
-
- createResp, err := metaldClient.CreateVM(ctx, &client.CreateVMRequest{
- Config: vmConfig,
- })
- if err != nil {
- log.Fatalf("Failed to create VM: %v", err)
- }
-
- bootResp, err := metaldClient.BootVM(ctx, createResp.VMID)
- if err != nil {
- log.Fatalf("Failed to boot VM: %v", err)
- }
-
- log.Printf("VM %s is now %s", createResp.VMID, bootResp.State)
-}
-```
-
-### Using VM Configuration Builder
-
-```go
-// Build a custom VM configuration
-vmConfig := client.NewVMConfigBuilder().
- WithCPU(8, 16). // 8 vCPUs, max 16
- WithMemoryGB(16, 64, true). // 16GB RAM, max 64GB, hotplug enabled
- WithDefaultBoot("console=ttyS0 reboot=k panic=1"). // Standard boot config
- AddRootStorage("/opt/vm-assets/ubuntu-rootfs.ext4"). // Root filesystem
- AddDataStorage("data", "/opt/vm-assets/data.ext4", false). // Additional storage
- AddDefaultNetwork(). // Standard dual-stack network
- WithDefaultConsole("/var/log/vm-console.log"). // Console logging
- AddMetadata("environment", "production"). // Custom metadata
- AddMetadata("owner", "platform-team").
- Build()
-
-// Validate configuration before use
-builder := client.NewVMConfigBuilder()
-builder.config = vmConfig
-if err := builder.Validate(); err != nil {
- log.Fatalf("Invalid VM configuration: %v", err)
-}
-```
-
-## Configuration
-
-### TLS Modes
-
-#### SPIFFE Mode (Recommended for Production)
-```go
-config := client.Config{
- ServerAddress: "https://metald:8080",
- CustomerID: "customer-123",
- TLSMode: "spiffe",
- SPIFFESocketPath: "/var/lib/spire/agent/agent.sock",
-}
-```
-
-#### File-based TLS Mode
-```go
-config := client.Config{
- ServerAddress: "https://metald:8080",
- CustomerID: "customer-123",
- TLSMode: "file",
- TLSCertFile: "/etc/ssl/certs/client.crt",
- TLSKeyFile: "/etc/ssl/private/client.key",
- TLSCAFile: "/etc/ssl/certs/ca.crt",
-}
-```
-
-#### Disabled TLS Mode (Development Only)
-```go
-config := client.Config{
- ServerAddress: "http://localhost:8080",
- CustomerID: "dev-customer",
- TLSMode: "disabled",
-}
-```
-
-### Environment Variables
-
-The client respects standard environment variable patterns:
-
-```bash
-# SPIFFE socket path (if not specified in config)
-export UNKEY_METALD_SPIFFE_SOCKET="/run/spire/sockets/agent.sock"
-
-# Server address
-export UNKEY_METALD_SERVER_ADDRESS="https://metald.internal:8080"
-
-# User and tenant IDs for authentication
-export UNKEY_METALD_USER_ID="user-123"
-export UNKEY_METALD_TENANT_ID="tenant-456"
-```
-
-## VM Configuration
-
-### Built-in Templates
-
-The client provides several built-in VM templates for common use cases:
-
-```go
-// Minimal VM (512MB RAM, 1 vCPU)
-config := client.NewVMConfigFromTemplate(client.TemplateMinimal).Build()
-
-// Standard VM (2GB RAM, 2 vCPUs)
-config := client.NewVMConfigFromTemplate(client.TemplateStandard).Build()
-
-// High-CPU VM (4GB RAM, 8 vCPUs)
-config := client.NewVMConfigFromTemplate(client.TemplateHighCPU).Build()
-
-// High-Memory VM (16GB RAM, 4 vCPUs)
-config := client.NewVMConfigFromTemplate(client.TemplateHighMemory).Build()
-
-// Development VM (8GB RAM, 4 vCPUs, extra storage)
-config := client.NewVMConfigFromTemplate(client.TemplateDevelopment).Build()
-```
-
-### Configuration Builder Methods
-
-#### CPU Configuration
-```go
-builder.WithCPU(vcpuCount, maxVcpuCount uint32)
-```
-
-#### Memory Configuration
-```go
-// Set memory in bytes
-builder.WithMemory(sizeBytes, maxSizeBytes uint64, hotplugEnabled bool)
-
-// Set memory in MB (convenience method)
-builder.WithMemoryMB(sizeMB, maxSizeMB uint64, hotplugEnabled bool)
-
-// Set memory in GB (convenience method)
-builder.WithMemoryGB(sizeGB, maxSizeGB uint64, hotplugEnabled bool)
-```
-
-#### Boot Configuration
-```go
-// Full boot configuration
-builder.WithBoot(kernelPath, initrdPath, kernelArgs string)
-
-// Default boot with custom kernel args
-builder.WithDefaultBoot(kernelArgs string)
-```
-
-#### Storage Configuration
-```go
-// Add storage device
-builder.AddStorage(id, path string, readOnly, isRoot bool, interfaceType string)
-
-// Add root filesystem (convenience method)
-builder.AddRootStorage(path string)
-
-// Add data storage (convenience method)
-builder.AddDataStorage(id, path string, readOnly bool)
-
-// Add storage with custom options
-builder.AddStorageWithOptions(id, path string, readOnly, isRoot bool,
- interfaceType string, options map[string]string)
-```
-
-#### Network Configuration
-```go
-// Add network interface
-builder.AddNetwork(id, interfaceType string, mode vmprovisionerv1.NetworkMode)
-
-// Add default dual-stack network
-builder.AddDefaultNetwork()
-
-// Add IPv4-only network
-builder.AddIPv4OnlyNetwork(id string)
-
-// Add IPv6-only network
-builder.AddIPv6OnlyNetwork(id string)
-
-// Add network with custom IPv4/IPv6 configuration
-builder.AddNetworkWithCustomConfig(id, interfaceType string, mode vmprovisionerv1.NetworkMode,
- ipv4Config *vmprovisionerv1.IPv4Config, ipv6Config *vmprovisionerv1.IPv6Config)
-```
-
-#### Console Configuration
-```go
-// Configure console
-builder.WithConsole(enabled bool, output, consoleType string)
-
-// Default console configuration
-builder.WithDefaultConsole(output string)
-
-// Disable console
-builder.DisableConsole()
-```
-
-#### Metadata
-```go
-// Add single metadata entry
-builder.AddMetadata(key, value string)
-
-// Set all metadata at once
-builder.WithMetadata(metadata map[string]string)
-```
-
-#### Docker Integration
-```go
-// Configure VM for Docker image
-builder.ForDockerImage(imageName string)
-```
-
-### Configuration Files
-
-#### Creating Configuration Files
-
-You can save VM configurations as JSON files for reuse:
-
-```go
-// Create configuration
-config := client.NewVMConfigFromTemplate(client.TemplateStandard).
- WithCPU(4, 8).
- WithMemoryGB(8, 32, true).
- Build()
-
-// Convert to file format
-configFile := client.FromVMConfig(config, "web-server", "Configuration for web server VMs")
-
-// Save to file
-err := client.SaveVMConfigToFile(configFile, "configs/web-server.json")
-```
-
-#### Loading Configuration Files
-
-```go
-// Load configuration from file
-configFile, err := client.LoadVMConfigFromFile("configs/web-server.json")
-if err != nil {
- log.Fatalf("Failed to load config: %v", err)
-}
-
-// Convert to VM configuration
-vmConfig, err := configFile.ToVMConfig()
-if err != nil {
- log.Fatalf("Failed to convert config: %v", err)
-}
-
-// Use in VM creation
-resp, err := client.CreateVM(ctx, &client.CreateVMRequest{
- Config: vmConfig,
-})
-```
-
-#### Configuration File Format
-
-```json
-{
- "name": "web-server",
- "description": "Configuration for web server VMs",
- "template": "standard",
- "cpu": {
- "vcpu_count": 4,
- "max_vcpu_count": 8
- },
- "memory": {
- "size_mb": 8192,
- "max_size_mb": 32768,
- "hotplug_enabled": true
- },
- "boot": {
- "kernel_path": "/opt/vm-assets/vmlinux",
- "kernel_args": "console=ttyS0 reboot=k panic=1 pci=off"
- },
- "storage": [
- {
- "id": "rootfs",
- "path": "/opt/vm-assets/rootfs.ext4",
- "read_only": false,
- "is_root_device": true,
- "interface_type": "virtio-blk"
- }
- ],
- "network": [
- {
- "id": "eth0",
- "interface_type": "virtio-net",
- "mode": "dual_stack",
- "ipv4": {
- "dhcp": true
- },
- "ipv6": {
- "slaac": true,
- "privacy_extensions": true
- }
- }
- ],
- "console": {
- "enabled": true,
- "output": "/tmp/vm-console.log",
- "console_type": "serial"
- },
- "metadata": {
- "purpose": "web-server",
- "environment": "production"
- }
-}
-```
-
-## API Reference
-
-### VM Lifecycle Operations
-
-#### CreateVM
-```go
-resp, err := client.CreateVM(ctx, &client.CreateVMRequest{
- VMID: "optional-vm-id", // Auto-generated if empty
- Config: vmConfig,
-})
-```
-
-#### BootVM
-```go
-resp, err := client.BootVM(ctx, vmID)
-```
-
-#### ShutdownVM
-```go
-resp, err := client.ShutdownVM(ctx, &client.ShutdownVMRequest{
- VMID: vmID,
- Force: false,
- TimeoutSeconds: 30,
-})
-```
-
-#### DeleteVM
-```go
-resp, err := client.DeleteVM(ctx, &client.DeleteVMRequest{
- VMID: vmID,
- Force: false,
-})
-```
-
-### VM Information Operations
-
-#### GetVMInfo
-```go
-vmInfo, err := client.GetVMInfo(ctx, vmID)
-// Returns detailed VM info including config, metrics, and network info
-```
-
-#### ListVMs
-```go
-resp, err := client.ListVMs(ctx, &client.ListVMsRequest{
- PageSize: 50,
- PageToken: "", // Empty for first page
-})
-// Returns paginated list of VMs for the authenticated customer
-```
-
-### VM Control Operations
-
-#### PauseVM / ResumeVM
-```go
-pauseResp, err := client.PauseVM(ctx, vmID)
-resumeResp, err := client.ResumeVM(ctx, vmID)
-```
-
-#### RebootVM
-```go
-resp, err := client.RebootVM(ctx, &client.RebootVMRequest{
- VMID: vmID,
- Force: false, // Graceful vs forced reboot
-})
-```
-
-## Authentication & Tenant Isolation
-
-### Customer ID Authentication
-
-The client automatically adds the appropriate `Authorization` header to all requests:
-
-- **Development Mode**: `Bearer dev_customer_`
-- **Production Mode**: Would use real JWT tokens or API keys
-
-### SPIFFE Workload Identity
-
-When using SPIFFE mode, the client:
-
-1. Connects to the SPIFFE agent socket (default: `/var/lib/spire/agent/agent.sock`)
-2. Retrieves X.509 SVIDs for mTLS authentication
-3. Automatically rotates certificates as they expire
-4. Validates server certificates against the same trust domain
-
-### Tenant Isolation
-
-All VM operations are automatically scoped to the authenticated customer:
-
-- VMs are only visible to their owning customer
-- Customer ID is extracted from authentication tokens
-- Database queries include customer-scoped filtering
-
-## Error Handling
-
-The client provides structured error handling:
-
-```go
-vmInfo, err := client.GetVMInfo(ctx, "non-existent-vm")
-if err != nil {
- // Error includes details about the failure
- log.Printf("Failed to get VM info: %v", err)
-
- // ConnectRPC errors can be inspected for status codes
- if connectErr := new(connect.Error); errors.As(err, &connectErr) {
- switch connectErr.Code() {
- case connect.CodeNotFound:
- log.Println("VM not found")
- case connect.CodePermissionDenied:
- log.Println("Access denied - check customer ID")
- }
- }
-}
-```
-
-## Performance Considerations
-
-### Connection Reuse
-- HTTP/2 connection pooling is handled automatically
-- Single client instance can handle multiple concurrent requests
-- TLS handshakes are minimized through connection reuse
-
-### Certificate Caching
-```go
-config := client.Config{
- // ... other config
- EnableCertCaching: true,
- CertCacheTTL: 5 * time.Second,
-}
-```
-
-### Timeouts
-```go
-config := client.Config{
- // ... other config
- Timeout: 30 * time.Second, // HTTP client timeout
-}
-
-// Per-request timeouts
-ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
-defer cancel()
-resp, err := client.CreateVM(ctx, req)
-```
-
-## Command Line Interface
-
-The metald-cli tool provides a command-line interface for VM operations:
-
-### Basic VM Operations
-
-```bash
-# Create and boot a VM with default settings
-metald-cli create-and-boot
-
-# Create VM with specific template
-metald-cli -template=high-cpu create-and-boot
-
-# Create VM with custom resources
-metald-cli -template=standard -cpu=8 -memory=16384 create-and-boot
-
-# Create VM for Docker image
-metald-cli -docker-image=nginx:alpine create-and-boot
-```
-
-### Using Configuration Files
-
-```bash
-# Generate a configuration file
-metald-cli -template=development config-gen > my-vm.json
-
-# Edit the configuration file as needed...
-
-# Create VM from configuration file
-metald-cli -config=my-vm.json create-and-boot
-
-# Validate configuration file
-metald-cli config-validate my-vm.json
-```
-
-### VM Management
-
-```bash
-# List all VMs
-metald-cli list
-
-# Get detailed VM information
-metald-cli info vm-12345
-
-# Control VM state
-metald-cli pause vm-12345
-metald-cli resume vm-12345
-metald-cli reboot vm-12345
-metald-cli shutdown vm-12345
-metald-cli delete vm-12345
-```
-
-### Authentication and TLS
-
-```bash
-# Use SPIFFE authentication (default)
-metald-cli -user=my-user -tenant=my-tenant list
-
-# Use disabled TLS for development
-metald-cli -tls-mode=disabled -server=http://localhost:8080 list
-
-# Use file-based TLS
-metald-cli -tls-mode=file -tls-cert=client.crt -tls-key=client.key list
-```
-
-### Output Formats
-
-```bash
-# Human-readable output (default)
-metald-cli list
-
-# JSON output for scripting
-metald-cli list -json
-
-# Generate configuration with JSON output
-metald-cli -template=high-memory config-gen -json
-```
-
-### Environment Variables
-
-Set environment variables to avoid repeating common options:
-
-```bash
-export UNKEY_METALD_SERVER_ADDRESS="https://metald.prod:8080"
-export UNKEY_METALD_USER_ID="production-user"
-export UNKEY_METALD_TENANT_ID="production-tenant"
-export UNKEY_METALD_TLS_MODE="spiffe"
-
-# Now you can use the CLI without specifying these options
-metald-cli create-and-boot
-metald-cli list
-```
-
-### Configuration Examples
-
-#### High-Performance Web Server
-```bash
-# Generate config for high-performance web server
-metald-cli -template=high-cpu -cpu=16 -memory=32768 config-gen > web-server.json
-
-# Customize the configuration file...
-# Add additional storage, network interfaces, etc.
-
-# Create the VM
-metald-cli -config=web-server.json create-and-boot
-```
-
-#### Database Server
-```bash
-# High-memory configuration for database
-metald-cli -template=high-memory -memory=65536 config-gen > database.json
-
-# Create with specific VM ID
-metald-cli -config=database.json create-and-boot db-primary-01
-```
-
-#### Development Environment
-```bash
-# Development VM with Docker support
-metald-cli -docker-image=ubuntu:22.04 -template=development create-and-boot dev-env
-```
-
-## Testing
-
-The client includes comprehensive examples and can be tested against a local metald instance:
-
-```bash
-# Run examples (requires running metald)
-go test -v ./client -run Example
-
-# Integration tests
-go test -v ./client -tags=integration
-
-# Test CLI tool
-cd client/cmd/metald-cli
-go build
-./metald-cli -help
-```
-
-## Security Best Practices
-
-1. **Use SPIFFE in Production**: Always use SPIFFE mode in production environments
-2. **Validate Customer IDs**: Ensure customer IDs come from authenticated sources
-3. **Network Security**: Deploy metald behind appropriate network security controls
-4. **Certificate Management**: Let SPIFFE handle certificate lifecycle automatically
-5. **Audit Logging**: All operations are logged with customer context for audit trails
-
-## Troubleshooting
-
-### SPIFFE Connection Issues
-```bash
-# Check SPIFFE agent status
-systemctl status spire-agent
-
-# Test SPIFFE socket connectivity
-ls -la /var/lib/spire/agent/agent.sock
-
-# Check SPIFFE ID assignment
-/opt/spire/bin/spire-agent api fetch -socketPath /var/lib/spire/agent/agent.sock
-```
-
-### TLS Verification Errors
-- Ensure trust domain configuration matches between client and server
-- Verify SPIFFE agent has proper workload attestation
-- Check that certificates are not expired
-
-### Authentication Failures
-- Verify customer ID format and validity
-- Check that metald has proper authentication configuration
-- Ensure customer exists in the system
\ No newline at end of file
diff --git a/go/apps/metald/client/client.go b/go/apps/metald/client/client.go
deleted file mode 100644
index 712bd0307a..0000000000
--- a/go/apps/metald/client/client.go
+++ /dev/null
@@ -1,271 +0,0 @@
-package client
-
-import (
- "context"
- "fmt"
- "net/http"
- "time"
-
- "connectrpc.com/connect"
- "github.com/unkeyed/unkey/go/deploy/pkg/tls"
- metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1"
- "github.com/unkeyed/unkey/go/gen/proto/metald/v1/metaldv1connect"
-)
-
-// This client provides a high-level interface for metald VM operations with proper authentication
-
-// Config holds the configuration for the metald client
-type Config struct {
- // ServerAddress is the metald server endpoint (e.g., "https://metald:8080")
- ServerAddress string
-
- // UserID is the user identifier for authentication
- UserID string
-
- DeploymentID string
-
- // TLS configuration
- TLSMode string // "disabled", "file", or "spiffe"
- SPIFFESocketPath string // Path to SPIFFE agent socket
- TLSCertFile string // TLS certificate file (for file mode)
- TLSKeyFile string // TLS key file (for file mode)
- TLSCAFile string // TLS CA file (for file mode)
- EnableCertCaching bool // Enable certificate caching
- CertCacheTTL time.Duration // Certificate cache TTL
-
- // Optional HTTP client timeout
- Timeout time.Duration
-}
-
-// Client provides a high-level interface to metald services
-type Client struct {
- vmService metaldv1connect.VmServiceClient
- tlsProvider tls.Provider
- tenantID string
- projectID string
- environmentID string
- serverAddr string
-}
-
-// New creates a new metald client with SPIFFE/SPIRE integration
-func New(ctx context.Context, config Config) (*Client, error) {
- // Set defaults
- if config.SPIFFESocketPath == "" {
- config.SPIFFESocketPath = "/var/lib/spire/agent/agent.sock"
- }
- if config.TLSMode == "" {
- config.TLSMode = "spiffe"
- }
- if config.Timeout == 0 {
- config.Timeout = 30 * time.Second
- }
- if config.CertCacheTTL == 0 {
- config.CertCacheTTL = 5 * time.Second
- }
-
- // Create TLS provider
- tlsConfig := tls.Config{
- Mode: tls.Mode(config.TLSMode),
- CertFile: config.TLSCertFile,
- KeyFile: config.TLSKeyFile,
- CAFile: config.TLSCAFile,
- SPIFFESocketPath: config.SPIFFESocketPath,
- EnableCertCaching: config.EnableCertCaching,
- CertCacheTTL: config.CertCacheTTL,
- }
-
- tlsProvider, err := tls.NewProvider(ctx, tlsConfig)
- if err != nil {
- return nil, fmt.Errorf("failed to create TLS provider: %w", err)
- }
-
- // Get HTTP client with SPIFFE mTLS
- httpClient := tlsProvider.HTTPClient()
- httpClient.Timeout = config.Timeout
-
- // Add authentication and tenant isolation transport
- httpClient.Transport = &tenantTransport{
- Base: httpClient.Transport,
- }
-
- // Create ConnectRPC client
- vmService := metaldv1connect.NewVmServiceClient(
- httpClient,
- config.ServerAddress,
- )
-
- return &Client{
- vmService: vmService,
- tlsProvider: tlsProvider,
- serverAddr: config.ServerAddress,
- }, nil
-}
-
-// Close closes the client and cleans up resources
-func (c *Client) Close() error {
- if c.tlsProvider != nil {
- return c.tlsProvider.Close()
- }
- return nil
-}
-
-// CreateVM creates a new virtual machine with the specified configuration
-func (c *Client) CreateVM(ctx context.Context, req *metaldv1.CreateVmRequest) (*metaldv1.CreateVmResponse, error) {
- resp, err := c.vmService.CreateVm(ctx, connect.NewRequest(req))
- if err != nil {
- return nil, fmt.Errorf("failed to create VM: %w", err)
- }
- return resp.Msg, nil
-}
-
-// BootVM starts a created virtual machine
-func (c *Client) BootVM(ctx context.Context, req *metaldv1.BootVmRequest) (*metaldv1.BootVmResponse, error) {
-
- resp, err := c.vmService.BootVm(ctx, connect.NewRequest(req))
- if err != nil {
- return nil, fmt.Errorf("failed to boot VM: %w", err)
- }
- return resp.Msg, nil
-}
-
-// ShutdownVM gracefully stops a running virtual machine
-func (c *Client) ShutdownVM(ctx context.Context, req *metaldv1.ShutdownVmRequest) (*metaldv1.ShutdownVmResponse, error) {
- resp, err := c.vmService.ShutdownVm(ctx, connect.NewRequest(req))
- if err != nil {
- return nil, fmt.Errorf("failed to shutdown VM: %w", err)
- }
- return resp.Msg, nil
-}
-
-// DeleteVM removes a virtual machine
-func (c *Client) DeleteVM(ctx context.Context, req *metaldv1.DeleteVmRequest) (*metaldv1.DeleteVmResponse, error) {
- resp, err := c.vmService.DeleteVm(ctx, connect.NewRequest(req))
- if err != nil {
- return nil, fmt.Errorf("failed to delete VM: %w", err)
- }
- return resp.Msg, nil
-}
-
-// GetVMInfo retrieves detailed information about a virtual machine
-func (c *Client) GetVMInfo(ctx context.Context, req *metaldv1.GetVmInfoRequest) (*metaldv1.GetVmInfoResponse, error) {
- resp, err := c.vmService.GetVmInfo(ctx, connect.NewRequest(req))
- if err != nil {
- return nil, fmt.Errorf("failed to get VM info: %w", err)
- }
- return resp.Msg, nil
-}
-
-// ListVMs retrieves a list of virtual machines for the authenticated customer
-func (c *Client) ListVMs(ctx context.Context, req *metaldv1.ListVmsRequest) (*metaldv1.ListVmsResponse, error) {
- resp, err := c.vmService.ListVms(ctx, connect.NewRequest(req))
- if err != nil {
- return nil, fmt.Errorf("failed to list VMs: %w", err)
- }
- return resp.Msg, nil
-}
-
-// PauseVM pauses a running virtual machine
-func (c *Client) PauseVM(ctx context.Context, req *metaldv1.PauseVmRequest) (*metaldv1.PauseVmResponse, error) {
- resp, err := c.vmService.PauseVm(ctx, connect.NewRequest(req))
- if err != nil {
- return nil, fmt.Errorf("failed to pause VM: %w", err)
- }
- return resp.Msg, nil
-}
-
-// ResumeVM resumes a paused virtual machine
-func (c *Client) ResumeVM(ctx context.Context, req *metaldv1.ResumeVmRequest) (*metaldv1.ResumeVmResponse, error) {
- resp, err := c.vmService.ResumeVm(ctx, connect.NewRequest(req))
- if err != nil {
- return nil, fmt.Errorf("failed to resume VM: %w", err)
- }
- return resp.Msg, nil
-}
-
-// RebootVM restarts a virtual machine
-func (c *Client) RebootVM(ctx context.Context, req *metaldv1.RebootVmRequest) (*metaldv1.RebootVmResponse, error) {
- resp, err := c.vmService.RebootVm(ctx, connect.NewRequest(req))
- if err != nil {
- return nil, fmt.Errorf("failed to reboot VM: %w", err)
- }
- return resp.Msg, nil
-}
-
-// GetTenantID returns the tenant ID associated with this client
-func (c *Client) GetTenantID() string {
- return c.tenantID
-}
-
-// GetServerAddress returns the server address this client is connected to
-func (c *Client) GetServerAddress() string {
- return c.serverAddr
-}
-
-// CreateDeployment creates a new deployment with multiple VMs
-func (c *Client) CreateDeployment(ctx context.Context, req *metaldv1.CreateDeploymentRequest) (*metaldv1.CreateDeploymentResponse, error) {
- resp, err := c.vmService.CreateDeployment(ctx, connect.NewRequest(req))
- if err != nil {
- return nil, fmt.Errorf("failed to create deployment: %w", err)
- }
- return resp.Msg, nil
-}
-
-// UpdateDeployment updates an existing deployment
-func (c *Client) UpdateDeployment(ctx context.Context, req *metaldv1.UpdateDeploymentRequest) (*metaldv1.UpdateDeploymentResponse, error) {
- resp, err := c.vmService.UpdateDeployment(ctx, connect.NewRequest(req))
- if err != nil {
- return nil, fmt.Errorf("failed to update deployment: %w", err)
- }
- return resp.Msg, nil
-}
-
-// DeleteDeployment deletes an existing deployment
-func (c *Client) DeleteDeployment(ctx context.Context, req *metaldv1.DeleteDeploymentRequest) (*metaldv1.DeleteDeploymentResponse, error) {
- resp, err := c.vmService.DeleteDeployment(ctx, connect.NewRequest(req))
- if err != nil {
- return nil, fmt.Errorf("failed to delete deployment: %w", err)
- }
- return resp.Msg, nil
-}
-
-// GetDeployment retrieves information about a deployment
-func (c *Client) GetDeployment(ctx context.Context, req *metaldv1.GetDeploymentRequest) (*metaldv1.GetDeploymentResponse, error) {
- resp, err := c.vmService.GetDeployment(ctx, connect.NewRequest(req))
- if err != nil {
- return nil, fmt.Errorf("failed to get deployment: %w", err)
- }
- return resp.Msg, nil
-}
-
-// tenantTransport adds authentication and tenant isolation headers to all requests
-type tenantTransport struct {
- Base http.RoundTripper
- EnvironmentID string
- ProjectID string
- TenantID string
-}
-
-func (t *tenantTransport) RoundTrip(req *http.Request) (*http.Response, error) {
- // Clone the request to avoid modifying the original
- req2 := req.Clone(req.Context())
- if req2.Header == nil {
- req2.Header = make(http.Header)
- }
-
- // Set Authorization header with development token format
- // AIDEV-BUSINESS_RULE: In development, use "dev_user_" format
- // TODO: Update to proper JWT tokens in production
- req2.Header.Set("Authorization", fmt.Sprintf("Bearer dev_user_%s", t.TenantID))
-
- // Also set X-Tenant-ID header for tenant identification
- req2.Header.Set("X-Tenant-ID", t.TenantID)
- req2.Header.Set("X-Project-ID", t.ProjectID)
- req2.Header.Set("X-Environment-ID", t.EnvironmentID)
-
- // Use the base transport, or default if nil
- base := t.Base
- if base == nil {
- base = http.DefaultTransport
- }
- return base.RoundTrip(req2)
-}
diff --git a/go/apps/metald/client/cmd/metald-cli/main.go b/go/apps/metald/client/cmd/metald-cli/main.go
deleted file mode 100644
index f20bdb49df..0000000000
--- a/go/apps/metald/client/cmd/metald-cli/main.go
+++ /dev/null
@@ -1,607 +0,0 @@
-package main
-
-import (
- "context"
- "encoding/json"
- "flag"
- "fmt"
- "log"
- "os"
- "time"
-
- "github.com/unkeyed/unkey/go/apps/metald/client"
- metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1"
-)
-
-var (
- serverAddr = flag.String("server", getEnvOrDefault("UNKEY_METALD_SERVER_ADDRESS", "https://localhost:8080"), "metald server address")
- userID = flag.String("user", getEnvOrDefault("UNKEY_METALD_USER_ID", "cli-user"), "user ID for authentication")
- tlsMode = flag.String("tls-mode", getEnvOrDefault("UNKEY_METALD_TLS_MODE", "spiffe"), "TLS mode: disabled, file, or spiffe")
- spiffeSocket = flag.String("spiffe-socket", getEnvOrDefault("UNKEY_METALD_SPIFFE_SOCKET", "/var/lib/spire/agent/agent.sock"), "SPIFFE agent socket path")
- tlsCert = flag.String("tls-cert", "", "TLS certificate file (for file mode)")
- tlsKey = flag.String("tls-key", "", "TLS key file (for file mode)")
- tlsCA = flag.String("tls-ca", "", "TLS CA file (for file mode)")
- timeout = flag.Duration("timeout", 30*time.Second, "request timeout")
- jsonOutput = flag.Bool("json", false, "output results as JSON")
-
- // VM configuration options
- configFile = flag.String("config", "", "path to VM configuration file (JSON)")
- template = flag.String("template", "standard", "VM template: minimal, standard, high-cpu, high-memory, development")
- cpuCount = flag.Uint("cpu", 0, "number of vCPUs (overrides template)")
- memoryMB = flag.Uint64("memory", 0, "memory in MB (overrides template)")
- dockerImage = flag.String("docker-image", "", "Docker image to run in VM")
- forceBuild = flag.Bool("force-build", false, "force rebuild assets even if cached versions exist")
-
- // Deployment configuration options
- deploymentID = flag.String("deployment-id", "", "deployment ID for deployment operations")
- image = flag.String("image", "", "container image for deployment")
- vmCount = flag.Uint("vm-count", 1, "number of VMs in deployment")
-)
-
-func main() {
- flag.Parse()
-
- if flag.NArg() == 0 {
- printUsage()
- os.Exit(1)
- }
-
- ctx := context.Background()
-
- // Create metald client
- config := client.Config{
- ServerAddress: *serverAddr,
- UserID: *userID,
- TLSMode: *tlsMode,
- SPIFFESocketPath: *spiffeSocket,
- TLSCertFile: *tlsCert,
- TLSKeyFile: *tlsKey,
- TLSCAFile: *tlsCA,
- Timeout: *timeout,
- }
-
- metaldClient, err := client.New(ctx, config)
- if err != nil {
- log.Fatalf("Failed to create metald client: %v", err)
- }
- defer metaldClient.Close()
-
- // VM configuration options for create commands
- vmConfigOptions := VMConfigOptions{
- ConfigFile: *configFile,
- Template: *template,
- CPUCount: uint32(*cpuCount),
- MemoryMB: *memoryMB,
- DockerImage: *dockerImage,
- ForceBuild: *forceBuild,
- }
-
- // Execute command
- command := flag.Arg(0)
- switch command {
- case "create":
- handleCreate(ctx, metaldClient, vmConfigOptions, *jsonOutput)
- case "boot":
- handleBoot(ctx, metaldClient, *jsonOutput)
- case "shutdown":
- handleShutdown(ctx, metaldClient, *jsonOutput)
- case "delete":
- handleDelete(ctx, metaldClient, *jsonOutput)
- case "info":
- handleInfo(ctx, metaldClient, *jsonOutput)
- case "list":
- handleList(ctx, metaldClient, *jsonOutput)
- case "pause":
- handlePause(ctx, metaldClient, *jsonOutput)
- case "resume":
- handleResume(ctx, metaldClient, *jsonOutput)
- case "reboot":
- handleReboot(ctx, metaldClient, *jsonOutput)
- case "create-and-boot":
- handleCreateAndBoot(ctx, metaldClient, vmConfigOptions, *jsonOutput)
- case "create-deployment":
- handleCreateDeployment(ctx, metaldClient, *deploymentID, *image, uint32(*vmCount), uint32(*cpuCount), *memoryMB, *jsonOutput)
- case "update-deployment":
- handleUpdateDeployment(ctx, metaldClient, *deploymentID, *image, uint32(*vmCount), uint32(*cpuCount), *memoryMB, *jsonOutput)
- case "delete-deployment":
- handleDeleteDeployment(ctx, metaldClient, *deploymentID, *jsonOutput)
- case "get-deployment":
- handleGetDeployment(ctx, metaldClient, *deploymentID, *jsonOutput)
- default:
- fmt.Fprintf(os.Stderr, "Unknown command: %s\n", command)
- printUsage()
- os.Exit(1)
- }
-}
-
-func printUsage() {
- fmt.Printf(`%s - CLI tool for metald VM operations
-
-Usage: %s [flags] [args...]
-
-Commands:
- create [vm-id] Create a new VM (VM ID optional)
- boot Boot a created VM
- shutdown [force] Shutdown a running VM
- delete [force] Delete a VM
- info Get detailed VM information
- list List all VMs for customer
- pause Pause a running VM
- resume Resume a paused VM
- reboot [force] Reboot a running VM
- create-and-boot [vm-id] Create and immediately boot a VM
- config-gen Generate a VM configuration file
- config-validate Validate a VM configuration file
-
- Deployment Commands:
- create-deployment Create a new deployment
- update-deployment Update an existing deployment
- delete-deployment Delete a deployment
- get-deployment Get deployment information
-`, os.Args[0], os.Args[0])
-}
-
-// VMConfigOptions holds options for VM configuration
-type VMConfigOptions struct {
- ConfigFile string
- Template string
- CPUCount uint32
- MemoryMB uint64
- DockerImage string
- ForceBuild bool
-}
-
-func handleCreate(ctx context.Context, metaldClient *client.Client, options VMConfigOptions, jsonOutput bool) {
- vmID := ""
- if flag.NArg() > 1 {
- vmID = flag.Arg(1)
- }
-
- // Create VM configuration from options
-
- req := &metaldv1.CreateVmRequest{
- VmId: vmID,
- Config: &metaldv1.VmConfig{},
- }
-
- // DO STUFF
- _, err := metaldClient.CreateVM(ctx, req)
- if err != nil {
- log.Fatalf("Failed to create VM: %v", err)
- }
-}
-
-func handleBoot(ctx context.Context, metaldClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("VM ID is required for boot command")
- }
- vmID := flag.Arg(1)
-
- req := &metaldv1.BootVmRequest{
- VmId: vmID,
- }
- resp, err := metaldClient.BootVM(ctx, req)
- if err != nil {
- log.Fatalf("Failed to boot VM: %v", err)
- }
-
- if jsonOutput {
- outputJSON(map[string]interface{}{
- "vm_id": vmID,
- "state": resp.State.String(),
- })
- } else {
- fmt.Printf("VM boot operation:\n")
- fmt.Printf(" VM ID: %s\n", vmID)
- fmt.Printf(" State: %s\n", resp.State.String())
- }
-}
-
-func handleShutdown(ctx context.Context, metaldClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("VM ID is required for shutdown command")
- }
- vmID := flag.Arg(1)
-
- force := false
- if flag.NArg() > 2 && flag.Arg(2) == "force" {
- force = true
- }
-
- req := &metaldv1.ShutdownVmRequest{
- VmId: vmID,
- Force: force,
- TimeoutSeconds: 30,
- }
-
- resp, err := metaldClient.ShutdownVM(ctx, req)
- if err != nil {
- log.Fatalf("Failed to shutdown VM: %v", err)
- }
-
- if jsonOutput {
- outputJSON(map[string]interface{}{
- "vm_id": vmID,
- "state": resp.State.String(),
- "force": force,
- })
- } else {
- fmt.Printf("VM shutdown operation:\n")
- fmt.Printf(" VM ID: %s\n", vmID)
- fmt.Printf(" State: %s\n", resp.State.String())
- fmt.Printf(" Force: %v\n", force)
- }
-}
-
-func handleDelete(ctx context.Context, metaldClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("VM ID is required for delete command")
- }
- vmID := flag.Arg(1)
-
- force := false
- if flag.NArg() > 2 && flag.Arg(2) == "force" {
- force = true
- }
-
- req := &metaldv1.DeleteVmRequest{
- VmId: vmID,
- Force: force,
- }
-
- resp, err := metaldClient.DeleteVM(ctx, req)
- if err != nil {
- log.Fatalf("Failed to delete VM: %v", err)
- }
-
- if jsonOutput {
- outputJSON(map[string]interface{}{
- "vm_id": vmID,
- "success": resp.Success,
- "force": force,
- })
- } else {
- fmt.Printf("VM delete operation:\n")
- fmt.Printf(" VM ID: %s\n", vmID)
- fmt.Printf(" Success: %v\n", resp.Success)
- fmt.Printf(" Force: %v\n", force)
- }
-}
-
-func handleInfo(ctx context.Context, metaldClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("VM ID is required for info command")
- }
- vmID := flag.Arg(1)
-
- req := &metaldv1.GetVmInfoRequest{
- VmId: vmID,
- }
- vmInfo, err := metaldClient.GetVMInfo(ctx, req)
- if err != nil {
- log.Fatalf("Failed to get VM info: %v", err)
- }
-
- if jsonOutput {
- outputJSON(vmInfo)
- } else {
- fmt.Printf("VM Information:\n")
- fmt.Printf(" VM ID: %s\n", vmInfo.GetVmId())
- fmt.Printf(" State: %s\n", vmInfo.GetState().String())
-
- if vmInfo.Config != nil {
- fmt.Printf(" Configuration:\n")
- fmt.Printf(" CPUs: %d\n", vmInfo.Config.GetVcpuCount())
- fmt.Printf(" Memory: %d MiB\n", vmInfo.Config.GetMemorySizeMib())
- fmt.Printf(" IP: \n") // DO STUFF
- }
-
- if vmInfo.Metrics != nil {
- fmt.Printf(" Metrics:\n")
- fmt.Printf(" CPU usage: %.2f%%\n", vmInfo.Metrics.CpuUsagePercent)
- fmt.Printf(" Memory: %d MiB\n", vmInfo.Config.MemorySizeMib)
- fmt.Printf(" Uptime: %d seconds\n", vmInfo.Metrics.UptimeSeconds)
- }
- }
-}
-
-func handleList(ctx context.Context, metaldClient *client.Client, jsonOutput bool) {
- req := &metaldv1.ListVmsRequest{
- PageSize: 50,
- }
-
- resp, err := metaldClient.ListVMs(ctx, req)
- if err != nil {
- log.Fatalf("Failed to list VMs: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- for _, vm := range resp.Vms {
- fmt.Printf(" - %s: %s (CPUs: %d, Memory: %d MB)\n",
- vm.GetVmId(),
- vm.GetState().String(),
- vm.GetVcpuCount(),
- vm.GetMemorySizeMib(),
- )
- }
- }
-}
-
-func handlePause(ctx context.Context, metaldClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("VM ID is required for pause command")
- }
- vmID := flag.Arg(1)
-
- req := &metaldv1.PauseVmRequest{
- VmId: vmID,
- }
- resp, err := metaldClient.PauseVM(ctx, req)
- if err != nil {
- log.Fatalf("Failed to pause VM: %v", err)
- }
-
- if jsonOutput {
- outputJSON(map[string]interface{}{
- "vm_id": vmID,
- "state": resp.State.String(),
- })
- } else {
- fmt.Printf("VM pause operation:\n")
- fmt.Printf(" VM ID: %s\n", vmID)
- fmt.Printf(" State: %s\n", resp.State.String())
- }
-}
-
-func handleResume(ctx context.Context, metaldClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("VM ID is required for resume command")
- }
- vmID := flag.Arg(1)
-
- req := &metaldv1.ResumeVmRequest{
- VmId: vmID,
- }
- resp, err := metaldClient.ResumeVM(ctx, req)
- if err != nil {
- log.Fatalf("Failed to resume VM: %v", err)
- }
-
- if jsonOutput {
- outputJSON(map[string]any{
- "vm_id": vmID,
- "state": resp.State.String(),
- })
- } else {
- fmt.Printf("VM resume operation:\n")
- fmt.Printf(" VM ID: %s\n", vmID)
- fmt.Printf(" State: %s\n", resp.State.String())
- }
-}
-
-func handleReboot(ctx context.Context, metaldClient *client.Client, jsonOutput bool) {
- if flag.NArg() < 2 {
- log.Fatal("VM ID is required for reboot command")
- }
- vmID := flag.Arg(1)
-
- force := false
- if flag.NArg() > 2 && flag.Arg(2) == "force" {
- force = true
- }
-
- req := &metaldv1.RebootVmRequest{
- VmId: vmID,
- Force: force,
- }
-
- resp, err := metaldClient.RebootVM(ctx, req)
- if err != nil {
- log.Fatalf("Failed to reboot VM: %v", err)
- }
-
- if jsonOutput {
- outputJSON(map[string]any{
- "vm_id": vmID,
- "state": resp.State.String(),
- "force": force,
- })
- } else {
- fmt.Printf("VM reboot operation:\n")
- fmt.Printf(" VM ID: %s\n", vmID)
- fmt.Printf(" State: %s\n", resp.State.String())
- fmt.Printf(" Force: %v\n", force)
- }
-}
-
-func handleCreateAndBoot(ctx context.Context, metaldClient *client.Client, options VMConfigOptions, jsonOutput bool) {
- vmID := ""
- if flag.NArg() > 1 {
- vmID = flag.Arg(1)
- }
-
- createReq := &metaldv1.CreateVmRequest{
- VmId: vmID,
- Config: &metaldv1.VmConfig{},
- }
- log.Printf("createReq: %+v/n", createReq)
- createResp, err := metaldClient.CreateVM(ctx, createReq)
- if err != nil {
- log.Fatalf("Failed to create VM: %v", err)
- }
-
- // Wait a moment for VM to be fully created
- time.Sleep(2 * time.Second)
-
- // Boot VM
- bootReq := &metaldv1.BootVmRequest{
- VmId: createReq.GetVmId(),
- }
- bootResp, err := metaldClient.BootVM(ctx, bootReq)
- if err != nil {
- log.Fatalf("Failed to boot VM: %v", err)
- }
-
- if jsonOutput {
- result := map[string]any{
- "vm_id": createReq.GetVmId(),
- "create_state": createResp.State.String(),
- "boot_state": bootResp.State.String(),
- }
- outputJSON(result)
- } else {
- fmt.Printf("VM created and booted successfully:\n")
- fmt.Printf(" VM ID: %s\n", createReq.GetVmId())
- fmt.Printf(" Create State: %s\n", createResp.State.String())
- fmt.Printf(" Boot State: %s\n", bootResp.State.String())
- }
-}
-
-func handleCreateDeployment(ctx context.Context, metaldClient *client.Client, deploymentID, image string, vmCount, cpu uint32, memorySizeMB uint64, jsonOutput bool) {
- if deploymentID == "" {
- log.Fatal("Deployment ID is required for create-deployment command")
- }
- if image == "" {
- log.Fatal("Image is required for create-deployment command")
- }
-
- req := &metaldv1.CreateDeploymentRequest{
- Deployment: &metaldv1.DeploymentRequest{
- DeploymentId: deploymentID,
- Image: image,
- VmCount: vmCount,
- Cpu: cpu,
- MemorySizeMib: memorySizeMB,
- },
- }
-
- resp, err := metaldClient.CreateDeployment(ctx, req)
- if err != nil {
- log.Fatalf("Failed to create deployment: %v", err)
- }
-
- if jsonOutput {
- outputJSON(map[string]interface{}{
- "deployment_id": deploymentID,
- "vm_ids": resp.VmIds,
- "vm_count": len(resp.VmIds),
- })
- } else {
- fmt.Printf("Deployment created successfully:\n")
- fmt.Printf(" Deployment ID: %s\n", deploymentID)
- fmt.Printf(" VM Count: %d\n", len(resp.VmIds))
- fmt.Printf(" VM IDs:\n")
- for _, vmID := range resp.VmIds {
- fmt.Printf(" - %s\n", vmID)
- }
- }
-}
-
-func handleUpdateDeployment(ctx context.Context, metaldClient *client.Client, deploymentID, image string, vmCount, cpu uint32, memorySizeMB uint64, jsonOutput bool) {
- if deploymentID == "" {
- log.Fatal("Deployment ID is required for update-deployment command")
- }
-
- req := &metaldv1.UpdateDeploymentRequest{
- Deployment: &metaldv1.DeploymentRequest{
- DeploymentId: deploymentID,
- Image: image,
- VmCount: vmCount,
- Cpu: cpu,
- MemorySizeMib: memorySizeMB,
- },
- }
-
- resp, err := metaldClient.UpdateDeployment(ctx, req)
- if err != nil {
- log.Fatalf("Failed to update deployment: %v", err)
- }
-
- if jsonOutput {
- outputJSON(map[string]interface{}{
- "deployment_id": deploymentID,
- "vm_ids": resp.VmIds,
- "vm_count": len(resp.VmIds),
- })
- } else {
- fmt.Printf("Deployment updated successfully:\n")
- fmt.Printf(" Deployment ID: %s\n", deploymentID)
- fmt.Printf(" VM Count: %d\n", len(resp.VmIds))
- fmt.Printf(" VM IDs:\n")
- for _, vmID := range resp.VmIds {
- fmt.Printf(" - %s\n", vmID)
- }
- }
-}
-
-func handleDeleteDeployment(ctx context.Context, metaldClient *client.Client, deploymentID string, jsonOutput bool) {
- if deploymentID == "" {
- log.Fatal("Deployment ID is required for delete-deployment command")
- }
-
- req := &metaldv1.DeleteDeploymentRequest{
- DeploymentId: deploymentID,
- }
-
- _, err := metaldClient.DeleteDeployment(ctx, req)
- if err != nil {
- log.Fatalf("Failed to delete deployment: %v", err)
- }
-
- if jsonOutput {
- outputJSON(map[string]interface{}{
- "deployment_id": deploymentID,
- "deleted": true,
- })
- } else {
- fmt.Printf("Deployment deleted successfully:\n")
- fmt.Printf(" Deployment ID: %s\n", deploymentID)
- }
-}
-
-func handleGetDeployment(ctx context.Context, metaldClient *client.Client, deploymentID string, jsonOutput bool) {
- if deploymentID == "" {
- log.Fatal("Deployment ID is required for get-deployment command")
- }
-
- req := &metaldv1.GetDeploymentRequest{
- DeploymentId: deploymentID,
- }
-
- resp, err := metaldClient.GetDeployment(ctx, req)
- if err != nil {
- log.Fatalf("Failed to get deployment: %v", err)
- }
-
- if jsonOutput {
- outputJSON(resp)
- } else {
- fmt.Printf("Deployment Information:\n")
- fmt.Printf(" Deployment ID: %s\n", resp.DeploymentId)
- fmt.Printf(" VM Count: %d\n", len(resp.Vms))
- fmt.Printf(" VMs:\n")
- for _, vm := range resp.Vms {
- fmt.Printf(" - ID: %s\n", vm.Id)
- fmt.Printf(" Host: %s\n", vm.Host)
- fmt.Printf(" State: %s\n", vm.State.String())
- fmt.Printf(" Port: %d\n", vm.Port)
- }
- }
-}
-
-func outputJSON(data interface{}) {
- encoder := json.NewEncoder(os.Stdout)
- encoder.SetIndent("", " ")
- if err := encoder.Encode(data); err != nil {
- log.Fatalf("Failed to encode JSON: %v", err)
- }
-}
-
-func getEnvOrDefault(key, defaultValue string) string {
- if value := os.Getenv(key); value != "" {
- return value
- }
- return defaultValue
-}
diff --git a/go/apps/metald/client/vmconfig.go b/go/apps/metald/client/vmconfig.go
deleted file mode 100644
index 158511e377..0000000000
--- a/go/apps/metald/client/vmconfig.go
+++ /dev/null
@@ -1,166 +0,0 @@
-package client
-
-import (
- "fmt"
-
- metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1"
-)
-
-// AIDEV-NOTE: VM configuration builder for customizable VM creation
-// This provides a fluent interface for building VM configurations with sensible defaults
-
-// VMConfigBuilder provides a fluent interface for building VM configurations
-type VMConfigBuilder struct {
- config *metaldv1.VmConfig
-}
-
-// GetConfig returns the current configuration (for accessing intermediate state)
-func (b *VMConfigBuilder) GetConfig() *metaldv1.VmConfig {
- return b.config
-}
-
-// NewVMConfigBuilder creates a new VM configuration builder with defaults
-func NewVMConfigBuilder() *VMConfigBuilder {
- return &VMConfigBuilder{
- config: &metaldv1.VmConfig{
- VcpuCount: 1,
- MemorySizeMib: 512,
- Boot: "",
- Storage: &metaldv1.StorageDevice{},
- NetworkConfig: "",
- Console: &metaldv1.ConsoleConfig{
- Enabled: true,
- Output: "/tmp/vm-console.log",
- ConsoleType: "serial",
- },
- Metadata: make(map[string]string),
- },
- }
-}
-
-// WithCPU configures CPU settings
-func (b *VMConfigBuilder) WithCPU(vcpuCount uint32) *VMConfigBuilder {
- b.config.VcpuCount = vcpuCount
- return b
-}
-
-// WithMemory configures memory settings
-func (b *VMConfigBuilder) WithMemory(sizeBytes uint64) *VMConfigBuilder {
- b.config.MemorySizeMib = sizeBytes
- return b
-}
-
-// WithBoot configures boot settings
-func (b *VMConfigBuilder) WithBoot(kernelArgs string) *VMConfigBuilder {
- b.config.Boot = kernelArgs
- return b
-}
-
-// WithDefaultBoot configures standard boot settings with kernel args
-func (b *VMConfigBuilder) WithDefaultBoot(kernelArgs string) *VMConfigBuilder {
- if kernelArgs == "" {
- kernelArgs = "console=ttyS0 reboot=k panic=1 pci=off"
- }
- return b.WithBoot(kernelArgs)
-}
-
-// WithConsole configures console settings
-func (b *VMConfigBuilder) WithConsole(enabled bool, output, consoleType string) *VMConfigBuilder {
- b.config.Console = &metaldv1.ConsoleConfig{
- Enabled: enabled,
- Output: output,
- ConsoleType: consoleType,
- }
- return b
-}
-
-// WithDefaultConsole configures standard console settings
-func (b *VMConfigBuilder) WithDefaultConsole(output string) *VMConfigBuilder {
- if output == "" {
- output = "/tmp/vm-console.log"
- }
- return b.WithConsole(true, output, "serial")
-}
-
-// DisableConsole disables console output
-func (b *VMConfigBuilder) DisableConsole() *VMConfigBuilder {
- return b.WithConsole(false, "", "")
-}
-
-// AddMetadata adds metadata key-value pairs
-func (b *VMConfigBuilder) AddMetadata(key, value string) *VMConfigBuilder {
- if b.config.Metadata == nil {
- b.config.Metadata = make(map[string]string)
- }
- b.config.Metadata[key] = value
- return b
-}
-
-// WithMetadata sets all metadata at once
-func (b *VMConfigBuilder) WithMetadata(metadata map[string]string) *VMConfigBuilder {
- b.config.Metadata = metadata
- return b
-}
-
-// Build returns the configured VM configuration
-func (b *VMConfigBuilder) Build() *metaldv1.VmConfig {
- return b.config
-}
-
-// VMTemplate represents common VM configuration templates
-type VMTemplate string
-
-const (
- // TemplateMinimal creates a minimal VM with basic resources
- TemplateMinimal VMTemplate = "minimal"
- // TemplateStandard creates a standard VM with balanced resources
- TemplateStandard VMTemplate = "standard"
- // TemplateHighCPU creates a VM optimized for CPU-intensive workloads
- TemplateHighCPU VMTemplate = "high-cpu"
- // TemplateHighMemory creates a VM optimized for memory-intensive workloads
- TemplateHighMemory VMTemplate = "high-memory"
- // TemplateDevelopment creates a VM suitable for development work
- TemplateDevelopment VMTemplate = "development"
-)
-
-// sanitizeImageName converts a Docker image name to a safe filename
-func sanitizeImageName(imageName string) string {
- // Replace special characters with underscores
- safe := imageName
- replacements := map[string]string{
- "/": "_",
- ":": "_",
- "@": "_",
- "+": "_",
- " ": "_",
- }
-
- for old, new := range replacements {
- safe = fmt.Sprintf("%s", safe)
- // Simple replacement without complex string manipulation
- result := ""
- for _, char := range safe {
- if string(char) == old {
- result += new
- } else {
- result += string(char)
- }
- }
- safe = result
- }
- return safe
-}
-
-// ForceBuild configures the VM to force rebuild assets even if cached versions exist
-func (b *VMConfigBuilder) ForceBuild(force bool) *VMConfigBuilder {
- // Add force build metadata that will be picked up by the asset management system
- if force {
- b.AddMetadata("force_rebuild", "true")
- } else {
- // Remove force rebuild metadata if it exists
- if b.config.Metadata != nil {
- delete(b.config.Metadata, "force_rebuild")
- }
- }
- return b
-}
diff --git a/go/apps/metald/cmd/metald-init/INIT_PROCESS_GUIDE.md b/go/apps/metald/cmd/metald-init/INIT_PROCESS_GUIDE.md
deleted file mode 100644
index d7d65b427a..0000000000
--- a/go/apps/metald/cmd/metald-init/INIT_PROCESS_GUIDE.md
+++ /dev/null
@@ -1,469 +0,0 @@
-# Understanding Init Processes: A Complete Guide
-
-## Table of Contents
-1. [What is an Init Process?](#what-is-an-init-process)
-2. [Why Init Processes Exist](#why-init-processes-exist)
-3. [Core Responsibilities of PID 1](#core-responsibilities-of-pid-1)
-4. [History of Init Systems](#history-of-init-systems)
-5. [Modern Init Systems](#modern-init-systems)
-6. [Why systemd Doesn't Fit MicroVMs](#why-systemd-doesnt-fit-microvms)
-7. [Our MicroVM Init Design](#our-microvm-init-design)
-8. [Technical Deep Dive](#technical-deep-dive)
-
-## What is an Init Process?
-
-The **init process** is the first userspace process started by the Linux kernel during boot. It always has **Process ID (PID) 1** and serves as the ancestor of all other processes in the system.
-
-```
-Kernel Boot Sequence:
-1. Hardware initialization
-2. Kernel loads and initializes
-3. Kernel starts init process (PID 1)
-4. Init process starts all other system processes
-```
-
-Think of init as the "root of the process tree" - every other process in the system is either started directly by init or is a descendant of a process started by init.
-
-## Why Init Processes Exist
-
-Init processes solve several fundamental operating system problems:
-
-### 1. **Process Lifecycle Management**
-- **Problem**: The kernel needs a way to start userspace processes
-- **Solution**: Init serves as the bridge between kernel and userspace
-
-### 2. **Orphan Process Adoption**
-- **Problem**: When a parent process dies, its children become "orphans"
-- **Solution**: Init automatically becomes the parent of orphaned processes
-
-### 3. **Zombie Process Reaping**
-- **Problem**: Dead processes remain as "zombies" until their parent reads their exit status
-- **Solution**: Init reaps zombie processes to free system resources
-
-### 4. **System Shutdown Coordination**
-- **Problem**: Processes need to be terminated gracefully during shutdown
-- **Solution**: Init handles system-wide shutdown signals
-
-### 5. **Signal Handling**
-- **Problem**: Some signals need system-wide coordination
-- **Solution**: Init provides a central point for signal management
-
-## Core Responsibilities of PID 1
-
-Any process running as PID 1 **must** handle these responsibilities:
-
-### 1. **Signal Handling**
-```c
-// Signals that PID 1 must handle specially:
-SIGTERM // Graceful shutdown request
-SIGINT // Interrupt (Ctrl+C)
-SIGCHLD // Child process died (triggers zombie reaping)
-```
-
-**Critical**: PID 1 cannot ignore signals like other processes. Unhandled signals can cause kernel panics.
-
-### 2. **Zombie Process Reaping**
-```c
-// When any process dies, init must reap it:
-while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
- // Process 'pid' has been reaped
-}
-```
-
-**Why this matters**: Unreapped zombies consume process table entries, eventually causing "fork: Resource temporarily unavailable" errors.
-
-### 3. **Process Group Management**
-- Init must properly manage process groups for signal propagation
-- Child processes should be in their own process groups when appropriate
-
-### 4. **Exit Code Propagation**
-- In containers/VMs, init's exit code often determines the container/VM exit status
-- Must properly extract and propagate child process exit codes
-
-## History of Init Systems
-
-### 1. **System V Init (1983-present)**
-The original Unix init system, still widely used:
-
-```bash
-# Simple process-based, runlevel-driven
-# /etc/inittab defines what processes to start
-# Sequential startup (slow)
-# Shell script based service management
-```
-
-**Pros**: Simple, well-understood, reliable
-**Cons**: Slow sequential startup, limited dependency management
-
-### 2. **BSD Init (1977-present)**
-Simpler than SysV, used in BSD systems:
-
-```bash
-# Single script /etc/rc
-# Very minimal, delegates to shell scripts
-# No runlevels, just single-user vs multi-user
-```
-
-**Pros**: Extremely simple
-**Cons**: No service management, basic functionality
-
-### 3. **Upstart (2006-2015)**
-Ubuntu's attempt to modernize init:
-
-```bash
-# Event-driven init system
-# Parallel startup
-# Better dependency handling
-# Eventually replaced by systemd
-```
-
-**Pros**: Parallel startup, event-driven
-**Cons**: Complex configuration, limited adoption
-
-### 4. **systemd (2010-present)**
-Modern Linux init system:
-
-```bash
-# Unit-based service management
-# Parallel startup with dependency resolution
-# Integrated logging, networking, and more
-# Binary logging (journald)
-```
-
-**Pros**: Fast boot, integrated system management, comprehensive features
-**Cons**: Complex, large, controversial, overkill for simple scenarios
-
-### 5. **OpenRC (2007-present)**
-Dependency-based init for Gentoo and Alpine:
-
-```bash
-# Dependency-based startup
-# Shell script based
-# Lighter than systemd
-# Good for embedded systems
-```
-
-**Pros**: Lighter than systemd, good dependency management
-**Cons**: Still complex for minimal environments
-
-### 6. **runit (2001-present)**
-Minimalist init system:
-
-```bash
-# Process supervision focused
-# Simple service directories
-# Reliable process monitoring
-# Used in some containers
-```
-
-**Pros**: Very simple, reliable supervision
-**Cons**: Limited service management features
-
-## Modern Init Systems
-
-### Feature Comparison
-
-| Feature | SysV | systemd | OpenRC | runit | Our Init |
-|---------|------|---------|--------|-------|----------|
-| Binary Size | ~100KB | ~1.2MB | ~200KB | ~50KB | ~2.4MB |
-| Startup Speed | Slow | Fast | Medium | Fast | N/A |
-| Dependencies | None | Many | Few | None | None |
-| Service Management | Basic | Advanced | Good | Basic | None |
-| Resource Usage | Low | High | Medium | Very Low | Very Low |
-| Complexity | Medium | Very High | Medium | Low | Very Low |
-
-## Why systemd Doesn't Fit MicroVMs
-
-While systemd is excellent for full Linux systems, it's poorly suited for microVMs:
-
-### 1. **Resource Overhead**
-```bash
-# systemd memory usage:
-systemd --version
-# Typically uses 10-50MB RAM just for init
-# Plus journald, networkd, resolved, etc.
-
-# Our init memory usage:
-ps aux | grep init
-# ~1-2MB total memory usage
-```
-
-### 2. **Complexity Overhead**
-```bash
-# systemd brings hundreds of components:
-- systemd (init)
-- journald (logging)
-- networkd (networking)
-- resolved (DNS)
-- logind (login management)
-- timedatectl (time management)
-- Many more...
-
-# Our init is a single binary with one job
-```
-
-### 3. **Startup Time**
-```bash
-# systemd initialization:
-# - Reads configuration files
-# - Initializes multiple subsystems
-# - Sets up D-Bus
-# - Starts default services
-# Total: 1-5 seconds even with no services
-
-# Our init initialization:
-# - Parse command line
-# - Set environment
-# - exec() target process
-# Total: <100ms
-```
-
-### 4. **Attack Surface**
-```bash
-# systemd attack surface:
-- Complex configuration parsing
-- D-Bus integration
-- Network configuration
-- Privilege escalation paths
-- Hundreds of thousands of lines of code
-
-# Our init attack surface:
-- Simple command line parsing
-- Minimal file operations
-- ~500 lines of auditable code
-```
-
-### 5. **Dependency Hell**
-```bash
-# systemd requires:
-systemctl list-dependencies
-# glibc, libsystemd, libcap, libselinux, etc.
-
-# Our init requires:
-ldd metald-init
-# "statically linked" - zero runtime dependencies
-```
-
-### 6. **Configuration Complexity**
-```bash
-# systemd service files:
-cat /etc/systemd/system/myapp.service
-# [Unit], [Service], [Install] sections
-# Dependency declarations
-# Complex service management
-
-# Our init configuration:
-# Kernel command line: env.KEY=value workdir=/app
-# Or JSON file with environment
-```
-
-### 7. **Overkill for Single Applications**
-MicroVMs typically run **one primary application**:
-- Web server
-- Database
-- Batch job
-- API service
-
-systemd is designed for **multi-service systems** with complex interdependencies.
-
-## Our MicroVM Init Design
-
-### Design Philosophy
-1. **Single Purpose**: Run one application reliably as PID 1
-2. **Minimal**: Only essential PID 1 responsibilities
-3. **Secure**: Input validation and minimal attack surface
-4. **Generic**: Works with any application
-5. **Debuggable**: Clear logging and debug information
-
-### Architecture
-
-```
-┌─────────────────────────────────────────┐
-│ MicroVM │
-├─────────────────────────────────────────┤
-│ Kernel │
-│ │ │
-│ └── PID 1: metald-init │
-│ │ │
-│ ├── Signal Handler │
-│ │ ├── SIGTERM/SIGINT │
-│ │ └── SIGCHLD (reaping) │
-│ │ │
-│ ├── Environment Setup │
-│ │ ├── Parse /proc/cmdline │
-│ │ └── Load metadata file │
-│ │ │
-│ └── PID 2: Your Application │
-│ ├── nginx │
-│ ├── postgres │
-│ └── or any process │
-└─────────────────────────────────────────┘
-```
-
-### Key Features
-
-#### 1. **Kernel Parameter Integration**
-```bash
-# Boot VM with environment:
-linux vmlinux env.PORT=8080 env.DATABASE_URL=postgres://... workdir=/app -- nginx
-```
-
-#### 2. **Metadata File Support**
-```json
-{
- "env": {
- "PORT": "8080",
- "DEBUG": "true"
- },
- "working_dir": "/app"
-}
-```
-
-#### 3. **Secure Input Validation**
-```go
-// Environment variable validation
-if len(key) > maxEnvKeyLen || !validEnvKeyPattern.MatchString(key) {
- return fmt.Errorf("invalid environment variable")
-}
-
-// Path traversal protection
-if !filepath.IsAbs(path) || filepath.Clean(path) != path {
- return fmt.Errorf("invalid path")
-}
-```
-
-#### 4. **Proper Signal Handling**
-```go
-// Forward signals to application process group
-syscall.Kill(-cmd.Process.Pid, signal)
-
-// Reap zombie processes
-for {
- pid, err := syscall.Wait4(-1, &status, syscall.WNOHANG, nil)
- if pid <= 0 { break }
- log.Printf("reaped zombie: PID %d", pid)
-}
-```
-
-## Technical Deep Dive
-
-### Why PID 1 is Special
-
-The Linux kernel treats PID 1 differently:
-
-1. **Signal Immunity**: PID 1 ignores signals unless it has a handler
-2. **Orphan Adoption**: All orphaned processes become children of PID 1
-3. **System Shutdown**: Kernel sends SIGTERM to PID 1 during shutdown
-4. **Cannot Exit**: If PID 1 exits, the kernel panics
-
-### Signal Handling Details
-
-```go
-// This is WRONG for PID 1:
-signal.Ignore(syscall.SIGTERM) // Can cause kernel panic!
-
-// This is CORRECT for PID 1:
-signal.Notify(sigChan, syscall.SIGTERM)
-go func() {
- sig := <-sigChan
- // Handle graceful shutdown
-}()
-```
-
-### Zombie Reaping Implementation
-
-```go
-// Set up SIGCHLD handler
-signal.Notify(sigChildChan, syscall.SIGCHLD)
-
-go func() {
- for {
- <-sigChildChan
- // Reap all available zombies
- for {
- pid, err := syscall.Wait4(-1, &status, syscall.WNOHANG, nil)
- if err != nil || pid <= 0 {
- break
- }
- log.Printf("reaped zombie: PID %d", pid)
- }
- }
-}()
-```
-
-### Process Group Management
-
-```go
-// Create child in its own process group
-cmd.SysProcAttr = &syscall.SysProcAttr{
- Setpgid: true, // Create new process group
- Pgid: 0, // Child becomes process group leader
-}
-
-// Forward signals to entire process group
-syscall.Kill(-cmd.Process.Pid, signal) // Negative PID = process group
-```
-
-### Exit Code Propagation
-
-```go
-err := cmd.Wait()
-exitCode := 0
-
-if err != nil {
- if exitErr, ok := err.(*exec.ExitError); ok {
- if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
- exitCode = status.ExitStatus()
- }
- }
-}
-
-os.Exit(exitCode) // Propagate child's exit code
-```
-
-## Best Practices for MicroVM Init
-
-### 1. **Keep It Simple**
-- Single responsibility: run your application
-- Minimal configuration
-- Clear error messages
-
-### 2. **Static Linking**
-- No runtime dependencies
-- Faster startup
-- Smaller attack surface
-
-### 3. **Security First**
-- Validate all inputs
-- Limit file operations
-- Use minimal privileges
-
-### 4. **Proper Debugging**
-- Log important events
-- Create debug files
-- Clear error messages
-
-### 5. **Resource Efficiency**
-- Minimal memory usage
-- Fast startup
-- No unnecessary features
-
-## Conclusion
-
-For microVMs running single applications, a minimal init like ours provides:
-
-✅ **All required PID 1 functionality**
-✅ **Minimal resource overhead**
-✅ **Fast startup times**
-✅ **High security**
-✅ **Easy debugging**
-✅ **Zero dependencies**
-
-While systemd is excellent for full Linux systems, it's overkill for microVMs where you want:
-- **Speed**: Boot in milliseconds, not seconds
-- **Efficiency**: Use KB, not MB of memory
-- **Simplicity**: Configure with command line, not config files
-- **Security**: Minimal attack surface
-- **Reliability**: Simple code with fewer bugs
-
-Our init strikes the perfect balance between functionality and simplicity for the microVM use case.
\ No newline at end of file
diff --git a/go/apps/metald/cmd/metald-init/Makefile b/go/apps/metald/cmd/metald-init/Makefile
deleted file mode 100644
index 5f752d634c..0000000000
--- a/go/apps/metald/cmd/metald-init/Makefile
+++ /dev/null
@@ -1,82 +0,0 @@
-# Makefile for metald-init - statically compiled init wrapper
-
-# Build variables
-VERSION ?= 0.1.0
-BUILD_TIME := $(shell date -u +"%Y-%m-%d %H:%M:%S UTC")
-LDFLAGS := -ldflags "-s -w -X main.version=$(VERSION) -X 'main.buildTime=$(BUILD_TIME)' -extldflags '-static'"
-
-# Output binary name
-BINARY := metald-init
-
-# Default target
-.DEFAULT_GOAL := build
-
-# Targets (alphabetically ordered)
-
-.PHONY: build
-build: ## Build static binary for linux/amd64
- @echo "Building $(BINARY) v$(VERSION) (static)..."
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(BINARY) .
- @echo "Built $(BINARY) successfully"
-
-.PHONY: build-all
-build-all: build-amd64 build-arm64 ## Build for multiple architectures
-
-.PHONY: build-amd64
-build-amd64: ## Build for linux/amd64
- @echo "Building $(BINARY) for linux/amd64..."
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(BINARY)-linux-amd64 .
-
-.PHONY: build-arm64
-build-arm64: ## Build for linux/arm64
- @echo "Building $(BINARY) for linux/arm64..."
- CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build $(LDFLAGS) -o $(BINARY)-linux-arm64 .
-
-.PHONY: clean
-clean: ## Remove build artifacts
- @echo "Cleaning..."
- rm -f $(BINARY) $(BINARY)-linux-*
-
-.PHONY: fmt
-fmt: ## Format code
- @echo "Formatting code..."
- go fmt ./...
-
-.PHONY: help
-help: ## Show this help message
- @echo "Available targets:"
- @echo " build - Build static binary for linux/amd64 (default)"
- @echo " build-all - Build for multiple architectures"
- @echo " build-amd64 - Build for linux/amd64"
- @echo " build-arm64 - Build for linux/arm64"
- @echo " clean - Remove build artifacts"
- @echo " fmt - Format code"
- @echo " info - Show binary information"
- @echo " install - Install to /usr/bin"
- @echo " lint - Run linter"
- @echo " test - Run basic tests"
- @echo " test-echo - Test with echo command"
- @echo " test-env - Test with environment variables"
- @echo " help - Show this help"
-
-.PHONY: info
-info: build ## Show binary information
- @echo "Binary information:"
- @file $(BINARY)
- @ls -lh $(BINARY)
- @ldd $(BINARY) 2>/dev/null || echo "Binary is statically linked (no dynamic dependencies)"
-
-.PHONY: install
-install: build ## Install to /usr/local/bin
- @echo "Installing $(BINARY) to /usr/bin..."
- sudo cp $(BINARY) /usr/bin/
- sudo chmod +x /usr/bin/$(BINARY)
-
-.PHONY: lint
-lint: ## Run linter
- @echo "Running linter..."
- golangci-lint run
-
-.PHONY: test-echo
-test-echo: build ## Test with echo command
- ./$(BINARY) -- echo "Hello from metald-init"
diff --git a/go/apps/metald/cmd/metald-init/container.cmd b/go/apps/metald/cmd/metald-init/container.cmd
deleted file mode 100644
index 073f88fcb9..0000000000
--- a/go/apps/metald/cmd/metald-init/container.cmd
+++ /dev/null
@@ -1 +0,0 @@
-["echo", "test successful"]
diff --git a/go/apps/metald/cmd/metald-init/go.mod b/go/apps/metald/cmd/metald-init/go.mod
deleted file mode 100644
index 1031f42a0b..0000000000
--- a/go/apps/metald/cmd/metald-init/go.mod
+++ /dev/null
@@ -1,8 +0,0 @@
-module github.com/unkeyed/unkey/go/deploy/metald/cmd/metald-init
-
-go 1.25
-
-toolchain go1.25.1
-
-// This is a standalone binary with minimal dependencies
-// It should be statically compiled for use in minimal rootfs environments
diff --git a/go/apps/metald/cmd/metald-init/main.go b/go/apps/metald/cmd/metald-init/main.go
deleted file mode 100644
index d45815f8e1..0000000000
--- a/go/apps/metald/cmd/metald-init/main.go
+++ /dev/null
@@ -1,614 +0,0 @@
-//go:build linux
-// +build linux
-
-package main
-
-import (
- "encoding/json"
- "fmt"
- "log"
- "os"
- "os/exec"
- "os/signal"
- "path/filepath"
- "regexp"
- "strings"
- "syscall"
- "time"
-)
-
-// AIDEV-NOTE: This init wrapper is designed to be the PID 1 process in a microvm
-// It handles:
-// - Environment variable setup from kernel cmdline
-// - Working directory changes
-// - Signal forwarding to the actual process
-// - Zombie process reaping
-// - Proper exit code propagation
-
-// Version information (set by build flags)
-var (
- version = "dev"
- buildTime = "unknown"
-)
-
-// AIDEV-BUSINESS_RULE: Security constants for safe operation
-const (
- maxJSONSize = 1024 * 1024 // 1MB limit for JSON files
- maxEnvKeyLen = 256 // Maximum environment variable key length
- maxEnvValueLen = 4096 // Maximum environment variable value length
-)
-
-// AIDEV-BUSINESS_RULE: Valid environment variable name pattern
-var validEnvKeyPattern = regexp.MustCompile(`^[A-Z][A-Z0-9_]*$`)
-
-func main() {
- // Set up logging to stderr (stdout might be used by the child process)
- log.SetOutput(os.Stderr)
- log.SetPrefix("[init] ")
-
- // AIDEV-NOTE: Write debug file with secure permissions
- os.WriteFile("/init.started", []byte(fmt.Sprintf("Started at %s\n", time.Now())), 0o600)
-
- // AIDEV-NOTE: Mount /proc filesystem so we can read kernel command line
- if err := syscall.Mount("proc", "/proc", "proc", 0, ""); err != nil {
- log.Printf("warning: failed to mount /proc: %v", err)
- // Continue anyway - we have fallback logic
- }
-
- // Parse command line arguments
- if len(os.Args) < 2 {
- // No args provided, try to read command file
- if _, err := os.Stat("/container.cmd"); err == nil {
- // Add a dummy arg so we don't exit
- os.Args = append(os.Args, "dummy")
- } else {
- log.Fatal("usage: metald-init [--version] [--help] -- command [args...]")
- }
- }
-
- // Handle special flags
- if os.Args[1] == "--version" {
- fmt.Printf("metald-init version %s (built %s)\n", version, buildTime)
- os.Exit(0)
- }
-
- if os.Args[1] == "--help" {
- printHelp()
- os.Exit(0)
- }
-
- // Find the command separator
- cmdStart := -1
- for i, arg := range os.Args[1:] {
- if arg == "--" {
- cmdStart = i + 2 // +1 for skipping os.Args[0], +1 for the "--" itself
- break
- }
- }
-
- var command string
- var commandArgs []string
-
- if cmdStart == -1 || cmdStart >= len(os.Args) {
- // AIDEV-BUSINESS_RULE: Add size limits for JSON parsing to prevent memory exhaustion
- // No command on command line, try to read from container.cmd file
- cmdData, err := readFileSafely("/container.cmd", maxJSONSize)
- if err != nil {
- log.Fatal("no command specified after '--' and no /container.cmd file found")
- }
-
- var fullCmd []string
- if err := json.Unmarshal(cmdData, &fullCmd); err != nil {
- log.Fatalf("failed to parse /container.cmd: %v", err)
- }
-
- if len(fullCmd) == 0 {
- log.Fatal("empty command in /container.cmd")
- }
-
- command = fullCmd[0]
- if len(fullCmd) > 1 {
- commandArgs = fullCmd[1:]
- }
- log.Printf("loaded command from /container.cmd: %s %v", command, commandArgs)
- } else {
- // Extract the command and its arguments from command line
- command = os.Args[cmdStart]
- commandArgs = os.Args[cmdStart+1:]
- }
-
- log.Printf("preparing to execute: %s %v", command, commandArgs)
-
- // AIDEV-NOTE: Write debug info with secure permissions
- debugInfo := fmt.Sprintf("Command: %s\nArgs: %v\nEnv count: %d\nWorking dir: %s\n",
- command, commandArgs, len(os.Environ()), os.Getenv("PWD"))
- os.WriteFile("/init.command", []byte(debugInfo), 0o600)
-
- // AIDEV-NOTE: Load container environment configuration for complete runtime replication
- containerEnv, err := loadContainerEnvironment()
- if err != nil {
- log.Printf("warning: failed to load container environment: %v", err)
- // Continue with default environment - this is not fatal
- }
-
- // Parse kernel command line for our parameters
- kernelParams := parseKernelCmdline()
-
- // AIDEV-NOTE: Apply container environment first for complete runtime replication
- if err := applyContainerEnvironment(containerEnv); err != nil {
- log.Fatalf("critical: failed to apply container environment: %v", err)
- }
-
- // AIDEV-BUSINESS_RULE: Critical failures should be fatal, not warnings
- // Set up environment variables (kernel params can override container env)
- if err := setupEnvironment(kernelParams); err != nil {
- log.Fatalf("critical: failed to setup environment: %v", err)
- }
-
- // Change working directory if specified (kernel params can override container workdir)
- if err := changeWorkingDirectory(kernelParams); err != nil {
- log.Fatalf("critical: failed to change working directory: %v", err)
- }
-
- // Create common directories that containers expect
- createCommonDirectories()
-
- // Set up signal handling
- sigChan := make(chan os.Signal, 1)
- signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
-
- // Start the command
- cmd := exec.Command(command, commandArgs...)
- cmd.Stdin = os.Stdin
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
-
- // Set up process attributes for proper signal handling
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Setpgid: true,
- Pgid: 0,
- }
-
- if err := cmd.Start(); err != nil {
- log.Fatalf("failed to start command: %v", err)
- }
-
- log.Printf("started process with PID %d", cmd.Process.Pid)
-
- // Handle signals and zombie reaping in a goroutine
- go handleSignalsAndReaping(cmd, sigChan)
-
- // Wait for the command to finish
- err = cmd.Wait()
-
- // Extract exit code
- exitCode := 0
- if err != nil {
- if exitErr, ok := err.(*exec.ExitError); ok {
- if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
- exitCode = status.ExitStatus()
- }
- } else {
- log.Printf("command failed: %v", err)
- exitCode = 1
- }
- }
-
- log.Printf("command exited with code %d", exitCode)
- os.Exit(exitCode)
-}
-
-// parseKernelCmdline reads and parses /proc/cmdline
-func parseKernelCmdline() map[string]string {
- params := make(map[string]string)
-
- cmdline, err := os.ReadFile("/proc/cmdline")
- if err != nil {
- log.Printf("warning: failed to read /proc/cmdline: %v", err)
- return params
- }
-
- // Parse space-separated key=value pairs
- for param := range strings.FieldsSeq(string(cmdline)) {
- if strings.Contains(param, "=") {
- parts := strings.SplitN(param, "=", 2)
- params[parts[0]] = parts[1]
- }
- }
-
- return params
-}
-
-// AIDEV-BUSINESS_RULE: Secure file reading with size limits
-func readFileSafely(path string, maxSize int64) ([]byte, error) {
- info, err := os.Stat(path)
- if err != nil {
- return nil, fmt.Errorf("failed to stat file: %w", err)
- }
-
- if info.Size() > maxSize {
- return nil, fmt.Errorf("file size %d exceeds maximum allowed size %d", info.Size(), maxSize)
- }
-
- return os.ReadFile(path)
-}
-
-// AIDEV-BUSINESS_RULE: Validate metadata file paths to prevent path traversal
-func validateMetadataPath(path string) error {
- // Only allow absolute paths under specific safe directories
- if !filepath.IsAbs(path) {
- return fmt.Errorf("metadata path must be absolute")
- }
-
- // Clean the path to remove any .. components
- cleanPath := filepath.Clean(path)
- if cleanPath != path {
- return fmt.Errorf("metadata path contains invalid components")
- }
-
- // Whitelist allowed directories
- allowedPrefixes := []string{
- "/metadata/",
- "/var/metadata/",
- "/tmp/metadata/",
- }
-
- for _, prefix := range allowedPrefixes {
- if strings.HasPrefix(cleanPath, prefix) {
- return nil
- }
- }
-
- return fmt.Errorf("metadata path %s is not in an allowed directory", cleanPath)
-}
-
-// AIDEV-BUSINESS_RULE: Validate and sanitize environment variable names and values
-func validateEnvVar(key, value string) error {
- if len(key) == 0 {
- return fmt.Errorf("environment variable key cannot be empty")
- }
-
- if len(key) > maxEnvKeyLen {
- return fmt.Errorf("environment variable key %s exceeds maximum length %d", key, maxEnvKeyLen)
- }
-
- if len(value) > maxEnvValueLen {
- return fmt.Errorf("environment variable value for %s exceeds maximum length %d", key, maxEnvValueLen)
- }
-
- // Validate key format (uppercase letters, numbers, underscores only)
- if !validEnvKeyPattern.MatchString(key) {
- return fmt.Errorf("environment variable key %s contains invalid characters (must match %s)", key, validEnvKeyPattern.String())
- }
-
- // Check for dangerous environment variables
- dangerousVars := []string{"LD_PRELOAD", "LD_LIBRARY_PATH", "DYLD_INSERT_LIBRARIES"}
- for _, dangerous := range dangerousVars {
- if key == dangerous {
- return fmt.Errorf("environment variable %s is not allowed for security reasons", key)
- }
- }
-
- return nil
-}
-
-// setupEnvironment sets up environment variables from kernel parameters and metadata file
-func setupEnvironment(params map[string]string) error {
- // AIDEV-BUSINESS_RULE: Validate metadata path to prevent path traversal
- // First, check if there's a metadata file specified
- if metadataPath, ok := params["metadata"]; ok {
- if err := validateMetadataPath(metadataPath); err != nil {
- return fmt.Errorf("invalid metadata path: %w", err)
- }
-
- if err := loadEnvironmentFromMetadata(metadataPath); err != nil {
- return fmt.Errorf("failed to load metadata from %s: %w", metadataPath, err)
- }
- }
-
- // AIDEV-BUSINESS_RULE: Validate and sanitize environment variables from kernel cmdline
- // Then apply env.KEY=VALUE parameters from kernel cmdline (these override metadata)
- for key, value := range params {
- if strings.HasPrefix(key, "env.") {
- envKey := strings.TrimPrefix(key, "env.")
-
- if err := validateEnvVar(envKey, value); err != nil {
- return fmt.Errorf("invalid environment variable from cmdline: %w", err)
- }
-
- if err := os.Setenv(envKey, value); err != nil {
- return fmt.Errorf("failed to set %s=%s: %w", envKey, value, err)
- }
- log.Printf("set environment from cmdline: %s=%s", envKey, value)
- }
- }
-
- // AIDEV-NOTE: Ensure PATH is set with a reasonable default if not provided
- if os.Getenv("PATH") == "" {
- defaultPath := "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
- if err := os.Setenv("PATH", defaultPath); err != nil {
- return fmt.Errorf("failed to set default PATH: %w", err)
- }
- log.Printf("set default PATH (no PATH provided): %s", defaultPath)
- }
-
- return nil
-}
-
-// loadEnvironmentFromMetadata loads environment variables from a metadata JSON file
-func loadEnvironmentFromMetadata(path string) error {
- // AIDEV-BUSINESS_RULE: Use safe file reading with size limits
- data, err := readFileSafely(path, maxJSONSize)
- if err != nil {
- return fmt.Errorf("failed to read metadata file: %w", err)
- }
-
- // Parse metadata JSON (compatible with builderd's ImageMetadata)
- var metadata struct {
- Env map[string]string `json:"env"`
- WorkingDir string `json:"working_dir"`
- Entrypoint []string `json:"entrypoint"`
- Command []string `json:"command"`
- }
-
- if err := json.Unmarshal(data, &metadata); err != nil {
- return fmt.Errorf("failed to parse metadata: %w", err)
- }
-
- // AIDEV-BUSINESS_RULE: Validate environment variables from metadata
- // Set environment variables from metadata
- for key, value := range metadata.Env {
- // Skip PATH from metadata to avoid conflicts
- if key == "PATH" {
- continue
- }
-
- if err := validateEnvVar(key, value); err != nil {
- log.Printf("warning: skipping invalid environment variable from metadata: %v", err)
- continue
- }
-
- if err := os.Setenv(key, value); err != nil {
- return fmt.Errorf("failed to set %s=%s from metadata: %w", key, value, err)
- }
- log.Printf("set environment from metadata: %s=%s", key, value)
- }
-
- return nil
-}
-
-// AIDEV-BUSINESS_RULE: Validate working directory path for security
-func validateWorkingDirectory(path string) error {
- if !filepath.IsAbs(path) {
- return fmt.Errorf("working directory must be absolute path")
- }
-
- // Clean the path to remove any .. components
- cleanPath := filepath.Clean(path)
- if cleanPath != path {
- return fmt.Errorf("working directory contains invalid path components")
- }
-
- return nil
-}
-
-// changeWorkingDirectory changes to the specified working directory
-func changeWorkingDirectory(params map[string]string) error {
- // AIDEV-BUSINESS_RULE: Implement complete working directory change with metadata support
- var targetWorkdir string
-
- // First check kernel cmdline parameter
- if workdir, ok := params["workdir"]; ok {
- targetWorkdir = workdir
- } else if metadataPath, ok := params["metadata"]; ok {
- // Try to get working directory from metadata
- if err := validateMetadataPath(metadataPath); err == nil {
- data, err := readFileSafely(metadataPath, maxJSONSize)
- if err == nil {
- var metadata struct {
- WorkingDir string `json:"working_dir"`
- }
- if json.Unmarshal(data, &metadata) == nil && metadata.WorkingDir != "" {
- targetWorkdir = metadata.WorkingDir
- log.Printf("using working directory from metadata: %s", targetWorkdir)
- }
- }
- }
- }
-
- if targetWorkdir == "" {
- return nil // No working directory specified
- }
-
- // AIDEV-BUSINESS_RULE: Validate working directory path
- if err := validateWorkingDirectory(targetWorkdir); err != nil {
- return fmt.Errorf("invalid working directory: %w", err)
- }
-
- // Ensure the directory exists
- if _, err := os.Stat(targetWorkdir); os.IsNotExist(err) {
- return fmt.Errorf("working directory %s does not exist", targetWorkdir)
- }
-
- if err := os.Chdir(targetWorkdir); err != nil {
- return fmt.Errorf("failed to change to %s: %w", targetWorkdir, err)
- }
- log.Printf("changed working directory to: %s", targetWorkdir)
- return nil
-}
-
-// createCommonDirectories creates directories commonly expected by applications
-func createCommonDirectories() {
- // List of directories that applications commonly expect to exist in a microvm
- commonDirs := []string{
- "/var/log",
- "/var/run",
- "/var/cache",
- "/tmp",
- }
-
- for _, dir := range commonDirs {
- if err := os.MkdirAll(dir, 0o755); err != nil {
- log.Printf("warning: failed to create directory %s: %v", dir, err)
- } else {
- log.Printf("ensured directory exists: %s", dir)
- }
- }
-}
-
-// AIDEV-BUSINESS_RULE: Validate process group exists before signaling
-func validateProcessGroup(pid int) error {
- // Check if the process group exists by getting its process group ID
- pgid, err := syscall.Getpgid(pid)
- if err != nil {
- return fmt.Errorf("failed to get process group for PID %d: %w", pid, err)
- }
-
- if pgid <= 0 {
- return fmt.Errorf("invalid process group ID %d for PID %d", pgid, pid)
- }
-
- return nil
-}
-
-// handleSignalsAndReaping handles signal forwarding and zombie process reaping
-func handleSignalsAndReaping(cmd *exec.Cmd, sigChan chan os.Signal) {
- // Set up SIGCHLD handler for immediate zombie reaping
- sigChildChan := make(chan os.Signal, 1)
- signal.Notify(sigChildChan, syscall.SIGCHLD)
-
- // AIDEV-BUSINESS_RULE: Remove busy-wait loop, use proper blocking select
- for {
- select {
- case sig := <-sigChan:
- log.Printf("received signal: %v, forwarding to child process", sig)
- if cmd.Process != nil {
- // AIDEV-BUSINESS_RULE: Validate process group before signaling
- if err := validateProcessGroup(cmd.Process.Pid); err != nil {
- log.Printf("warning: cannot validate process group: %v", err)
- continue
- }
-
- // Forward signal to the entire process group
- if err := syscall.Kill(-cmd.Process.Pid, sig.(syscall.Signal)); err != nil {
- log.Printf("warning: failed to forward signal: %v", err)
- }
- }
-
- case <-sigChildChan:
- // SIGCHLD received, reap any zombie processes with bounds
- reapedCount := 0
- maxReapIterations := 100 // Prevent infinite loops
-
- for i := 0; i < maxReapIterations; i++ {
- var status syscall.WaitStatus
- pid, err := syscall.Wait4(-1, &status, syscall.WNOHANG, nil)
- if err != nil {
- if err != syscall.ECHILD {
- log.Printf("wait4 error: %v", err)
- }
- break
- }
- if pid <= 0 {
- // No more children to reap
- break
- }
- reapedCount++
- log.Printf("reaped zombie process: PID %d, status: %v", pid, status)
- }
-
- if reapedCount > 0 {
- log.Printf("reaped %d zombie processes", reapedCount)
- }
- }
- // AIDEV-NOTE: Removed default case to eliminate busy-wait loop
- }
-}
-
-// ContainerEnvironment represents container runtime environment configuration
-// AIDEV-NOTE: This matches the structure created by builderd's createContainerEnv function
-type ContainerEnvironment struct {
- WorkingDir string `json:"working_dir,omitempty"`
- Env map[string]string `json:"env,omitempty"`
- User string `json:"user,omitempty"`
- ExposedPorts []string `json:"exposed_ports,omitempty"`
-}
-
-// loadContainerEnvironment loads container environment configuration from /container.env
-// AIDEV-NOTE: This function provides complete container runtime environment replication
-func loadContainerEnvironment() (*ContainerEnvironment, error) {
- envData, err := readFileSafely("/container.env", maxJSONSize)
- if err != nil {
- return nil, fmt.Errorf("failed to read container.env: %w", err)
- }
-
- var containerEnv ContainerEnvironment
- if err := json.Unmarshal(envData, &containerEnv); err != nil {
- return nil, fmt.Errorf("failed to parse container.env: %w", err)
- }
-
- log.Printf("loaded container environment: workdir=%s, env_vars=%d, user=%s",
- containerEnv.WorkingDir, len(containerEnv.Env), containerEnv.User)
-
- return &containerEnv, nil
-}
-
-// applyContainerEnvironment applies container environment configuration
-// AIDEV-NOTE: This sets up the complete container runtime environment
-func applyContainerEnvironment(containerEnv *ContainerEnvironment) error {
- if containerEnv == nil {
- // AIDEV-NOTE: No container.env file - environment will be set from kernel cmdline instead
- log.Printf("no container.env found - relying on kernel cmdline environment")
- return nil
- }
-
- // Set environment variables
- if containerEnv.Env != nil {
- for key, value := range containerEnv.Env {
- if err := validateEnvVar(key, value); err != nil {
- log.Printf("warning: skipping invalid env var %s: %v", key, err)
- continue
- }
- if err := os.Setenv(key, value); err != nil {
- return fmt.Errorf("failed to set env var %s: %w", key, err)
- }
- }
- log.Printf("applied %d environment variables", len(containerEnv.Env))
- }
-
- // Change working directory
- if containerEnv.WorkingDir != "" && containerEnv.WorkingDir != "/" {
- if err := os.Chdir(containerEnv.WorkingDir); err != nil {
- return fmt.Errorf("failed to change working directory to %s: %w", containerEnv.WorkingDir, err)
- }
- log.Printf("changed working directory to: %s", containerEnv.WorkingDir)
- }
-
- return nil
-}
-
-// printHelp prints usage information
-func printHelp() {
- binaryName := filepath.Base(os.Args[0])
- help := fmt.Sprintf(`%s - Generic init process for microvms
-
-Usage:
- %s [options] -- command [args...]
-
-Options:
- --version Show version information
- --help Show this help message
-
-Environment:
- The init process reads kernel command line parameters from /proc/cmdline:
-
- env.KEY=VALUE Set environment variable KEY to VALUE
- workdir=/path Change working directory to /path
-
-Example:
- %s -- nginx -g "daemon off;"
-
- With kernel cmdline: env.NGINX_PORT=8080 workdir=/app
-`, binaryName, binaryName, binaryName)
- fmt.Print(help)
-}
diff --git a/go/apps/metald/cmd/metald-init/test-all.sh b/go/apps/metald/cmd/metald-init/test-all.sh
deleted file mode 100755
index 3fdf41b58d..0000000000
--- a/go/apps/metald/cmd/metald-init/test-all.sh
+++ /dev/null
@@ -1,139 +0,0 @@
-#!/bin/bash
-# Comprehensive test suite for metald-init
-
-set -e
-
-echo "=== Comprehensive metald-init test suite ==="
-echo "Building metald-init..."
-make build
-
-TESTS_PASSED=0
-TESTS_FAILED=0
-
-# Helper function to run a test
-run_test() {
- local test_name="$1"
- local test_cmd="$2"
- echo -e "\n--- Test: $test_name ---"
- if eval "$test_cmd"; then
- echo "✓ PASSED: $test_name"
- ((TESTS_PASSED++))
- else
- echo "✗ FAILED: $test_name"
- ((TESTS_FAILED++))
- fi
-}
-
-# Test 1: Basic execution
-test_basic() {
- ./metald-init -- echo "Hello World" | grep -q "Hello World"
-}
-run_test "Basic execution" test_basic
-
-# Test 2: Exit code propagation
-test_exit_code() {
- # Should exit with 0
- ./metald-init -- true
- [ $? -eq 0 ] || return 1
-
- # Should exit with 1
- ./metald-init -- false || [ $? -eq 1 ]
-}
-run_test "Exit code propagation" test_exit_code
-
-# Test 3: Environment variables from metadata
-test_env_metadata() {
- # Create test metadata
- cat > test-metadata.json < /dev/null
- rm -f test-metadata.json
-}
-run_test "Environment metadata parsing" test_env_metadata
-
-# Test 4: Working directory
-test_workdir() {
- # Test changing to /tmp
- cd /
- ./metald-init -- pwd | grep -q "/"
-}
-run_test "Working directory" test_workdir
-
-# Test 5: Signal forwarding
-test_signal_forward() {
- # Start a sleep process
- timeout 2 ./metald-init -- sleep 10 || [ $? -eq 124 ]
-}
-run_test "Signal forwarding (timeout)" test_signal_forward
-
-# Test 6: Multiple arguments
-test_multiple_args() {
- ./metald-init -- echo "one" "two" "three" | grep -q "one two three"
-}
-run_test "Multiple arguments" test_multiple_args
-
-# Test 7: Stdin/stdout/stderr
-test_stdio() {
- # Test stdin
- echo "test input" | ./metald-init -- cat | grep -q "test input" || return 1
-
- # Test stderr
- ./metald-init -- sh -c 'echo "error" >&2' 2>&1 | grep -q "error"
-}
-run_test "Stdin/stdout/stderr" test_stdio
-
-# Test 8: Binary execution
-test_binary() {
- ./metald-init -- /bin/ls /bin > /dev/null
-}
-run_test "Binary execution" test_binary
-
-# Test 9: Shell script execution
-test_shell_script() {
- cat > test-script.sh <<'EOF'
-#!/bin/bash
-echo "Script executed"
-exit 42
-EOF
- chmod +x test-script.sh
- ./metald-init -- ./test-script.sh | grep -q "Script executed" || return 1
- # Check exit code
- ./metald-init -- ./test-script.sh > /dev/null || [ $? -eq 42 ]
- rm -f test-script.sh
-}
-run_test "Shell script execution" test_shell_script
-
-# Test 10: Long running process
-test_long_running() {
- # Start a process that runs for 1 second
- start_time=$(date +%s)
- ./metald-init -- sleep 1
- end_time=$(date +%s)
- duration=$((end_time - start_time))
- [ $duration -ge 1 ] && [ $duration -le 2 ]
-}
-run_test "Long running process" test_long_running
-
-# Summary
-echo -e "\n=== Test Summary ==="
-echo "Tests passed: $TESTS_PASSED"
-echo "Tests failed: $TESTS_FAILED"
-echo "Total tests: $((TESTS_PASSED + TESTS_FAILED))"
-
-if [ $TESTS_FAILED -eq 0 ]; then
- echo -e "\n✓ All tests passed!"
- exit 0
-else
- echo -e "\n✗ Some tests failed"
- exit 1
-fi
\ No newline at end of file
diff --git a/go/apps/metald/cmd/metald-init/test-env.sh b/go/apps/metald/cmd/metald-init/test-env.sh
deleted file mode 100755
index 1462c14d22..0000000000
--- a/go/apps/metald/cmd/metald-init/test-env.sh
+++ /dev/null
@@ -1,73 +0,0 @@
-#!/bin/bash
-# Test environment variable setup in metald-init
-
-set -e
-
-echo "=== Testing metald-init environment variable setup ==="
-
-# Build the init wrapper
-echo "Building metald-init..."
-make build
-
-# Create a test metadata file
-cat > test-metadata.json < test-env-wrapper.sh <<'EOF'
-#!/bin/bash
-# This simulates what would happen with kernel cmdline: env.TEST_VAR=cmdline_value
-export TEST_FROM_WRAPPER=wrapper_value
-exec ./metald-init -- env
-EOF
-chmod +x test-env-wrapper.sh
-./test-env-wrapper.sh | grep -E "(TEST_|wrapper)" || true
-
-# Test 3: Create a modified version that reads from a test cmdline file
-echo -e "\n--- Test 3: Creating test version with mock cmdline ---"
-cat > test-init.go <<'EOF'
-package main
-
-import (
- "os"
- "os/exec"
- "strings"
-)
-
-func main() {
- // Set up test kernel parameters
- os.Setenv("TEST_CMDLINE", "env.TEST_VAR=from_cmdline env.ANOTHER_VAR=test123 metadata=/metadata.json workdir=/tmp")
-
- // Run the actual init with test environment
- cmd := exec.Command("./metald-init", os.Args[1:]...)
- cmd.Stdin = os.Stdin
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- cmd.Env = append(os.Environ(), "TEST_MODE=1")
- cmd.Run()
-}
-EOF
-
-# Clean up
-echo -e "\n--- Cleanup ---"
-rm -f test-metadata.json test-env-wrapper.sh test-init.go
-
-echo -e "\n=== Environment variable tests completed ==="
-echo "Note: Full testing requires running in a VM where /proc/cmdline can be controlled"
\ No newline at end of file
diff --git a/go/apps/metald/cmd/metald-init/test-signals.sh b/go/apps/metald/cmd/metald-init/test-signals.sh
deleted file mode 100755
index 7c7032d02f..0000000000
--- a/go/apps/metald/cmd/metald-init/test-signals.sh
+++ /dev/null
@@ -1,109 +0,0 @@
-#!/bin/bash
-# Test signal handling in metald-init
-
-set -e
-
-echo "=== Testing metald-init signal handling ==="
-
-# Build the init wrapper
-echo "Building metald-init..."
-make build
-
-# Test 1: Test with sleep and SIGTERM
-echo -e "\n--- Test 1: SIGTERM forwarding ---"
-echo "Starting sleep process through metald-init..."
-./metald-init -- sleep 30 &
-INIT_PID=$!
-sleep 1
-
-# Get the sleep process PID
-SLEEP_PID=$(pgrep -P $INIT_PID sleep || echo "not found")
-echo "Init PID: $INIT_PID, Sleep PID: $SLEEP_PID"
-
-if [ "$SLEEP_PID" != "not found" ]; then
- echo "Sending SIGTERM to init process..."
- kill -TERM $INIT_PID
-
- # Wait a bit and check if process terminated
- sleep 2
- if ! kill -0 $INIT_PID 2>/dev/null; then
- echo "✓ Init process terminated after SIGTERM"
- else
- echo "✗ Init process still running, forcing kill"
- kill -9 $INIT_PID 2>/dev/null || true
- fi
-else
- echo "✗ Could not find sleep process"
- kill -9 $INIT_PID 2>/dev/null || true
-fi
-
-# Test 2: Test with a script that handles signals
-echo -e "\n--- Test 2: Signal handling with trap ---"
-cat > signal-test.sh <<'EOF'
-#!/bin/bash
-echo "Signal test script started with PID $$"
-trap 'echo "Received SIGTERM, cleaning up..."; exit 0' TERM
-trap 'echo "Received SIGINT, cleaning up..."; exit 0' INT
-
-echo "Waiting for signals..."
-while true; do
- sleep 1
-done
-EOF
-chmod +x signal-test.sh
-
-echo "Starting signal test script..."
-./metald-init -- ./signal-test.sh &
-INIT_PID=$!
-sleep 1
-
-echo "Sending SIGTERM to init..."
-kill -TERM $INIT_PID
-sleep 2
-
-if ! kill -0 $INIT_PID 2>/dev/null; then
- echo "✓ Init and child process terminated gracefully"
-else
- echo "✗ Process still running, forcing kill"
- kill -9 $INIT_PID 2>/dev/null || true
-fi
-
-# Test 3: Test zombie reaping
-echo -e "\n--- Test 3: Zombie process reaping ---"
-cat > zombie-test.sh <<'EOF'
-#!/bin/bash
-echo "Creating zombie processes..."
-# Create a process that exits immediately (becomes zombie)
-(sleep 0.1) &
-(sleep 0.1) &
-(sleep 0.1) &
-echo "Created 3 potential zombie processes"
-# Keep running so we can check
-sleep 5
-echo "Zombie test complete"
-EOF
-chmod +x zombie-test.sh
-
-echo "Starting zombie test..."
-./metald-init -- ./zombie-test.sh &
-INIT_PID=$!
-sleep 2
-
-# Check for zombie processes
-ZOMBIES=$(ps aux | grep -E "Z.*defunct" | grep -v grep | wc -l)
-echo "Number of zombie processes: $ZOMBIES"
-
-if [ $ZOMBIES -eq 0 ]; then
- echo "✓ No zombie processes found - reaping works!"
-else
- echo "✗ Found $ZOMBIES zombie processes"
-fi
-
-# Clean up
-kill -TERM $INIT_PID 2>/dev/null || true
-wait $INIT_PID 2>/dev/null || true
-
-# Cleanup
-rm -f signal-test.sh zombie-test.sh
-
-echo -e "\n=== Signal handling tests completed ==="
\ No newline at end of file
diff --git a/go/apps/metald/cmd/metald-init/test-zombie-reap.sh b/go/apps/metald/cmd/metald-init/test-zombie-reap.sh
deleted file mode 100755
index d185a41744..0000000000
--- a/go/apps/metald/cmd/metald-init/test-zombie-reap.sh
+++ /dev/null
@@ -1,73 +0,0 @@
-#!/bin/bash
-# More comprehensive zombie reaping test
-
-echo "=== Testing zombie reaping ==="
-
-# Build
-make build
-
-# Create a C program that creates zombies
-cat > zombie-creator.c <<'EOF'
-#include
-#include
-#include
-#include
-
-int main() {
- printf("Zombie creator started, PID: %d\n", getpid());
-
- // Create some child processes that exit immediately
- for (int i = 0; i < 5; i++) {
- pid_t pid = fork();
- if (pid == 0) {
- // Child exits immediately
- printf("Child %d (PID %d) exiting\n", i, getpid());
- exit(0);
- } else if (pid > 0) {
- printf("Created child PID %d\n", pid);
- // Parent doesn't wait - creates zombie
- }
- usleep(100000); // 100ms between forks
- }
-
- // Keep running for a bit
- printf("Parent continuing without reaping...\n");
- sleep(3);
- printf("Zombie creator exiting\n");
- return 0;
-}
-EOF
-
-# Compile the zombie creator
-gcc -o zombie-creator zombie-creator.c
-
-echo -e "\n--- Running with standard shell (zombies expected) ---"
-./zombie-creator &
-CREATOR_PID=$!
-sleep 1
-
-# Check zombies
-echo "Checking for zombies..."
-ps aux | grep defunct | grep -v grep || echo "No zombies found"
-
-# Clean up
-wait $CREATOR_PID 2>/dev/null
-
-echo -e "\n--- Running with metald-init (zombies should be reaped) ---"
-./metald-init -- ./zombie-creator &
-INIT_PID=$!
-sleep 2
-
-# Check zombies
-echo "Checking for zombies..."
-ZOMBIE_COUNT=$(ps aux | grep defunct | grep -v grep | wc -l)
-echo "Zombie count: $ZOMBIE_COUNT"
-
-# Let it finish
-sleep 2
-wait $INIT_PID 2>/dev/null
-
-# Clean up
-rm -f zombie-creator zombie-creator.c
-
-echo "=== Zombie reaping test completed ==="
\ No newline at end of file
diff --git a/go/apps/metald/cmd/metald/main.go b/go/apps/metald/cmd/metald/main.go
deleted file mode 100644
index 222034da34..0000000000
--- a/go/apps/metald/cmd/metald/main.go
+++ /dev/null
@@ -1,507 +0,0 @@
-//go:build linux
-// +build linux
-
-package main
-
-import (
- "context"
- "errors"
- "flag"
- "fmt"
- "log/slog"
- "net/http"
- "os"
- "os/signal"
- "runtime"
- "runtime/debug"
- "syscall"
- "time"
-
- "github.com/unkeyed/unkey/go/apps/metald/internal/assetmanager"
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/firecracker"
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/types"
- "github.com/unkeyed/unkey/go/apps/metald/internal/billing"
- "github.com/unkeyed/unkey/go/apps/metald/internal/config"
- "github.com/unkeyed/unkey/go/apps/metald/internal/database"
- "github.com/unkeyed/unkey/go/apps/metald/internal/observability"
- "github.com/unkeyed/unkey/go/apps/metald/internal/service"
- healthpkg "github.com/unkeyed/unkey/go/deploy/pkg/health"
- "github.com/unkeyed/unkey/go/deploy/pkg/observability/interceptors"
- tlspkg "github.com/unkeyed/unkey/go/deploy/pkg/tls"
- "github.com/unkeyed/unkey/go/gen/proto/metald/v1/metaldv1connect"
-
- "connectrpc.com/connect"
- "github.com/prometheus/client_golang/prometheus/promhttp"
- "go.opentelemetry.io/otel"
- "golang.org/x/net/http2"
- "golang.org/x/net/http2/h2c"
-)
-
-// version is set at build time via ldflags
-var version = ""
-
-// getVersion returns the version string, with fallback to debug.ReadBuildInfo
-func getVersion() string {
- // If version was set via ldflags (production builds), use it
- if version != "" {
- return version
- }
-
- // Fallback to debug.ReadBuildInfo for development/module builds
- if info, ok := debug.ReadBuildInfo(); ok {
- // Use the module version if available
- if info.Main.Version != "(devel)" && info.Main.Version != "" {
- return info.Main.Version
- }
-
- // Try to get version from VCS info
- for _, setting := range info.Settings {
- if setting.Key == "vcs.revision" && len(setting.Value) >= 7 {
- return "dev-" + setting.Value[:7] // First 8 chars of commit hash
- }
- }
-
- return "dev"
- }
-
- return version
-}
-
-func main() {
- startTime := time.Now()
-
- var (
- showHelp = flag.Bool("help", false, "Show help information")
- showVersion = flag.Bool("version", false, "Show version information")
- )
- flag.Parse()
-
- if *showHelp {
- printUsage()
- os.Exit(0)
- }
-
- if *showVersion {
- printVersion()
- os.Exit(0)
- }
-
- // Initialize structured logger with JSON output
- //exhaustruct:ignore
- logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
- Level: slog.LevelDebug,
- }))
- slog.SetDefault(logger)
-
- // Log startup
- logger.Info("starting metald",
- slog.String("version", getVersion()),
- slog.String("go_version", runtime.Version()),
- )
-
- cfg, err := config.LoadConfig()
- if err != nil {
- logger.Error("failed to load configuration",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
-
- logger.Info("configuration loaded",
- slog.String("address", cfg.Server.Address),
- slog.String("port", cfg.Server.Port),
- slog.Bool("otel_enabled", cfg.OpenTelemetry.Enabled),
- )
-
- // Initialize OpenTelemetry
- ctx := context.Background()
- otelProviders, err := observability.InitProviders(ctx, cfg, getVersion(), logger)
- if err != nil {
- logger.Error("failed to initialize OpenTelemetry",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
- defer func() {
- shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
- if shutdownErr := otelProviders.Shutdown(shutdownCtx); shutdownErr != nil {
- logger.Error("failed to shutdown OpenTelemetry",
- slog.String("error", shutdownErr.Error()),
- )
- }
- }()
-
- if cfg.OpenTelemetry.Enabled {
- logger.Info("OpenTelemetry initialized",
- slog.String("service_name", cfg.OpenTelemetry.ServiceName),
- slog.String("service_version", cfg.OpenTelemetry.ServiceVersion),
- slog.Float64("sampling_rate", cfg.OpenTelemetry.TracingSamplingRate),
- slog.String("otlp_endpoint", cfg.OpenTelemetry.OTLPEndpoint),
- slog.Bool("prometheus_enabled", cfg.OpenTelemetry.PrometheusEnabled),
- slog.Bool("high_cardinality_enabled", cfg.OpenTelemetry.HighCardinalityLabelsEnabled),
- )
- }
-
- // Initialize TLS provider (defaults to disabled)
- //exhaustruct:ignore
- tlsConfig := tlspkg.Config{
- Mode: tlspkg.Mode(cfg.TLS.Mode),
- CertFile: cfg.TLS.CertFile,
- KeyFile: cfg.TLS.KeyFile,
- CAFile: cfg.TLS.CAFile,
- SPIFFESocketPath: cfg.TLS.SPIFFESocketPath,
- EnableCertCaching: cfg.TLS.EnableCertCaching,
- }
- // Parse certificate cache TTL
- if cfg.TLS.CertCacheTTL != "" {
- if duration, parseErr := time.ParseDuration(cfg.TLS.CertCacheTTL); parseErr == nil {
- tlsConfig.CertCacheTTL = duration
- } else {
- logger.Warn("invalid TLS certificate cache TTL, using default 5s",
- "value", cfg.TLS.CertCacheTTL,
- "error", parseErr)
- }
- }
- tlsProvider, err := tlspkg.NewProvider(ctx, tlsConfig)
- if err != nil {
- // AIDEV-BUSINESS_RULE: TLS/SPIFFE is required - fatal error if it fails
- logger.Error("TLS initialization failed",
- "error", err,
- "mode", cfg.TLS.Mode)
- os.Exit(1)
- }
- defer tlsProvider.Close()
-
- logger.Info("TLS provider initialized",
- "mode", cfg.TLS.Mode,
- "spiffe_enabled", cfg.TLS.Mode == "spiffe")
-
- db, dbErr := database.NewDatabaseWithLogger(cfg.Database.DataDir, slog.Default())
- if dbErr != nil {
- logger.Error("failed to get DB",
- slog.String("error", dbErr.Error()),
- )
- os.Exit(1)
- }
-
- // Initialize backend based on configuration
- var backend types.Backend
- switch cfg.Backend.Type {
- case types.BackendTypeFirecracker:
- // Base directory for VM data
- baseDir := "/opt/metald/vms"
-
- // Create AssetManager client for asset preparation
- var assetClient assetmanager.Client
- if cfg.AssetManager.Enabled {
- // Use TLS-enabled HTTP client
- httpClient := tlsProvider.HTTPClient()
- assetClient, err = assetmanager.NewClientWithHTTP(&cfg.AssetManager, logger, httpClient)
- if err != nil {
- logger.Error("failed to create assetmanager client",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
- logger.Info("initialized assetmanager client",
- slog.String("endpoint", cfg.AssetManager.Endpoint),
- )
- } else {
- // Use noop client if assetmanager is disabled
- assetClient, _ = assetmanager.NewClient(&cfg.AssetManager, logger)
- logger.Info("assetmanager disabled, using noop client")
- }
-
- sdkClient, err := firecracker.NewClient(logger, assetClient, &cfg.Backend.Jailer, baseDir)
- if err != nil {
- logger.Error("failed to create firecracker client",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
-
- logger.Info("initialized firecracker backend",
- slog.String("firecracker_binary", "/usr/local/bin/firecracker"),
- slog.Uint64("uid", uint64(cfg.Backend.Jailer.UID)),
- slog.Uint64("gid", uint64(cfg.Backend.Jailer.GID)),
- slog.String("chroot_base", cfg.Backend.Jailer.ChrootBaseDir),
- )
-
- backend = sdkClient
- case types.BackendTypeDocker:
- // // AIDEV-NOTE: Docker backend for development - creates containers instead of VMs
- // logger.Info("initializing Docker backend for development")
-
- // dockerClient, err := docker.NewDockerBackend(logger, docker.DefaultDockerBackendConfig())
- // if err != nil {
- // logger.Error("failed to create Docker backend",
- // slog.String("error", err.Error()),
- // )
- // os.Exit(1)
- // }
-
- // backend = dockerClient
- // logger.Info("Docker backend initialized successfully")
- default:
- logger.Error("unsupported backend type",
- slog.String("backend", string(cfg.Backend.Type)),
- )
- os.Exit(1)
- }
-
- // Create billing client based on configuration
- var billingClient billing.BillingClient
- if cfg.Billing.Enabled {
- if cfg.Billing.MockMode {
- billingClient = billing.NewMockBillingClient(logger)
- logger.Info("initialized mock billing client")
- } else {
- // Use TLS-enabled HTTP client
- httpClient := tlsProvider.HTTPClient()
- // AIDEV-NOTE: Enhanced debug logging for service connection initialization
- logger.Debug("attempting to initialize billing client",
- slog.String("endpoint", cfg.Billing.Endpoint),
- slog.String("tls_mode", cfg.TLS.Mode),
- slog.Bool("mock_mode", cfg.Billing.MockMode),
- )
- billingClient = billing.NewConnectRPCBillingClientWithHTTP(cfg.Billing.Endpoint, logger, httpClient)
- logger.Info("initialized ConnectRPC billing client",
- "endpoint", cfg.Billing.Endpoint,
- "tls_enabled", cfg.TLS.Mode != "disabled",
- )
- }
- } else {
- billingClient = billing.NewMockBillingClient(logger)
- logger.Info("billing disabled, using mock client")
- }
-
- // Create VM metrics (only if OpenTelemetry is enabled)
- var vmMetrics *observability.VMMetrics
- var billingMetrics *observability.BillingMetrics
- if cfg.OpenTelemetry.Enabled {
- var err error
- vmMetrics, err = observability.NewVMMetrics(logger, cfg.OpenTelemetry.HighCardinalityLabelsEnabled)
- if err != nil {
- logger.Error("failed to initialize VM metrics",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
-
- billingMetrics, err = observability.NewBillingMetrics(logger, cfg.OpenTelemetry.HighCardinalityLabelsEnabled)
- if err != nil {
- logger.Error("failed to initialize billing metrics",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
- logger.Info("VM and billing metrics initialized",
- slog.Bool("high_cardinality_enabled", cfg.OpenTelemetry.HighCardinalityLabelsEnabled),
- )
- }
-
- // Create metrics collector
- instanceID := fmt.Sprintf("metald-%d", time.Now().Unix())
- metricsCollector := billing.NewMetricsCollector(backend, billingClient, logger, instanceID, billingMetrics)
-
- // Start heartbeat service
- metricsCollector.StartHeartbeat()
-
- // Create VM service
- vmService := service.NewVMService(backend, logger, metricsCollector, vmMetrics, db.Queries)
-
- // Create unified health handler
- healthHandler := healthpkg.Handler("metald", getVersion(), startTime)
-
- // Create ConnectRPC handler with shared interceptors
- var interceptorList []connect.Interceptor
-
- // Configure shared interceptor options
- interceptorOpts := []interceptors.Option{
- interceptors.WithServiceName("metald"),
- interceptors.WithLogger(logger),
- interceptors.WithActiveRequestsMetric(true),
- interceptors.WithRequestDurationMetric(true), // Match existing behavior
- interceptors.WithErrorResampling(true),
- interceptors.WithPanicStackTrace(true),
- }
-
- // Add meter if OpenTelemetry is enabled
- if cfg.OpenTelemetry.Enabled {
- interceptorOpts = append(interceptorOpts, interceptors.WithMeter(otel.Meter("metald")))
- }
-
- // Get default interceptors (tenant auth, metrics, logging)
- sharedInterceptors := interceptors.NewDefaultInterceptors("metald", interceptorOpts...)
-
- // Add shared interceptors (convert UnaryInterceptorFunc to Interceptor)
- for _, interceptor := range sharedInterceptors {
- interceptorList = append(interceptorList, connect.Interceptor(interceptor))
- }
-
- mux := http.NewServeMux()
- path, handler := metaldv1connect.NewVmServiceHandler(vmService,
- connect.WithInterceptors(interceptorList...),
- )
- mux.Handle(path, handler)
-
- // Add Prometheus metrics endpoint if enabled
- if cfg.OpenTelemetry.Enabled && cfg.OpenTelemetry.PrometheusEnabled {
- mux.Handle("/metrics", otelProviders.PrometheusHTTP)
- logger.Info("Prometheus metrics endpoint enabled",
- slog.String("path", "/metrics"),
- )
- }
-
- // Create HTTP server with H2C support for gRPC
- addr := fmt.Sprintf("%s:%s", cfg.Server.Address, cfg.Server.Port)
-
- // AIDEV-NOTE: Removed otelhttp.NewHandler to prevent double-span issues
- // The OTEL interceptor in the ConnectRPC handler already handles tracing
- var httpHandler http.Handler = mux
-
- // Configure server with optional TLS and security timeouts
- server := &http.Server{
- Addr: addr,
- Handler: h2c.NewHandler(httpHandler, &http2.Server{}), //nolint:exhaustruct
- ReadTimeout: 30 * time.Second, // Time to read request headers
- WriteTimeout: 30 * time.Second, // Time to write response
- IdleTimeout: 120 * time.Second, // Keep-alive timeout
- MaxHeaderBytes: 1 << 20, // 1MB max header size
- }
-
- // Apply TLS configuration if enabled
- serverTLSConfig, _ := tlsProvider.ServerTLSConfig()
- if serverTLSConfig != nil {
- server.TLSConfig = serverTLSConfig
- // For TLS, we need to use regular handler, not h2c
- server.Handler = httpHandler
- }
-
- // Start main server in goroutine
- go func() {
- if serverTLSConfig != nil {
- logger.Info("starting HTTPS server with TLS",
- slog.String("address", addr),
- slog.String("tls_mode", cfg.TLS.Mode),
- )
- // Empty strings for cert/key paths - SPIFFE provides them in memory
- if err := server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
- logger.Error("server failed",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
- } else {
- logger.Info("starting HTTP server without TLS",
- slog.String("address", addr),
- )
- if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
- logger.Error("server failed",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
- }
- }()
-
- // Start Prometheus server on separate port if enabled
- var promServer *http.Server
- if cfg.OpenTelemetry.Enabled && cfg.OpenTelemetry.PrometheusEnabled {
- // AIDEV-NOTE: Use configured interface, defaulting to localhost for security
- promAddr := fmt.Sprintf("%s:%s", cfg.OpenTelemetry.PrometheusInterface, cfg.OpenTelemetry.PrometheusPort)
- promMux := http.NewServeMux()
- promMux.Handle("/metrics", promhttp.Handler())
- promMux.HandleFunc("/health", healthHandler)
-
- promServer = &http.Server{
- Addr: promAddr,
- Handler: promMux,
- ReadTimeout: 30 * time.Second,
- WriteTimeout: 30 * time.Second,
- IdleTimeout: 120 * time.Second,
- }
-
- go func() {
- localhostOnly := cfg.OpenTelemetry.PrometheusInterface == "127.0.0.1" || cfg.OpenTelemetry.PrometheusInterface == "localhost"
- logger.Info("starting prometheus metrics server",
- slog.String("address", promAddr),
- slog.Bool("localhost_only", localhostOnly),
- )
- if err := promServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
- logger.Error("prometheus server failed",
- slog.String("error", err.Error()),
- )
- }
- }()
- }
-
- // Wait for interrupt signal
- sigChan := make(chan os.Signal, 1)
- signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
- <-sigChan
-
- logger.Info("shutting down server")
-
- // Graceful shutdown with timeout
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
-
- // Shutdown all servers
- var shutdownErrors []error
-
- if err := server.Shutdown(ctx); err != nil {
- shutdownErrors = append(shutdownErrors, fmt.Errorf("main server: %w", err))
- }
-
- if promServer != nil {
- if err := promServer.Shutdown(ctx); err != nil {
- shutdownErrors = append(shutdownErrors, fmt.Errorf("prometheus server: %w", err))
- }
- }
-
- if len(shutdownErrors) > 0 {
- logger.Error("failed to shutdown servers gracefully",
- slog.String("error", errors.Join(shutdownErrors...).Error()),
- )
- os.Exit(1)
- }
-
- logger.Info("server shutdown complete")
-}
-
-// printUsage displays help information
-func printUsage() {
- fmt.Printf("Metald API Server\n\n")
- fmt.Printf("Usage: %s [OPTIONS]\n\n", os.Args[0])
- fmt.Printf("Options:\n")
- flag.PrintDefaults()
- fmt.Printf("\nEnvironment Variables:\n")
- fmt.Printf(" UNKEY_METALD_PORT Server port (default: 8080)\n")
- fmt.Printf(" UNKEY_METALD_ADDRESS Bind address (default: 0.0.0.0)\n")
- fmt.Printf(" UNKEY_METALD_BACKEND Backend type (default: firecracker)\n")
- fmt.Printf("\nOpenTelemetry Configuration:\n")
- fmt.Printf(" UNKEY_METALD_OTEL_ENABLED Enable OpenTelemetry (default: false)\n")
- fmt.Printf(" UNKEY_METALD_OTEL_SERVICE_NAME Service name (default: vmm-controlplane)\n")
- fmt.Printf(" UNKEY_METALD_OTEL_SERVICE_VERSION Service version (default: 0.1.0)\n")
- fmt.Printf(" UNKEY_METALD_OTEL_SAMPLING_RATE Trace sampling rate 0.0-1.0 (default: 1.0)\n")
- fmt.Printf(" UNKEY_METALD_OTEL_ENDPOINT OTLP endpoint (default: localhost:4318)\n")
- fmt.Printf(" UNKEY_METALD_OTEL_PROMETHEUS_ENABLED Enable Prometheus metrics (default: true)\n")
- fmt.Printf(" UNKEY_METALD_OTEL_PROMETHEUS_PORT Prometheus metrics port on 0.0.0.0 (default: 9464)\n")
- fmt.Printf(" UNKEY_METALD_OTEL_HIGH_CARDINALITY_ENABLED Enable high-cardinality labels (default: false)\n")
- fmt.Printf("\nJailer Configuration (Integrated):\n")
- fmt.Printf(" UNKEY_METALD_JAILER_UID User ID for jailer process (default: 1000)\n")
- fmt.Printf(" UNKEY_METALD_JAILER_GID Group ID for jailer process (default: 1000)\n")
- fmt.Printf(" UNKEY_METALD_JAILER_CHROOT_DIR Chroot base directory (default: /srv/jailer)\n")
- fmt.Printf("\nExamples:\n")
- fmt.Printf(" %s # Start metald with default configuration\n", os.Args[0])
- fmt.Printf(" sudo %s # Start metald as root (required for networking)\n", os.Args[0])
-}
-
-// printVersion displays version information
-func printVersion() {
- fmt.Printf("Metald API Server\n")
- fmt.Printf("Version: %s\n", getVersion())
- fmt.Printf("Built with: %s\n", runtime.Version())
-}
diff --git a/go/apps/metald/contrib/grafana-dashboards/README.md b/go/apps/metald/contrib/grafana-dashboards/README.md
deleted file mode 100644
index fddd8cb23b..0000000000
--- a/go/apps/metald/contrib/grafana-dashboards/README.md
+++ /dev/null
@@ -1,226 +0,0 @@
-# Metald Grafana Dashboards
-
-This directory contains pre-built Grafana dashboards for comprehensive metald monitoring.
-
-## Dashboards Overview
-
-### 1. VM Operations Dashboard (`vm-operations.json`)
-- **VM Lifecycle Metrics**: Create, boot, shutdown, delete operations
-- **Success Rates**: Real-time success/failure rates for all operations
-- **Process Management**: Firecracker process creation and management
-- **Jailer Integration**: AWS production jailer operations
-- **Backend Support**: Firecracker and Cloud Hypervisor metrics
-
-**Key Metrics:**
-- `unkey_metald_vm_*_requests_total` - Operation request counts
-- `unkey_metald_vm_*_success_total` - Successful operations
-- `unkey_metald_vm_*_failures_total` - Failed operations
-- `unkey_metald_process_*_total` - Process management metrics
-- `unkey_metald_jailer_*_total` - Jailer operations
-
-**⚠️ Missing Features:**
-- No authentication/authorization failure tracking
-- No customer_id filtering or multi-tenant segmentation
-- No security validation metrics
-
-### 1b. Security & Authentication Operations Dashboard (`security-operations.json`) **[NEW]**
-- **Authentication Failures**: Track auth/authz failures by type and operation
-- **Ownership Validation**: Monitor customer ownership validation failures
-- **Security Alerts**: Real-time security incident monitoring
-- **Jailer Security**: Enhanced jailer operation monitoring
-
-**Key Metrics:**
-- `unkey_metald_vm_*_failures_total{error_type="ownership_validation_failed"}` - Ownership violations
-- `unkey_metald_vm_*_failures_total{error_type="missing_customer_context"}` - Auth context missing
-- `unkey_metald_vm_*_failures_total{error_type="permission_denied"}` - Permission failures
-- `unkey_metald_jailer_*_total` - Enhanced jailer security metrics
-
-### 2. Billing & Metrics Dashboard (`billing-metrics.json`)
-- **Metrics Collection**: Real-time VM metrics collection (100ms precision)
-- **Billing Batches**: Batch transmission to billing service
-- **Collection Performance**: Duration and throughput metrics
-- **Per-VM Analytics**: Individual VM billing breakdown
-- **Heartbeat Monitoring**: Billing service connectivity
-
-**Key Metrics:**
-- `metald_metrics_collected_total` - Metrics collection counts
-- `metald_billing_batches_sent_total` - Billing batch transmission
-- `metald_heartbeat_sent_total` - Heartbeat counts
-- `metald_*_duration_seconds` - Performance metrics
-
-**⚠️ Missing Features:**
-- Customer_id template variable exists but is NOT used in panel queries
-- No customer-level billing breakdowns (only VM-level)
-- No per-customer usage or cost analysis
-
-### 2b. Multi-Tenant Billing & Usage Dashboard (`multi-tenant-billing.json`) **[NEW]**
-- **Customer Segmentation**: Filter and analyze by customer_id
-- **Per-Customer Metrics**: Billing metrics broken down by customer
-- **Customer Usage Patterns**: VM operations and resource usage by customer
-- **Customer Failure Analysis**: Authentication and operation failures by customer
-- **Billing Performance**: Per-customer billing batch processing performance
-
-**Key Metrics:**
-- `metald_billing_batches_sent_total` by customer_id - Customer billing transmission
-- `unkey_metald_vm_*_requests_total` by customer_id - Customer VM operations
-- `metald_metrics_collected_total` by customer_id - Customer metrics collection
-- `unkey_metald_vm_*_failures_total` by customer_id - Customer failure rates
-
-### 3. System Health Dashboard (`system-health.json`)
-- **Service Status**: Overall metald health and uptime
-- **Resource Usage**: CPU, memory, and Go runtime metrics
-- **HTTP Performance**: Request rates and response times
-- **Go Runtime**: GC, goroutines, and memory statistics
-- **Log Analysis**: Error and warning log trends
-
-**Key Metrics:**
-- `up{job="metald"}` - Service availability
-- `process_*` - System resource usage
-- `go_*` - Go runtime statistics
-- `http_*` - HTTP server performance
-
-## Quick Start
-
-### 1. Start the LGTM Stack
-```bash
-# Start Grafana LGTM stack (Loki, Grafana, Tempo, Mimir)
-make o11y
-
-# Verify Grafana is running
-curl http://localhost:3000/api/health
-```
-
-### 2. Import Dashboards
-```bash
-# Automated import
-./scripts/import-dashboards.sh
-
-# Manual import via Grafana UI
-# 1. Open http://localhost:3000 (admin/admin)
-# 2. Go to Dashboards > Import
-# 3. Upload each .json file
-```
-
-### 3. Start Metald with Telemetry
-```bash
-# Enable OpenTelemetry and start metald
-UNKEY_METALD_OTEL_ENABLED=true \
-UNKEY_METALD_OTEL_PROMETHEUS_ENABLED=true \
-./build/metald
-```
-
-### 4. Access Dashboards
-- **Grafana UI**: http://localhost:3000 (admin/admin)
-- **VM Operations**: http://localhost:3000/d/metald-vm-ops
-- **Security Operations**: http://localhost:3000/d/metald-security-ops **[NEW]**
-- **Billing & Metrics**: http://localhost:3000/d/metald-billing
-- **Multi-Tenant Billing**: http://localhost:3000/d/metald-multi-tenant-billing **[NEW]**
-- **System Health**: http://localhost:3000/d/metald-system-health
-
-## Configuration
-
-### Environment Variables
-```bash
-# Required for telemetry
-export UNKEY_METALD_OTEL_ENABLED=true
-export UNKEY_METALD_OTEL_PROMETHEUS_ENABLED=true
-
-# Optional configuration
-export UNKEY_METALD_OTEL_SAMPLING_RATE=1.0
-export UNKEY_METALD_OTEL_ENDPOINT=localhost:4318
-export UNKEY_METALD_OTEL_PROMETHEUS_PORT=9464
-```
-
-### Prometheus Configuration
-The LGTM stack automatically scrapes metrics from metald. For custom Prometheus setup:
-
-```yaml
-# prometheus.yml
-scrape_configs:
- - job_name: 'metald'
- static_configs:
- - targets: ['localhost:9464']
- scrape_interval: 10s
- metrics_path: /metrics
-```
-
-## Dashboard Features
-
-### Variables and Templating
-- **Backend Filter**: Filter by Firecracker/Cloud Hypervisor (vm-operations, security-operations)
-- **Customer ID Filter**: Multi-tenant filtering (multi-tenant-billing, security-operations)
-- **VM ID Filter**: Focus on specific VMs (legacy dashboards)
-
-### New Multi-Tenant Features
-- **Customer Segmentation**: Filter all metrics by customer_id
-- **Security Monitoring**: Authentication and authorization failure tracking
-- **Ownership Validation**: Customer ownership violation alerts
-- **Per-Customer Analytics**: Usage, billing, and performance by customer
-
-### Alerting Ready
-All dashboards include threshold configurations suitable for Grafana alerting:
-- VM operation failure rates > 5%
-- High memory usage > 500MB
-- Billing batch failures
-- Service downtime
-
-### Time Range Controls
-- Default: Last 15 minutes with 5-second refresh
-- Customizable time ranges
-- Real-time monitoring support
-
-## Troubleshooting
-
-### Common Issues
-
-**Dashboard shows "No data":**
-1. Verify metald is running with telemetry enabled
-2. Check Prometheus datasource configuration
-3. Ensure metrics endpoint is accessible: `curl http://localhost:9464/metrics`
-
-**Import script fails:**
-1. Check Grafana is running: `curl http://localhost:3000/api/health`
-2. Verify jq is installed: `sudo apt install jq` (Ubuntu/Debian)
-3. Check Grafana credentials (default: admin/admin)
-
-**Missing metrics:**
-1. Confirm OpenTelemetry is enabled in metald config
-2. Check for backend-specific metrics (Firecracker vs Cloud Hypervisor)
-3. Verify billing service integration for billing metrics
-
-### Manual Verification
-```bash
-# Check service health
-curl http://localhost:8080/_/health
-
-# View raw metrics
-curl http://localhost:9464/metrics | grep unkey_metald
-
-# Test VM operations
-curl -X POST http://localhost:8080/vmprovisioner.v1.VmService/CreateVm \
- -H "Content-Type: application/json" \
- -d '{"config":{"cpu":{"vcpu_count":1},"memory":{"size_bytes":134217728}}}'
-```
-
-## Customization
-
-### Adding Custom Panels
-1. Use Grafana UI to create new panels
-2. Export dashboard JSON
-3. Save to this directory
-4. Update import script if needed
-
-### Metric Queries
-All dashboards use standard PromQL queries. Common patterns:
-- Rate calculations: `rate(metric_total[5m])`
-- Success rates: `rate(success_total[5m]) / rate(requests_total[5m]) * 100`
-- Percentiles: `histogram_quantile(0.95, rate(metric_bucket[5m]))`
-
-### Integration with LGTM Stack
-The dashboards are designed to work seamlessly with the included LGTM stack:
-- **Loki**: Log aggregation and querying
-- **Grafana**: Visualization and dashboards
-- **Tempo**: Distributed tracing
-- **Mimir**: Long-term metrics storage
-
-For production deployments, consider configuring persistent storage and retention policies.
\ No newline at end of file
diff --git a/go/apps/metald/contrib/grafana-dashboards/billing-metrics.json b/go/apps/metald/contrib/grafana-dashboards/billing-metrics.json
deleted file mode 100644
index 24fca8db56..0000000000
--- a/go/apps/metald/contrib/grafana-dashboards/billing-metrics.json
+++ /dev/null
@@ -1,704 +0,0 @@
-{
- "annotations": {
- "list": [
- {
- "builtIn": 1,
- "datasource": {
- "type": "grafana",
- "uid": "-- Grafana --"
- },
- "enable": true,
- "hide": true,
- "iconColor": "rgba(0, 211, 255, 1)",
- "name": "Annotations & Alerts",
- "type": "dashboard"
- }
- ]
- },
- "editable": true,
- "fiscalYearStartMonth": 0,
- "graphTooltip": 0,
- "id": null,
- "links": [],
- "liveNow": false,
- "panels": [
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "short"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 8,
- "x": 0,
- "y": 0
- },
- "id": 1,
- "options": {
- "colorMode": "value",
- "graphMode": "area",
- "justifyMode": "auto",
- "orientation": "auto",
- "reduceOptions": {
- "values": false,
- "calcs": ["lastNotNull"],
- "fields": ""
- },
- "text": {},
- "textMode": "auto"
- },
- "pluginVersion": "9.5.2",
- "targets": [
- {
- "expr": "sum(rate(metald_vm_metrics_requests_total[$__rate_interval]))",
- "format": "time_series",
- "legendFormat": "VM Metrics Requests Rate",
- "refId": "A"
- }
- ],
- "title": "VM Metrics Request Rate",
- "type": "stat"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "yellow",
- "value": 5000
- },
- {
- "color": "red",
- "value": 10000
- }
- ]
- },
- "unit": "reqps"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 8,
- "x": 8,
- "y": 0
- },
- "id": 2,
- "options": {
- "colorMode": "value",
- "graphMode": "area",
- "justifyMode": "auto",
- "orientation": "auto",
- "reduceOptions": {
- "values": false,
- "calcs": ["lastNotNull"],
- "fields": ""
- },
- "text": {},
- "textMode": "auto"
- },
- "pluginVersion": "9.5.2",
- "targets": [
- {
- "expr": "sum(rate(metald_metrics_collected_total[$__rate_interval]))",
- "format": "time_series",
- "legendFormat": "Metrics Collection Rate",
- "refId": "A"
- }
- ],
- "title": "Metrics Collection Rate (per second)",
- "type": "stat"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "yellow",
- "value": 10
- },
- {
- "color": "red",
- "value": 50
- }
- ]
- },
- "unit": "reqps"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 8,
- "x": 16,
- "y": 0
- },
- "id": 3,
- "options": {
- "colorMode": "value",
- "graphMode": "area",
- "justifyMode": "auto",
- "orientation": "auto",
- "reduceOptions": {
- "values": false,
- "calcs": ["lastNotNull"],
- "fields": ""
- },
- "text": {},
- "textMode": "auto"
- },
- "pluginVersion": "9.5.2",
- "targets": [
- {
- "expr": "sum(rate(metald_billing_batches_sent_total[$__rate_interval]))",
- "format": "time_series",
- "legendFormat": "Billing Batches Sent Rate",
- "refId": "A"
- }
- ],
- "title": "Billing Batches Sent Rate (per second)",
- "type": "stat"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "reqps"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 8
- },
- "id": 4,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "rate(metald_metrics_collected_total[$__rate_interval]) by (vm_id)",
- "format": "time_series",
- "legendFormat": "VM {{vm_id}} - Metrics Collected/sec",
- "refId": "A"
- }
- ],
- "title": "Metrics Collection Rate by VM",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "reqps"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 8
- },
- "id": 5,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "rate(metald_billing_batches_sent_total[$__rate_interval]) by (vm_id)",
- "format": "time_series",
- "legendFormat": "VM {{vm_id}} - Billing Batches/sec",
- "refId": "A"
- }
- ],
- "title": "Billing Batch Transmission Rate by VM",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "yellow",
- "value": 10
- },
- {
- "color": "red",
- "value": 30
- }
- ]
- },
- "unit": "reqps"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 16
- },
- "id": 6,
- "options": {
- "colorMode": "value",
- "graphMode": "area",
- "justifyMode": "auto",
- "orientation": "auto",
- "reduceOptions": {
- "values": false,
- "calcs": ["lastNotNull"],
- "fields": ""
- },
- "text": {},
- "textMode": "auto"
- },
- "pluginVersion": "9.5.2",
- "targets": [
- {
- "expr": "rate(metald_heartbeat_sent_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Heartbeat Rate",
- "refId": "A"
- }
- ],
- "title": "Billing Service Heartbeat Rate",
- "type": "stat"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "ms"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 16
- },
- "id": 7,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "histogram_quantile(0.95, rate(metald_billing_batch_send_duration_seconds_bucket[$__rate_interval])) * 1000",
- "format": "time_series",
- "legendFormat": "95th percentile",
- "refId": "A"
- },
- {
- "expr": "histogram_quantile(0.50, rate(metald_billing_batch_send_duration_seconds_bucket[$__rate_interval])) * 1000",
- "format": "time_series",
- "legendFormat": "50th percentile",
- "refId": "B"
- },
- {
- "expr": "rate(metald_billing_batch_send_duration_seconds_sum[$__rate_interval]) / rate(metald_billing_batch_send_duration_seconds_count[$__rate_interval]) * 1000",
- "format": "time_series",
- "legendFormat": "Average",
- "refId": "C"
- }
- ],
- "title": "Billing Batch Send Duration",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "ms"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 24,
- "x": 0,
- "y": 24
- },
- "id": 8,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "histogram_quantile(0.95, rate(metald_metrics_collection_duration_seconds_bucket[$__rate_interval])) * 1000",
- "format": "time_series",
- "legendFormat": "95th percentile - Collection Duration",
- "refId": "A"
- },
- {
- "expr": "histogram_quantile(0.50, rate(metald_metrics_collection_duration_seconds_bucket[$__rate_interval])) * 1000",
- "format": "time_series",
- "legendFormat": "50th percentile - Collection Duration",
- "refId": "B"
- },
- {
- "expr": "rate(metald_metrics_collection_duration_seconds_sum[$__rate_interval]) / rate(metald_metrics_collection_duration_seconds_count[$__rate_interval]) * 1000",
- "format": "time_series",
- "legendFormat": "Average - Collection Duration",
- "refId": "C"
- }
- ],
- "title": "Metrics Collection Duration (100ms precision)",
- "type": "timeseries"
- }
- ],
- "refresh": "5s",
- "schemaVersion": 37,
- "style": "dark",
- "tags": ["metald", "billing", "metrics"],
- "templating": {
- "list": [
- {
- "current": {
- "selected": false,
- "text": "All",
- "value": "$__all"
- },
- "datasource": {
- "type": "prometheus"
- },
- "definition": "label_values(metald_metrics_collected_total, vm_id)",
- "hide": 0,
- "includeAll": true,
- "label": "VM ID",
- "multi": true,
- "name": "vm_id",
- "options": [],
- "query": {
- "query": "label_values(metald_metrics_collected_total, vm_id)",
- "refId": "StandardVariableQuery"
- },
- "refresh": 1,
- "regex": "",
- "skipUrlSync": false,
- "sort": 0,
- "type": "query"
- },
- {
- "current": {
- "selected": false,
- "text": "All",
- "value": "$__all"
- },
- "datasource": {
- "type": "prometheus"
- },
- "definition": "label_values(metald_billing_batches_sent_total, customer_id)",
- "hide": 0,
- "includeAll": true,
- "label": "Customer ID",
- "multi": true,
- "name": "customer_id",
- "options": [],
- "query": {
- "query": "label_values(metald_billing_batches_sent_total, customer_id)",
- "refId": "StandardVariableQuery"
- },
- "refresh": 1,
- "regex": "",
- "skipUrlSync": false,
- "sort": 0,
- "type": "query"
- }
- ]
- },
- "time": {
- "from": "now-15m",
- "to": "now"
- },
- "timepicker": {},
- "timezone": "",
- "title": "Metald Billing & Metrics Dashboard",
- "uid": "metald-billing",
- "version": 1,
- "weekStart": ""
-}
diff --git a/go/apps/metald/contrib/grafana-dashboards/multi-tenant-billing.json b/go/apps/metald/contrib/grafana-dashboards/multi-tenant-billing.json
deleted file mode 100644
index 949d275236..0000000000
--- a/go/apps/metald/contrib/grafana-dashboards/multi-tenant-billing.json
+++ /dev/null
@@ -1,522 +0,0 @@
-{
- "annotations": {
- "list": [
- {
- "builtIn": 1,
- "datasource": {
- "type": "grafana",
- "uid": "-- Grafana --"
- },
- "enable": true,
- "hide": true,
- "iconColor": "rgba(0, 211, 255, 1)",
- "name": "Annotations & Alerts",
- "type": "dashboard"
- }
- ]
- },
- "editable": true,
- "fiscalYearStartMonth": 0,
- "graphTooltip": 0,
- "id": null,
- "links": [],
- "liveNow": false,
- "panels": [
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "custom": {
- "align": "auto",
- "displayMode": "auto",
- "inspect": false
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- }
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 24,
- "x": 0,
- "y": 0
- },
- "id": 1,
- "options": {
- "showHeader": true
- },
- "pluginVersion": "8.5.2",
- "targets": [
- {
- "datasource": {
- "type": "prometheus"
- },
- "editorMode": "code",
- "expr": "sort_desc(sum by (customer_id) (rate(metald_metrics_collected_total{customer_id=~\"$customer_id\"}[$__rate_interval]) * 60))",
- "format": "table",
- "interval": "",
- "legendFormat": "__auto",
- "range": true,
- "refId": "A"
- }
- ],
- "title": "Customer Metrics Collection Rate (per minute)",
- "transformations": [
- {
- "id": "organize",
- "options": {
- "excludeByName": {
- "Time": true
- },
- "indexByName": {},
- "renameByName": {
- "Value": "Metrics/min",
- "customer_id": "Customer ID"
- }
- }
- }
- ],
- "type": "table"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "reqps"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 8
- },
- "id": 2,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "datasource": {
- "type": "prometheus"
- },
- "editorMode": "code",
- "expr": "sum by (customer_id) (rate(metald_billing_batches_sent_total{customer_id=~\"$customer_id\"}[$__rate_interval]))",
- "interval": "",
- "legendFormat": "{{customer_id}}",
- "range": true,
- "refId": "A"
- }
- ],
- "title": "Billing Batch Transmission by Customer",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "normal"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "reqps"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 8
- },
- "id": 3,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "datasource": {
- "type": "prometheus"
- },
- "editorMode": "code",
- "expr": "sum by (customer_id) (rate(unkey_metald_vm_create_requests_total{customer_id=~\"$customer_id\"}[$__rate_interval]))",
- "interval": "",
- "legendFormat": "Create - {{customer_id}}",
- "range": true,
- "refId": "A"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "editorMode": "code",
- "expr": "sum by (customer_id) (rate(unkey_metald_vm_boot_requests_total{customer_id=~\"$customer_id\"}[$__rate_interval]))",
- "interval": "",
- "legendFormat": "Boot - {{customer_id}}",
- "range": true,
- "refId": "B"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "editorMode": "code",
- "expr": "sum by (customer_id) (rate(unkey_metald_vm_delete_requests_total{customer_id=~\"$customer_id\"}[$__rate_interval]))",
- "interval": "",
- "legendFormat": "Delete - {{customer_id}}",
- "range": true,
- "refId": "C"
- }
- ],
- "title": "VM Operations by Customer",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "custom": {
- "align": "auto",
- "displayMode": "auto",
- "inspect": false
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "yellow",
- "value": 5
- },
- {
- "color": "red",
- "value": 10
- }
- ]
- }
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 16
- },
- "id": 4,
- "options": {
- "showHeader": true
- },
- "pluginVersion": "8.5.2",
- "targets": [
- {
- "datasource": {
- "type": "prometheus"
- },
- "editorMode": "code",
- "expr": "sort_desc(sum by (customer_id) (rate(unkey_metald_vm_create_failures_total{customer_id=~\"$customer_id\"}[$__rate_interval]) * 60))",
- "format": "table",
- "interval": "",
- "legendFormat": "__auto",
- "range": true,
- "refId": "A"
- }
- ],
- "title": "Customer Failure Rates (per minute)",
- "transformations": [
- {
- "id": "organize",
- "options": {
- "excludeByName": {
- "Time": true
- },
- "indexByName": {},
- "renameByName": {
- "Value": "Failures/min",
- "customer_id": "Customer ID"
- }
- }
- }
- ],
- "type": "table"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "s"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 16
- },
- "id": 5,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "datasource": {
- "type": "prometheus"
- },
- "editorMode": "code",
- "expr": "histogram_quantile(0.95, sum by (customer_id, le) (rate(metald_billing_batch_send_duration_seconds_bucket{customer_id=~\"$customer_id\"}[$__rate_interval])))",
- "interval": "",
- "legendFormat": "95th percentile - {{customer_id}}",
- "range": true,
- "refId": "A"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "editorMode": "code",
- "expr": "histogram_quantile(0.50, sum by (customer_id, le) (rate(metald_billing_batch_send_duration_seconds_bucket{customer_id=~\"$customer_id\"}[$__rate_interval])))",
- "interval": "",
- "legendFormat": "50th percentile - {{customer_id}}",
- "range": true,
- "refId": "B"
- }
- ],
- "title": "Billing Duration by Customer",
- "type": "timeseries"
- }
- ],
- "refresh": "5s",
- "schemaVersion": 36,
- "style": "dark",
- "tags": ["metald", "billing", "multi-tenant", "customer"],
- "templating": {
- "list": [
- {
- "current": {
- "selected": false,
- "text": "All",
- "value": "$__all"
- },
- "datasource": {
- "type": "prometheus"
- },
- "definition": "label_values(metald_billing_batches_sent_total, customer_id)",
- "hide": 0,
- "includeAll": true,
- "label": "Customer ID",
- "multi": true,
- "name": "customer_id",
- "options": [],
- "query": {
- "query": "label_values(metald_billing_batches_sent_total, customer_id)",
- "refId": "StandardVariableQuery"
- },
- "refresh": 1,
- "regex": "",
- "skipUrlSync": false,
- "sort": 0,
- "type": "query"
- }
- ]
- },
- "time": {
- "from": "now-15m",
- "to": "now"
- },
- "timepicker": {},
- "timezone": "",
- "title": "Metald Multi-Tenant Billing & Usage",
- "uid": "metald-multi-tenant-billing",
- "version": 1,
- "weekStart": ""
-}
diff --git a/go/apps/metald/contrib/grafana-dashboards/security-operations.json b/go/apps/metald/contrib/grafana-dashboards/security-operations.json
deleted file mode 100644
index a79211135a..0000000000
--- a/go/apps/metald/contrib/grafana-dashboards/security-operations.json
+++ /dev/null
@@ -1,563 +0,0 @@
-{
- "annotations": {
- "list": [
- {
- "builtIn": 1,
- "datasource": {
- "type": "grafana",
- "uid": "-- Grafana --"
- },
- "enable": true,
- "hide": true,
- "iconColor": "rgba(0, 211, 255, 1)",
- "name": "Annotations & Alerts",
- "type": "dashboard"
- }
- ]
- },
- "editable": true,
- "fiscalYearStartMonth": 0,
- "graphTooltip": 0,
- "id": null,
- "links": [],
- "liveNow": false,
- "panels": [
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "percent"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 0
- },
- "id": 1,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "datasource": {
- "type": "prometheus"
- },
- "editorMode": "code",
- "expr": "(\n rate(unkey_metald_vm_create_success_total{backend=~\"$backend\"}[$__rate_interval]) /\n (rate(unkey_metald_vm_create_success_total{backend=~\"$backend\"}[$__rate_interval]) + rate(unkey_metald_vm_create_failures_total{backend=~\"$backend\"}[$__rate_interval]))\n) * 100",
- "interval": "",
- "legendFormat": "VM Create Success Rate",
- "range": true,
- "refId": "A"
- }
- ],
- "title": "VM Operation Success Rate",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "custom": {
- "align": "auto",
- "displayMode": "auto",
- "inspect": false
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "yellow",
- "value": 1
- },
- {
- "color": "red",
- "value": 10
- }
- ]
- }
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 0
- },
- "id": 2,
- "options": {
- "showHeader": true
- },
- "pluginVersion": "8.5.2",
- "targets": [
- {
- "datasource": {
- "type": "prometheus"
- },
- "editorMode": "code",
- "expr": "sort_desc(sum by (error_type) (rate(unkey_metald_vm_create_failures_total{backend=~\"$backend\"}[$__rate_interval]) * 60))",
- "format": "table",
- "interval": "",
- "legendFormat": "__auto",
- "range": true,
- "refId": "A"
- }
- ],
- "title": "Authentication & Authorization Failures by Type",
- "transformations": [
- {
- "id": "organize",
- "options": {
- "excludeByName": {
- "Time": true
- },
- "indexByName": {},
- "renameByName": {
- "Value": "Failures/min",
- "error_type": "Failure Type"
- }
- }
- }
- ],
- "type": "table"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "reqps"
- },
- "overrides": [
- {
- "matcher": {
- "id": "byRegexp",
- "options": "/ownership_validation_failed/"
- },
- "properties": [
- {
- "id": "color",
- "value": {
- "mode": "fixed",
- "fixedColor": "red"
- }
- }
- ]
- },
- {
- "matcher": {
- "id": "byRegexp",
- "options": "/missing_customer_context/"
- },
- "properties": [
- {
- "id": "color",
- "value": {
- "mode": "fixed",
- "fixedColor": "orange"
- }
- }
- ]
- }
- ]
- },
- "gridPos": {
- "h": 8,
- "w": 24,
- "x": 0,
- "y": 8
- },
- "id": 3,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "datasource": {
- "type": "prometheus"
- },
- "editorMode": "code",
- "expr": "sum by (error_type) (rate(unkey_metald_vm_create_failures_total{backend=~\"$backend\", error_type=~\"ownership_validation_failed|missing_customer_context|permission_denied|authentication_failed\"}[$__rate_interval]))",
- "interval": "",
- "legendFormat": "{{error_type}}",
- "range": true,
- "refId": "A"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "editorMode": "code",
- "expr": "sum by (error_type) (rate(unkey_metald_vm_boot_failures_total{backend=~\"$backend\", error_type=~\"ownership_validation_failed\"}[$__rate_interval]))",
- "interval": "",
- "legendFormat": "Boot: {{error_type}}",
- "range": true,
- "refId": "B"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "editorMode": "code",
- "expr": "sum by (error_type) (rate(unkey_metald_vm_delete_failures_total{backend=~\"$backend\", error_type=~\"ownership_validation_failed\"}[$__rate_interval]))",
- "interval": "",
- "legendFormat": "Delete: {{error_type}}",
- "range": true,
- "refId": "C"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "editorMode": "code",
- "expr": "sum by (error_type) (rate(unkey_metald_vm_shutdown_failures_total{backend=~\"$backend\", error_type=~\"ownership_validation_failed\"}[$__rate_interval]))",
- "interval": "",
- "legendFormat": "Shutdown: {{error_type}}",
- "range": true,
- "refId": "D"
- }
- ],
- "title": "Authentication & Security Failures by Operation",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "reqps"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 16
- },
- "id": 4,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "datasource": {
- "type": "prometheus"
- },
- "editorMode": "code",
- "expr": "sum by (backend) (rate(unkey_metald_jailer_start_requests_total{backend=~\"$backend\"}[$__rate_interval]))",
- "interval": "",
- "legendFormat": "Jailer Start Requests - {{backend}}",
- "range": true,
- "refId": "A"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "editorMode": "code",
- "expr": "sum by (backend) (rate(unkey_metald_jailer_start_success_total{backend=~\"$backend\"}[$__rate_interval]))",
- "interval": "",
- "legendFormat": "Jailer Start Success - {{backend}}",
- "range": true,
- "refId": "B"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "editorMode": "code",
- "expr": "sum by (backend) (rate(unkey_metald_jailer_start_failures_total{backend=~\"$backend\"}[$__rate_interval]))",
- "interval": "",
- "legendFormat": "Jailer Start Failures - {{backend}}",
- "range": true,
- "refId": "C"
- }
- ],
- "title": "Jailer Security Operations",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 1
- }
- ]
- }
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 16
- },
- "id": 5,
- "options": {
- "colorMode": "value",
- "graphMode": "area",
- "justifyMode": "auto",
- "orientation": "auto",
- "reduceOptions": {
- "calcs": ["lastNotNull"],
- "fields": "",
- "values": false
- },
- "textMode": "auto"
- },
- "pluginVersion": "8.5.2",
- "targets": [
- {
- "datasource": {
- "type": "prometheus"
- },
- "editorMode": "code",
- "expr": "sum(rate(unkey_metald_vm_create_failures_total{backend=~\"$backend\", error_type=~\"ownership_validation_failed|missing_customer_context|permission_denied|authentication_failed\"}[$__rate_interval]) * 60)",
- "interval": "",
- "legendFormat": "Auth Failures/min",
- "range": true,
- "refId": "A"
- }
- ],
- "title": "Security Alerts - Authentication Failures",
- "type": "stat"
- }
- ],
- "refresh": "5s",
- "schemaVersion": 36,
- "style": "dark",
- "tags": ["metald", "security", "authentication", "authorization", "multi-tenant"],
- "templating": {
- "list": [
- {
- "current": {
- "selected": false,
- "text": "All",
- "value": "$__all"
- },
- "datasource": {
- "type": "prometheus"
- },
- "definition": "label_values(unkey_metald_vm_create_requests_total, backend)",
- "hide": 0,
- "includeAll": true,
- "label": "Backend",
- "multi": false,
- "name": "backend",
- "options": [],
- "query": {
- "query": "label_values(unkey_metald_vm_create_requests_total, backend)",
- "refId": "StandardVariableQuery"
- },
- "refresh": 1,
- "regex": "",
- "skipUrlSync": false,
- "sort": 0,
- "type": "query"
- }
- ]
- },
- "time": {
- "from": "now-15m",
- "to": "now"
- },
- "timepicker": {},
- "timezone": "",
- "title": "Metald Security & Authentication Operations",
- "uid": "metald-security-ops",
- "version": 1,
- "weekStart": ""
-}
diff --git a/go/apps/metald/contrib/grafana-dashboards/system-health.json b/go/apps/metald/contrib/grafana-dashboards/system-health.json
deleted file mode 100644
index 0b3e94c29e..0000000000
--- a/go/apps/metald/contrib/grafana-dashboards/system-health.json
+++ /dev/null
@@ -1,803 +0,0 @@
-{
- "annotations": {
- "list": [
- {
- "builtIn": 1,
- "datasource": {
- "type": "grafana",
- "uid": "-- Grafana --"
- },
- "enable": true,
- "hide": true,
- "iconColor": "rgba(0, 211, 255, 1)",
- "name": "Annotations & Alerts",
- "type": "dashboard"
- }
- ]
- },
- "editable": true,
- "fiscalYearStartMonth": 0,
- "graphTooltip": 0,
- "id": null,
- "links": [],
- "liveNow": false,
- "panels": [
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "mappings": [
- {
- "options": {
- "0": {
- "color": "red",
- "index": 1,
- "text": "DOWN"
- },
- "1": {
- "color": "green",
- "index": 0,
- "text": "UP"
- }
- },
- "type": "value"
- }
- ],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 0.5
- }
- ]
- },
- "unit": "short"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 6,
- "x": 0,
- "y": 0
- },
- "id": 1,
- "options": {
- "colorMode": "background",
- "graphMode": "none",
- "justifyMode": "center",
- "orientation": "auto",
- "reduceOptions": {
- "values": false,
- "calcs": ["lastNotNull"],
- "fields": ""
- },
- "text": {},
- "textMode": "auto"
- },
- "pluginVersion": "9.5.2",
- "targets": [
- {
- "expr": "up{job=\"metald\"}",
- "format": "time_series",
- "legendFormat": "Metald Service",
- "refId": "A"
- }
- ],
- "title": "Service Status",
- "type": "stat"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "yellow",
- "value": 70
- },
- {
- "color": "red",
- "value": 90
- }
- ]
- },
- "unit": "percent"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 6,
- "x": 6,
- "y": 0
- },
- "id": 2,
- "options": {
- "colorMode": "value",
- "graphMode": "area",
- "justifyMode": "auto",
- "orientation": "auto",
- "reduceOptions": {
- "values": false,
- "calcs": ["lastNotNull"],
- "fields": ""
- },
- "text": {},
- "textMode": "auto"
- },
- "pluginVersion": "9.5.2",
- "targets": [
- {
- "expr": "rate(process_cpu_seconds_total{job=\"metald\"}[$__rate_interval]) * 100",
- "format": "time_series",
- "legendFormat": "CPU Usage",
- "refId": "A"
- }
- ],
- "title": "CPU Usage",
- "type": "stat"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "yellow",
- "value": 500000000
- },
- {
- "color": "red",
- "value": 1000000000
- }
- ]
- },
- "unit": "bytes"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 6,
- "x": 12,
- "y": 0
- },
- "id": 3,
- "options": {
- "colorMode": "value",
- "graphMode": "area",
- "justifyMode": "auto",
- "orientation": "auto",
- "reduceOptions": {
- "values": false,
- "calcs": ["lastNotNull"],
- "fields": ""
- },
- "text": {},
- "textMode": "auto"
- },
- "pluginVersion": "9.5.2",
- "targets": [
- {
- "expr": "process_resident_memory_bytes{job=\"metald\"}",
- "format": "time_series",
- "legendFormat": "Memory Usage",
- "refId": "A"
- }
- ],
- "title": "Memory Usage",
- "type": "stat"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "yellow",
- "value": 50
- },
- {
- "color": "red",
- "value": 100
- }
- ]
- },
- "unit": "short"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 6,
- "x": 18,
- "y": 0
- },
- "id": 4,
- "options": {
- "colorMode": "value",
- "graphMode": "area",
- "justifyMode": "auto",
- "orientation": "auto",
- "reduceOptions": {
- "values": false,
- "calcs": ["lastNotNull"],
- "fields": ""
- },
- "text": {},
- "textMode": "auto"
- },
- "pluginVersion": "9.5.2",
- "targets": [
- {
- "expr": "go_goroutines{job=\"metald\"}",
- "format": "time_series",
- "legendFormat": "Active Goroutines",
- "refId": "A"
- }
- ],
- "title": "Active Goroutines",
- "type": "stat"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "reqps"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 8
- },
- "id": 5,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "rate(promhttp_metric_handler_requests_total{job=\"metald\"}[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Metrics Endpoint Requests - {{code}}",
- "refId": "A"
- },
- {
- "expr": "rate(http_requests_total{job=\"metald\", endpoint=\"/_/health\"}[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Health Endpoint Requests - {{code}}",
- "refId": "B"
- }
- ],
- "title": "HTTP Requests per Second",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "ms"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 8
- },
- "id": 6,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "histogram_quantile(0.95, rate(http_request_duration_seconds_bucket{job=\"metald\"}[$__rate_interval])) * 1000",
- "format": "time_series",
- "legendFormat": "95th percentile",
- "refId": "A"
- },
- {
- "expr": "histogram_quantile(0.50, rate(http_request_duration_seconds_bucket{job=\"metald\"}[$__rate_interval])) * 1000",
- "format": "time_series",
- "legendFormat": "50th percentile",
- "refId": "B"
- },
- {
- "expr": "rate(http_request_duration_seconds_sum{job=\"metald\"}[$__rate_interval]) / rate(http_request_duration_seconds_count{job=\"metald\"}[$__rate_interval]) * 1000",
- "format": "time_series",
- "legendFormat": "Average",
- "refId": "C"
- }
- ],
- "title": "HTTP Request Duration",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "bytes"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 16
- },
- "id": 7,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "go_memstats_alloc_bytes{job=\"metald\"}",
- "format": "time_series",
- "legendFormat": "Allocated Memory",
- "refId": "A"
- },
- {
- "expr": "go_memstats_sys_bytes{job=\"metald\"}",
- "format": "time_series",
- "legendFormat": "System Memory",
- "refId": "B"
- },
- {
- "expr": "go_memstats_heap_alloc_bytes{job=\"metald\"}",
- "format": "time_series",
- "legendFormat": "Heap Allocated",
- "refId": "C"
- },
- {
- "expr": "go_memstats_heap_inuse_bytes{job=\"metald\"}",
- "format": "time_series",
- "legendFormat": "Heap In Use",
- "refId": "D"
- }
- ],
- "title": "Go Memory Statistics",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "short"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 16
- },
- "id": 8,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "rate(go_gc_duration_seconds_count{job=\"metald\"}[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "GC Runs per Second",
- "refId": "A"
- },
- {
- "expr": "go_memstats_gc_cpu_fraction{job=\"metald\"}",
- "format": "time_series",
- "legendFormat": "GC CPU Fraction",
- "refId": "B"
- },
- {
- "expr": "go_goroutines{job=\"metald\"}",
- "format": "time_series",
- "legendFormat": "Active Goroutines",
- "refId": "C"
- }
- ],
- "title": "Go Runtime Statistics",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "short"
- },
- "overrides": [
- {
- "matcher": {
- "id": "byName",
- "options": "Errors"
- },
- "properties": [
- {
- "id": "color",
- "value": {
- "mode": "fixed",
- "fixedColor": "red"
- }
- }
- ]
- }
- ]
- },
- "gridPos": {
- "h": 8,
- "w": 24,
- "x": 0,
- "y": 24
- },
- "id": 9,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "increase(log_entries_total{job=\"metald\", level=\"error\"}[5m])",
- "format": "time_series",
- "legendFormat": "Error Logs (5m)",
- "refId": "A"
- },
- {
- "expr": "increase(log_entries_total{job=\"metald\", level=\"warn\"}[5m])",
- "format": "time_series",
- "legendFormat": "Warning Logs (5m)",
- "refId": "B"
- },
- {
- "expr": "increase(log_entries_total{job=\"metald\", level=\"info\"}[5m])",
- "format": "time_series",
- "legendFormat": "Info Logs (5m)",
- "refId": "C"
- }
- ],
- "title": "Log Levels Over Time",
- "type": "timeseries"
- }
- ],
- "refresh": "5s",
- "schemaVersion": 37,
- "style": "dark",
- "tags": ["metald", "system", "health", "go"],
- "templating": {
- "list": []
- },
- "time": {
- "from": "now-15m",
- "to": "now"
- },
- "timepicker": {},
- "timezone": "",
- "title": "Metald System Health Dashboard",
- "uid": "metald-system-health",
- "version": 1,
- "weekStart": ""
-}
diff --git a/go/apps/metald/contrib/grafana-dashboards/vm-operations.json b/go/apps/metald/contrib/grafana-dashboards/vm-operations.json
deleted file mode 100644
index d8b4f275be..0000000000
--- a/go/apps/metald/contrib/grafana-dashboards/vm-operations.json
+++ /dev/null
@@ -1,686 +0,0 @@
-{
- "annotations": {
- "list": [
- {
- "builtIn": 1,
- "datasource": {
- "type": "grafana",
- "uid": "-- Grafana --"
- },
- "enable": true,
- "hide": true,
- "iconColor": "rgba(0, 211, 255, 1)",
- "name": "Annotations & Alerts",
- "type": "dashboard"
- }
- ]
- },
- "editable": true,
- "fiscalYearStartMonth": 0,
- "graphTooltip": 0,
- "id": null,
- "links": [],
- "liveNow": false,
- "panels": [
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "reqps"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 0
- },
- "id": 1,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "rate(unkey_metald_vm_create_requests_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Create Requests",
- "refId": "A"
- },
- {
- "expr": "rate(unkey_metald_vm_boot_requests_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Boot Requests",
- "refId": "B"
- },
- {
- "expr": "rate(unkey_metald_vm_shutdown_requests_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Shutdown Requests",
- "refId": "C"
- },
- {
- "expr": "rate(unkey_metald_vm_delete_requests_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Delete Requests",
- "refId": "D"
- }
- ],
- "title": "VM Lifecycle Requests per Second",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "yellow",
- "value": 90
- },
- {
- "color": "red",
- "value": 95
- }
- ]
- },
- "unit": "percent"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 0
- },
- "id": 2,
- "options": {
- "orientation": "auto",
- "reduceOptions": {
- "values": false,
- "calcs": ["lastNotNull"],
- "fields": ""
- },
- "showThresholdLabels": false,
- "showThresholdMarkers": true
- },
- "pluginVersion": "9.5.2",
- "targets": [
- {
- "expr": "rate(unkey_metald_vm_create_success_total[$__rate_interval]) / rate(unkey_metald_vm_create_requests_total[$__rate_interval]) * 100",
- "format": "time_series",
- "legendFormat": "Create Success Rate",
- "refId": "A"
- },
- {
- "expr": "rate(unkey_metald_vm_boot_success_total[$__rate_interval]) / rate(unkey_metald_vm_boot_requests_total[$__rate_interval]) * 100",
- "format": "time_series",
- "legendFormat": "Boot Success Rate",
- "refId": "B"
- },
- {
- "expr": "rate(unkey_metald_vm_shutdown_success_total[$__rate_interval]) / rate(unkey_metald_vm_shutdown_requests_total[$__rate_interval]) * 100",
- "format": "time_series",
- "legendFormat": "Shutdown Success Rate",
- "refId": "C"
- },
- {
- "expr": "rate(unkey_metald_vm_delete_success_total[$__rate_interval]) / rate(unkey_metald_vm_delete_requests_total[$__rate_interval]) * 100",
- "format": "time_series",
- "legendFormat": "Delete Success Rate",
- "refId": "D"
- }
- ],
- "title": "VM Operation Success Rates",
- "type": "gauge"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "short"
- },
- "overrides": [
- {
- "matcher": {
- "id": "byName",
- "options": "Failures"
- },
- "properties": [
- {
- "id": "color",
- "value": {
- "mode": "fixed",
- "fixedColor": "red"
- }
- }
- ]
- }
- ]
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 8
- },
- "id": 3,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "rate(unkey_metald_vm_create_failures_total[$__rate_interval]) + rate(unkey_metald_vm_boot_failures_total[$__rate_interval]) + rate(unkey_metald_vm_shutdown_failures_total[$__rate_interval]) + rate(unkey_metald_vm_delete_failures_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Total Failures/sec",
- "refId": "A"
- },
- {
- "expr": "rate(unkey_metald_vm_create_failures_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Create Failures/sec",
- "refId": "B"
- },
- {
- "expr": "rate(unkey_metald_vm_boot_failures_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Boot Failures/sec",
- "refId": "C"
- },
- {
- "expr": "rate(unkey_metald_vm_shutdown_failures_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Shutdown Failures/sec",
- "refId": "D"
- },
- {
- "expr": "rate(unkey_metald_vm_delete_failures_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Delete Failures/sec",
- "refId": "E"
- }
- ],
- "title": "VM Operation Failures per Second",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "reqps"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 8
- },
- "id": 4,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "rate(unkey_metald_vm_pause_requests_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Pause Requests",
- "refId": "A"
- },
- {
- "expr": "rate(unkey_metald_vm_resume_requests_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Resume Requests",
- "refId": "B"
- },
- {
- "expr": "rate(unkey_metald_vm_reboot_requests_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Reboot Requests",
- "refId": "C"
- },
- {
- "expr": "rate(unkey_metald_vm_info_requests_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Info Requests",
- "refId": "D"
- },
- {
- "expr": "rate(unkey_metald_vm_list_requests_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "List Requests",
- "refId": "E"
- }
- ],
- "title": "VM State Operations per Second",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "short"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 24,
- "x": 0,
- "y": 16
- },
- "id": 5,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "rate(unkey_metald_process_create_requests_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Process Create Requests",
- "refId": "A"
- },
- {
- "expr": "rate(unkey_metald_process_create_success_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Process Create Success",
- "refId": "B"
- },
- {
- "expr": "rate(unkey_metald_process_create_failures_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Process Create Failures",
- "refId": "C"
- },
- {
- "expr": "rate(unkey_metald_process_terminations_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Process Terminations",
- "refId": "D"
- },
- {
- "expr": "rate(unkey_metald_process_cleanups_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Process Cleanups",
- "refId": "E"
- }
- ],
- "title": "Process Management Operations",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 10,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "vis": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": false,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "short"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 24,
- "x": 0,
- "y": 24
- },
- "id": 6,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "targets": [
- {
- "expr": "rate(unkey_metald_jailer_start_requests_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Jailer Start Requests",
- "refId": "A"
- },
- {
- "expr": "rate(unkey_metald_jailer_start_success_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Jailer Start Success",
- "refId": "B"
- },
- {
- "expr": "rate(unkey_metald_jailer_start_failures_total[$__rate_interval])",
- "format": "time_series",
- "legendFormat": "Jailer Start Failures",
- "refId": "C"
- }
- ],
- "title": "Jailer Operations (AWS Production)",
- "type": "timeseries"
- }
- ],
- "refresh": "5s",
- "schemaVersion": 37,
- "style": "dark",
- "tags": ["metald", "vm", "operations"],
- "templating": {
- "list": [
- {
- "current": {
- "selected": false,
- "text": "All",
- "value": "$__all"
- },
- "datasource": {
- "type": "prometheus"
- },
- "definition": "label_values(unkey_metald_vm_create_requests_total, backend)",
- "hide": 0,
- "includeAll": true,
- "label": "Backend",
- "multi": false,
- "name": "backend",
- "options": [],
- "query": {
- "query": "label_values(unkey_metald_vm_create_requests_total, backend)",
- "refId": "StandardVariableQuery"
- },
- "refresh": 1,
- "regex": "",
- "skipUrlSync": false,
- "sort": 0,
- "type": "query"
- }
- ]
- },
- "time": {
- "from": "now-15m",
- "to": "now"
- },
- "timepicker": {},
- "timezone": "",
- "title": "Metald VM Operations Dashboard",
- "uid": "metald-vm-ops",
- "version": 1,
- "weekStart": ""
-}
diff --git a/go/apps/metald/contrib/systemd/metald.service b/go/apps/metald/contrib/systemd/metald.service
deleted file mode 100644
index 7191cc0967..0000000000
--- a/go/apps/metald/contrib/systemd/metald.service
+++ /dev/null
@@ -1,68 +0,0 @@
-[Unit]
-Description=Metald VM Management Service
-Documentation=https://github.com/unkeyed/unkey/go/deploy/metald
-After=network.target spire-agent.service assetmanagerd.service
-Wants=network.target
-Requires=spire-agent.service assetmanagerd.service
-
-[Service]
-Type=simple
-User=root
-Group=root
-ExecStartPre=/usr/bin/mkdir -p /opt/metald/sockets
-ExecStartPre=/usr/bin/mkdir -p /opt/metald/logs
-ExecStartPre=/usr/bin/mkdir -p /opt/metald/assets
-ExecStartPre=/usr/bin/mkdir -p /srv/jailer
-ExecStartPre=/usr/bin/mkdir -p /var/log/metald
-ExecStartPre=/usr/bin/mkdir -p /opt/vm-assets
-ExecStart=/usr/local/bin/metald
-Restart=always
-RestartSec=5
-StandardOutput=journal
-StandardError=journal
-SyslogIdentifier=metald
-
-# Core service configuration
-Environment=UNKEY_METALD_BACKEND=firecracker
-Environment=UNKEY_METALD_OTEL_ENABLED=true
-Environment=UNKEY_METALD_PORT=8080
-Environment=UNKEY_METALD_ADDRESS=0.0.0.0
-Environment=UNKEY_METALD_OTEL_SERVICE_NAME=metald
-Environment=UNKEY_METALD_OTEL_ENDPOINT=localhost:4318
-Environment=UNKEY_METALD_OTEL_PROMETHEUS_PORT=9464
-
-# Process Manager Configuration
-Environment=UNKEY_METALD_SOCKET_DIR=/opt/metald/sockets
-Environment=UNKEY_METALD_LOG_DIR=/opt/metald/logs
-Environment=UNKEY_METALD_MAX_PROCESSES=1000
-
-# Billing Configuration
-Environment=UNKEY_METALD_BILLING_ENABLED=true
-Environment=UNKEY_METALD_BILLING_ENDPOINT=https://localhost:8081
-Environment=UNKEY_METALD_BILLING_MOCK_MODE=false
-
-# AssetManager Configuration
-Environment=UNKEY_METALD_ASSETMANAGER_ENABLED=true
-Environment=UNKEY_METALD_ASSETMANAGER_ENDPOINT=https://localhost:8083
-Environment=UNKEY_METALD_ASSETMANAGER_CACHE_DIR=/opt/metald/assets
-
-# Jailer configuration (Integrated)
-# AIDEV-BUSINESS_RULE: Integrated jailer provides better control and security
-Environment=UNKEY_METALD_JAILER_UID=977
-Environment=UNKEY_METALD_JAILER_GID=976
-Environment=UNKEY_METALD_JAILER_CHROOT_DIR=/srv/jailer
-
-# TLS/SPIFFE configuration (REQUIRED)
-# AIDEV-BUSINESS_RULE: mTLS is required for secure inter-service communication
-Environment=UNKEY_METALD_TLS_MODE=spiffe
-Environment=UNKEY_METALD_SPIFFE_SOCKET=/var/lib/spire/agent/agent.sock
-
-# Network bridge configuration
-# AIDEV-BUSINESS_RULE: Metald reads bridge count from environment to know available bridges
-
-# Resource limits
-LimitNOFILE=65536
-LimitNPROC=4096
-
-[Install]
-WantedBy=multi-user.target
diff --git a/go/apps/metald/internal/assetmanager/client.go b/go/apps/metald/internal/assetmanager/client.go
deleted file mode 100644
index 6b26eba19c..0000000000
--- a/go/apps/metald/internal/assetmanager/client.go
+++ /dev/null
@@ -1,406 +0,0 @@
-package assetmanager
-
-import (
- "context"
- "errors"
- "fmt"
- "log/slog"
- "net/http"
- "time"
-
- "connectrpc.com/connect"
- "github.com/unkeyed/unkey/go/apps/metald/internal/config"
- "github.com/unkeyed/unkey/go/apps/metald/internal/observability"
- "github.com/unkeyed/unkey/go/deploy/pkg/observability/interceptors"
- assetv1 "github.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1"
- "github.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1/assetmanagerdv1connect"
- "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
-)
-
-// Client provides access to assetmanagerd services
-type Client interface {
- // ListAssets returns available assets with optional filtering
- ListAssets(ctx context.Context, assetType assetv1.AssetType, labels map[string]string) ([]*assetv1.Asset, error)
-
- // QueryAssets returns available assets with automatic build triggering if not found
- QueryAssets(ctx context.Context, assetType assetv1.AssetType, labels map[string]string, buildOptions *assetv1.BuildOptions) (*assetv1.QueryAssetsResponse, error)
-
- // PrepareAssets stages assets for a specific VM in the target path
- PrepareAssets(ctx context.Context, assetIDs []string, targetPath string, vmID string) (map[string]string, error)
-
- // AcquireAsset marks an asset as in-use by a VM
- AcquireAsset(ctx context.Context, assetID string, vmID string) (string, error)
-
- // ReleaseAsset releases an asset reference
- ReleaseAsset(ctx context.Context, leaseID string) error
-}
-
-// client implements the Client interface
-type client struct {
- assetClient assetmanagerdv1connect.AssetManagerServiceClient
- logger *slog.Logger
-}
-
-// NewClient creates a new assetmanagerd client
-func NewClient(cfg *config.AssetManagerConfig, logger *slog.Logger) (Client, error) {
- if !cfg.Enabled {
- return &noopClient{}, nil
- }
-
- // Create HTTP client with timeouts and OpenTelemetry instrumentation
- httpClient := &http.Client{
- Timeout: 30 * time.Second,
- Transport: otelhttp.NewTransport(http.DefaultTransport),
- }
-
- // Create Connect client with default client interceptors plus custom logging
- // AIDEV-NOTE: Using shared client interceptors for consistency across services
- clientInterceptors := interceptors.NewDefaultClientInterceptors("metald", logger)
- // Add custom logging and debug interceptors
- clientInterceptors = append(clientInterceptors,
- loggingInterceptor(logger),
- observability.DebugInterceptor(logger, "assetmanager"),
- )
-
- // Convert UnaryInterceptorFunc to Interceptor
- var interceptorList []connect.Interceptor
- for _, interceptor := range clientInterceptors {
- interceptorList = append(interceptorList, connect.Interceptor(interceptor))
- }
-
- assetClient := assetmanagerdv1connect.NewAssetManagerServiceClient(
- httpClient,
- cfg.Endpoint,
- connect.WithInterceptors(interceptorList...),
- )
-
- return &client{
- assetClient: assetClient,
- logger: logger.With(slog.String("component", "assetmanager-client")),
- }, nil
-}
-
-// NewClientWithHTTP creates a new assetmanagerd client with a custom HTTP client (for TLS)
-func NewClientWithHTTP(cfg *config.AssetManagerConfig, logger *slog.Logger, httpClient *http.Client) (Client, error) {
- if !cfg.Enabled {
- return &noopClient{}, nil
- }
-
- // Use provided HTTP client which may have TLS configuration
- // AIDEV-NOTE: Using shared client interceptors for consistency across services
- clientInterceptors := interceptors.NewDefaultClientInterceptors("metald", logger)
- // Add custom logging and debug interceptors
- clientInterceptors = append(clientInterceptors,
- loggingInterceptor(logger),
- observability.DebugInterceptor(logger, "assetmanager"),
- )
-
- // Convert UnaryInterceptorFunc to Interceptor
- var interceptorList []connect.Interceptor
- for _, interceptor := range clientInterceptors {
- interceptorList = append(interceptorList, connect.Interceptor(interceptor))
- }
-
- assetClient := assetmanagerdv1connect.NewAssetManagerServiceClient(
- httpClient,
- cfg.Endpoint,
- connect.WithInterceptors(interceptorList...),
- )
-
- return &client{
- assetClient: assetClient,
- logger: logger.With(slog.String("component", "assetmanager-client")),
- }, nil
-}
-
-// ListAssets returns available assets with optional filtering
-func (c *client) ListAssets(ctx context.Context, assetType assetv1.AssetType, labels map[string]string) ([]*assetv1.Asset, error) {
- // AIDEV-NOTE: Pagination is not implemented in this initial version
- // For production use, implement pagination handling based on expected asset counts
-
- //exhaustruct:ignore
- req := &assetv1.ListAssetsRequest{
- Type: assetType,
- Status: assetv1.AssetStatus_ASSET_STATUS_AVAILABLE,
- LabelSelector: labels,
- PageSize: 1000, // Reasonable default for initial implementation
- }
-
- resp, err := c.assetClient.ListAssets(ctx, connect.NewRequest(req))
- if err != nil {
- // AIDEV-NOTE: Enhanced debug logging for connection errors
- var connectErr *connect.Error
- if errors.As(err, &connectErr) {
- c.logger.LogAttrs(ctx, slog.LevelError, "assetmanager connection error",
- slog.String("error", err.Error()),
- slog.String("code", connectErr.Code().String()),
- slog.String("message", connectErr.Message()),
- slog.String("asset_type", assetType.String()),
- slog.String("operation", "ListAssets"),
- )
- } else {
- c.logger.LogAttrs(ctx, slog.LevelError, "failed to list assets",
- slog.String("error", err.Error()),
- slog.String("asset_type", assetType.String()),
- slog.String("operation", "ListAssets"),
- )
- }
- return nil, fmt.Errorf("failed to list assets: %w", err)
- }
-
- c.logger.LogAttrs(ctx, slog.LevelDebug, "listed assets",
- slog.Int("count", len(resp.Msg.GetAssets())),
- slog.String("asset_type", assetType.String()),
- )
-
- return resp.Msg.GetAssets(), nil
-}
-
-// QueryAssets returns available assets with automatic build triggering if not found
-func (c *client) QueryAssets(ctx context.Context, assetType assetv1.AssetType, labels map[string]string, buildOptions *assetv1.BuildOptions) (*assetv1.QueryAssetsResponse, error) {
- // AIDEV-NOTE: This method supports automatic asset building when assets don't exist
- // It's the key integration point for the metald → assetmanagerd → builderd workflow
-
- //exhaustruct:ignore
- req := &assetv1.QueryAssetsRequest{
- Type: assetType,
- LabelSelector: labels,
- PageSize: 1000, // Reasonable default for initial implementation
- BuildOptions: buildOptions,
- }
-
- // Only filter by AVAILABLE status if we're not doing automatic builds
- // Otherwise we might miss PENDING/BUILDING assets and trigger duplicate builds
- if buildOptions == nil || !buildOptions.GetEnableAutoBuild() {
- req.Status = assetv1.AssetStatus_ASSET_STATUS_AVAILABLE
- }
-
- resp, err := c.assetClient.QueryAssets(ctx, connect.NewRequest(req))
- if err != nil {
- // AIDEV-NOTE: Enhanced debug logging for connection errors
- var connectErr *connect.Error
- if errors.As(err, &connectErr) {
- c.logger.LogAttrs(ctx, slog.LevelError, "assetmanager connection error",
- slog.String("error", err.Error()),
- slog.String("code", connectErr.Code().String()),
- slog.String("message", connectErr.Message()),
- slog.String("asset_type", assetType.String()),
- slog.String("operation", "QueryAssets"),
- )
- } else {
- c.logger.LogAttrs(ctx, slog.LevelError, "failed to query assets",
- slog.String("error", err.Error()),
- slog.String("asset_type", assetType.String()),
- slog.String("operation", "QueryAssets"),
- )
- }
- return nil, fmt.Errorf("failed to query assets: %w", err)
- }
-
- c.logger.LogAttrs(ctx, slog.LevelDebug, "queried assets",
- slog.Int("asset_count", len(resp.Msg.GetAssets())),
- slog.Int("builds_triggered", len(resp.Msg.GetTriggeredBuilds())),
- slog.String("asset_type", assetType.String()),
- )
-
- // Log any triggered builds
- for _, build := range resp.Msg.GetTriggeredBuilds() {
- c.logger.LogAttrs(ctx, slog.LevelInfo, "build triggered for missing asset",
- slog.String("build_id", build.GetBuildId()),
- slog.String("docker_image", build.GetDockerImage()),
- slog.String("status", build.GetStatus()),
- slog.String("asset_id", build.GetAssetId()),
- )
- }
-
- return resp.Msg, nil
-}
-
-// PrepareAssets stages assets for a specific VM in the target path
-func (c *client) PrepareAssets(ctx context.Context, assetIDs []string, targetPath string, vmID string) (map[string]string, error) {
- req := &assetv1.PrepareAssetsRequest{
- AssetIds: assetIDs,
- TargetPath: targetPath,
- PreparedFor: vmID,
- }
-
- resp, err := c.assetClient.PrepareAssets(ctx, connect.NewRequest(req))
- if err != nil {
- // AIDEV-NOTE: Enhanced debug logging for connection errors
- var connectErr *connect.Error
- if errors.As(err, &connectErr) {
- c.logger.LogAttrs(ctx, slog.LevelError, "assetmanager connection error",
- slog.String("error", err.Error()),
- slog.String("code", connectErr.Code().String()),
- slog.String("message", connectErr.Message()),
- slog.String("vm_id", vmID),
- slog.String("target_path", targetPath),
- slog.String("operation", "PrepareAssets"),
- slog.Int("asset_count", len(assetIDs)),
- )
- } else {
- c.logger.LogAttrs(ctx, slog.LevelError, "failed to prepare assets",
- slog.String("error", err.Error()),
- slog.String("vm_id", vmID),
- slog.String("target_path", targetPath),
- slog.String("operation", "PrepareAssets"),
- slog.Int("asset_count", len(assetIDs)),
- )
- }
- return nil, fmt.Errorf("failed to prepare assets: %w", err)
- }
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "prepared assets for VM",
- slog.String("vm_id", vmID),
- slog.Int("asset_count", len(resp.Msg.GetAssetPaths())),
- )
-
- return resp.Msg.GetAssetPaths(), nil
-}
-
-// AcquireAsset marks an asset as in-use by a VM
-func (c *client) AcquireAsset(ctx context.Context, assetID string, vmID string) (string, error) {
- req := &assetv1.AcquireAssetRequest{
- AssetId: assetID,
- AcquiredBy: vmID,
- TtlSeconds: 86400, // 24 hours default TTL
- }
-
- resp, err := c.assetClient.AcquireAsset(ctx, connect.NewRequest(req))
- if err != nil {
- // AIDEV-NOTE: Enhanced debug logging for connection errors
- var connectErr *connect.Error
- if errors.As(err, &connectErr) {
- c.logger.LogAttrs(ctx, slog.LevelError, "assetmanager connection error",
- slog.String("error", err.Error()),
- slog.String("code", connectErr.Code().String()),
- slog.String("message", connectErr.Message()),
- slog.String("asset_id", assetID),
- slog.String("vm_id", vmID),
- slog.String("operation", "AcquireAsset"),
- )
- } else {
- c.logger.LogAttrs(ctx, slog.LevelError, "failed to acquire asset",
- slog.String("error", err.Error()),
- slog.String("asset_id", assetID),
- slog.String("vm_id", vmID),
- slog.String("operation", "AcquireAsset"),
- )
- }
- return "", fmt.Errorf("failed to acquire asset: %w", err)
- }
-
- c.logger.LogAttrs(ctx, slog.LevelDebug, "acquired asset",
- slog.String("asset_id", assetID),
- slog.String("vm_id", vmID),
- slog.String("lease_id", resp.Msg.GetLeaseId()),
- )
-
- return resp.Msg.GetLeaseId(), nil
-}
-
-// ReleaseAsset releases an asset reference
-func (c *client) ReleaseAsset(ctx context.Context, leaseID string) error {
- req := &assetv1.ReleaseAssetRequest{
- LeaseId: leaseID,
- }
-
- _, err := c.assetClient.ReleaseAsset(ctx, connect.NewRequest(req))
- if err != nil {
- // AIDEV-NOTE: Enhanced debug logging for connection errors
- var connectErr *connect.Error
- if errors.As(err, &connectErr) {
- c.logger.LogAttrs(ctx, slog.LevelError, "assetmanager connection error",
- slog.String("error", err.Error()),
- slog.String("code", connectErr.Code().String()),
- slog.String("message", connectErr.Message()),
- slog.String("lease_id", leaseID),
- slog.String("operation", "ReleaseAsset"),
- )
- } else {
- c.logger.LogAttrs(ctx, slog.LevelError, "failed to release asset",
- slog.String("error", err.Error()),
- slog.String("lease_id", leaseID),
- slog.String("operation", "ReleaseAsset"),
- )
- }
- return fmt.Errorf("failed to release asset: %w", err)
- }
-
- c.logger.LogAttrs(ctx, slog.LevelDebug, "released asset",
- slog.String("lease_id", leaseID),
- )
-
- return nil
-}
-
-// noopClient is used when assetmanagerd integration is disabled
-type noopClient struct{}
-
-func (n *noopClient) ListAssets(ctx context.Context, assetType assetv1.AssetType, labels map[string]string) ([]*assetv1.Asset, error) {
- // Return empty list when disabled
- return []*assetv1.Asset{}, nil
-}
-
-func (n *noopClient) QueryAssets(ctx context.Context, assetType assetv1.AssetType, labels map[string]string, buildOptions *assetv1.BuildOptions) (*assetv1.QueryAssetsResponse, error) {
- // Return empty response when disabled
- return &assetv1.QueryAssetsResponse{
- Assets: []*assetv1.Asset{},
- }, nil
-}
-
-func (n *noopClient) PrepareAssets(ctx context.Context, assetIDs []string, targetPath string, vmID string) (map[string]string, error) {
- // Return empty map when disabled
- return map[string]string{}, nil
-}
-
-func (n *noopClient) AcquireAsset(ctx context.Context, assetID string, vmID string) (string, error) {
- // Return empty lease ID when disabled
- return "", nil
-}
-
-func (n *noopClient) ReleaseAsset(ctx context.Context, leaseID string) error {
- // No-op when disabled
- return nil
-}
-
-// loggingInterceptor provides basic logging for RPC calls
-func loggingInterceptor(logger *slog.Logger) connect.UnaryInterceptorFunc {
- return func(next connect.UnaryFunc) connect.UnaryFunc {
- return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
- start := time.Now()
-
- // Execute request
- resp, err := next(ctx, req)
-
- // Log result
- duration := time.Since(start)
- if err != nil {
- // AIDEV-NOTE: Enhanced debug logging for RPC errors
- var connectErr *connect.Error
- if errors.As(err, &connectErr) {
- logger.LogAttrs(ctx, slog.LevelError, "assetmanager rpc connection error",
- slog.String("procedure", req.Spec().Procedure),
- slog.Duration("duration", duration),
- slog.String("error", err.Error()),
- slog.String("code", connectErr.Code().String()),
- slog.String("details", connectErr.Message()),
- )
- } else {
- logger.LogAttrs(ctx, slog.LevelError, "assetmanager rpc error",
- slog.String("procedure", req.Spec().Procedure),
- slog.Duration("duration", duration),
- slog.String("error", err.Error()),
- )
- }
- } else {
- logger.LogAttrs(ctx, slog.LevelDebug, "assetmanager rpc success",
- slog.String("procedure", req.Spec().Procedure),
- slog.Duration("duration", duration),
- )
- }
-
- return resp, err
- }
- }
-}
diff --git a/go/apps/metald/internal/assetmanager/client_test.go b/go/apps/metald/internal/assetmanager/client_test.go
deleted file mode 100644
index b14160bb45..0000000000
--- a/go/apps/metald/internal/assetmanager/client_test.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package assetmanager
-
-import (
- "context"
- "log/slog"
- "testing"
-
- "github.com/unkeyed/unkey/go/apps/metald/internal/config"
- assetv1 "github.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1"
-)
-
-func TestNewClient(t *testing.T) {
- logger := slog.Default()
-
- tests := []struct {
- name string
- config *config.AssetManagerConfig
- wantErr bool
- wantNoop bool
- }{
- {
- name: "enabled client",
- config: &config.AssetManagerConfig{
- Enabled: true,
- Endpoint: "http://localhost:8082",
- CacheDir: "/tmp/assets",
- },
- wantErr: false,
- wantNoop: false,
- },
- {
- name: "disabled client returns noop",
- config: &config.AssetManagerConfig{
- Enabled: false,
- Endpoint: "http://localhost:8082",
- CacheDir: "/tmp/assets",
- },
- wantErr: false,
- wantNoop: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- client, err := NewClient(tt.config, logger)
- if (err != nil) != tt.wantErr {
- t.Errorf("NewClient() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
-
- // Check if we got a noop client
- _, isNoop := client.(*noopClient)
- if isNoop != tt.wantNoop {
- t.Errorf("NewClient() returned noop = %v, want %v", isNoop, tt.wantNoop)
- }
- })
- }
-}
-
-func TestNoopClient(t *testing.T) {
- ctx := context.Background()
- client := &noopClient{}
-
- // Test ListAssets returns empty list
- assets, err := client.ListAssets(ctx, assetv1.AssetType_ASSET_TYPE_KERNEL, nil)
- if err != nil {
- t.Errorf("ListAssets() unexpected error: %v", err)
- }
- if len(assets) != 0 {
- t.Errorf("ListAssets() expected empty list, got %d assets", len(assets))
- }
-
- // Test PrepareAssets returns empty map
- paths, err := client.PrepareAssets(ctx, []string{"asset1", "asset2"}, "/tmp", "vm-123")
- if err != nil {
- t.Errorf("PrepareAssets() unexpected error: %v", err)
- }
- if len(paths) != 0 {
- t.Errorf("PrepareAssets() expected empty map, got %d paths", len(paths))
- }
-
- // Test AcquireAsset returns empty lease
- lease, err := client.AcquireAsset(ctx, "asset1", "vm-123")
- if err != nil {
- t.Errorf("AcquireAsset() unexpected error: %v", err)
- }
- if lease != "" {
- t.Errorf("AcquireAsset() expected empty lease, got %s", lease)
- }
-
- // Test ReleaseAsset succeeds
- err = client.ReleaseAsset(ctx, "lease-123")
- if err != nil {
- t.Errorf("ReleaseAsset() unexpected error: %v", err)
- }
-}
diff --git a/go/apps/metald/internal/backend/docker/docker.go b/go/apps/metald/internal/backend/docker/docker.go
deleted file mode 100644
index f6cafd49ea..0000000000
--- a/go/apps/metald/internal/backend/docker/docker.go
+++ /dev/null
@@ -1,558 +0,0 @@
-package docker
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net"
- "strings"
- "time"
-
- "github.com/docker/docker/api/types/container"
- "github.com/docker/docker/api/types/filters"
- "github.com/docker/docker/api/types/image"
- "github.com/docker/docker/api/types/network"
- "github.com/docker/docker/client"
- "github.com/docker/docker/errdefs"
- "github.com/docker/go-connections/nat"
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/types"
- metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1"
- "github.com/unkeyed/unkey/go/pkg/otel/logging"
-)
-
-// Compile-time interface checks
-var _ types.Backend = (*Backend)(nil)
-var _ types.VMListProvider = (*Backend)(nil)
-
-// Backend implements types.Backend using Docker containers
-type Backend struct {
- logger logging.Logger
- dockerClient *client.Client
-}
-
-// New creates a new Docker backend
-func New(logger logging.Logger) (*Backend, error) {
- dockerClient, err := client.NewClientWithOpts(
- client.FromEnv,
- client.WithAPIVersionNegotiation(),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create Docker client: %w", err)
- }
-
- // Verify Docker connection
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
-
- if _, pingErr := dockerClient.Ping(ctx); pingErr != nil {
- return nil, fmt.Errorf("failed to connect to Docker daemon: %w", pingErr)
- }
-
- return &Backend{
- logger: logger.With("backend", "docker"),
- dockerClient: dockerClient,
- }, nil
-}
-
-// calculateGatewayIP calculates the gateway IP for a subnet (usually .1)
-func calculateGatewayIP(subnet string) (string, error) {
- _, ipnet, err := net.ParseCIDR(subnet)
- if err != nil {
- return "", fmt.Errorf("invalid subnet: %w", err)
- }
-
- // Get the first IP in the subnet (network address)
- ip := ipnet.IP.Mask(ipnet.Mask)
-
- // Increment to get the first usable IP (gateway)
- ip[len(ip)-1]++
-
- return ip.String(), nil
-}
-
-// createCustomNetwork creates a Docker network for the deployment
-func (b *Backend) createCustomNetwork(ctx context.Context, deploymentID, subnet string) (string, error) {
- networkName := fmt.Sprintf("unkey-deployment-%s", deploymentID)
-
- // Check if network already exists
- networks, err := b.dockerClient.NetworkList(ctx, network.ListOptions{
- Filters: filters.NewArgs(filters.Arg("name", networkName)),
- })
- if err != nil {
- return "", fmt.Errorf("failed to list networks: %w", err)
- }
-
- if len(networks) > 0 {
- b.logger.Info("network already exists", "network", networkName)
- return networks[0].ID, nil
- }
-
- // Calculate gateway IP
- gatewayIP, err := calculateGatewayIP(subnet)
- if err != nil {
- return "", fmt.Errorf("failed to calculate gateway IP: %w", err)
- }
-
- // Create new network
- networkConfig := network.CreateOptions{
- Driver: "bridge",
- IPAM: &network.IPAM{
- Driver: "default",
- Config: []network.IPAMConfig{
- {
- Subnet: subnet,
- Gateway: gatewayIP,
- },
- },
- },
- Options: map[string]string{
- "com.docker.network.bridge.enable_ip_masquerade": "true",
- "com.docker.network.bridge.enable_icc": "true",
- },
- Labels: map[string]string{
- "unkey.managed.by": "metald",
- "unkey.deployment.id": deploymentID,
- },
- }
-
- resp, err := b.dockerClient.NetworkCreate(ctx, networkName, networkConfig)
- if err != nil {
- return "", fmt.Errorf("failed to create network: %w", err)
- }
-
- b.logger.Info("created custom network",
- "network", networkName,
- "subnet", subnet,
- "gateway", gatewayIP,
- )
-
- return resp.ID, nil
-}
-
-// CreateVM creates a new Docker container as a VM
-func (b *Backend) CreateVM(ctx context.Context, config *metaldv1.VmConfig) (string, error) {
- // Use the provided VM ID from config
- vmID := config.Id
- if vmID == "" {
- return "", fmt.Errorf("VM ID is required")
- }
-
- b.logger.Info("creating Docker VM",
- "vm_id", vmID,
- "image", config.Boot)
-
- // Use boot configuration as Docker image name
- imageName := config.Boot
- if imageName == "" {
- return "", fmt.Errorf("boot image (Docker image) is required")
- }
-
- // Parse network configuration
- var networkInfo map[string]string
- if config.NetworkConfig != "" {
- if err := json.Unmarshal([]byte(config.NetworkConfig), &networkInfo); err != nil {
- return "", fmt.Errorf("failed to parse network config: %w", err)
- }
- }
-
- // Pull image if not present
- if err := b.pullImageIfNeeded(ctx, imageName); err != nil {
- return "", fmt.Errorf("failed to pull image: %w", err)
- }
-
- // Use VM ID directly as container name
- containerName := vmID
- containerConfig := &container.Config{
- Image: imageName,
- Labels: map[string]string{
- "unkey.vm.id": vmID,
- "unkey.managed.by": "metald",
- },
- ExposedPorts: nat.PortSet{
- "8080/tcp": struct{}{},
- },
- Hostname: containerName,
- }
-
- // Set memory and CPU limits based on VM config
- resources := container.Resources{}
- if config.MemorySizeMib > 0 {
- resources.Memory = int64(config.MemorySizeMib) * 1024 * 1024
- }
- if config.VcpuCount > 0 {
- resources.NanoCPUs = int64(config.VcpuCount) * 1000000000
- }
-
- hostConfig := &container.HostConfig{
- PortBindings: nat.PortMap{
- "8080/tcp": []nat.PortBinding{
- {
- HostIP: "0.0.0.0",
- HostPort: "0", // Let Docker assign a random port
- },
- },
- },
- AutoRemove: false,
- Resources: resources,
- }
-
- var networkingConfig *network.NetworkingConfig
-
- // If we have network info, create/use custom network
- if deploymentID, ok := networkInfo["deployment_id"]; ok {
- subnet := networkInfo["subnet"]
- allocatedIP := networkInfo["allocated_ip"]
-
- // Create custom network for this deployment
- _, err := b.createCustomNetwork(ctx, deploymentID, subnet)
- if err != nil {
- return "", fmt.Errorf("failed to create custom network: %w", err)
- }
-
- networkName := fmt.Sprintf("unkey-deployment-%s", deploymentID)
- networkingConfig = &network.NetworkingConfig{
- EndpointsConfig: map[string]*network.EndpointSettings{
- networkName: {
- IPAMConfig: &network.EndpointIPAMConfig{
- IPv4Address: allocatedIP,
- },
- },
- },
- }
-
- b.logger.Info("using custom network",
- "vm_id", vmID,
- "network", networkName,
- "ip", allocatedIP,
- )
- }
-
- // Create the container
- resp, err := b.dockerClient.ContainerCreate(ctx, containerConfig, hostConfig, networkingConfig, nil, containerName)
- if err != nil {
- return "", fmt.Errorf("failed to create container: %w", err)
- }
-
- b.logger.Info("Docker VM created",
- "vm_id", vmID,
- "container_id", resp.ID)
-
- return vmID, nil
-}
-
-// DeleteVM removes a Docker container VM
-func (b *Backend) DeleteVM(ctx context.Context, vmID string) error {
- // Get container info to find which networks it's connected to
- inspect, err := b.dockerClient.ContainerInspect(ctx, vmID)
- if err != nil && !errdefs.IsNotFound(err) {
- b.logger.Warn("failed to inspect container", "vm_id", vmID, "error", err)
- }
-
- // Stop container if running
- if err := b.dockerClient.ContainerStop(ctx, vmID, container.StopOptions{}); err != nil {
- if !errdefs.IsNotFound(err) {
- b.logger.Error("failed to stop container",
- "vm_id", vmID,
- "error", err)
- }
- }
-
- // Remove container
- if err := b.dockerClient.ContainerRemove(ctx, vmID, container.RemoveOptions{
- Force: true,
- }); err != nil {
- if !errdefs.IsNotFound(err) {
- return fmt.Errorf("failed to remove container: %w", err)
- }
- }
-
- // Clean up deployment networks if this was the last container
- if inspect.ID != "" {
- for networkName := range inspect.NetworkSettings.Networks {
- if strings.HasPrefix(networkName, "unkey-deployment-") {
- b.cleanupNetworkIfEmpty(ctx, networkName)
- }
- }
- }
-
- b.logger.Info("Docker VM deleted", "vm_id", vmID)
- return nil
-}
-
-// cleanupNetworkIfEmpty removes a deployment network if no containers are using it
-func (b *Backend) cleanupNetworkIfEmpty(ctx context.Context, networkName string) {
- // Get network info
- networkInfo, err := b.dockerClient.NetworkInspect(ctx, networkName, network.InspectOptions{})
- if err != nil {
- b.logger.Warn("failed to inspect network", "network", networkName, "error", err)
- return
- }
-
- // Count VM containers (containers managed by metald)
- vmContainers := 0
- for _, endpoint := range networkInfo.Containers {
- // Count containers that look like VM containers
- if strings.HasPrefix(endpoint.Name, "ud-") || strings.Contains(endpoint.Name, "unkey-vm") {
- vmContainers++
- }
- }
-
- // If no VM containers are left, remove the network
- if vmContainers == 0 {
- // Remove the network
- err := b.dockerClient.NetworkRemove(ctx, networkName)
- if err != nil {
- b.logger.Warn("failed to remove empty network", "network", networkName, "error", err)
- } else {
- b.logger.Info("cleaned up empty deployment network", "network", networkName)
- }
- }
-}
-
-// BootVM starts a Docker container VM
-func (b *Backend) BootVM(ctx context.Context, vmID string) error {
- // Docker client accepts both container ID and container name
- if err := b.dockerClient.ContainerStart(ctx, vmID, container.StartOptions{}); err != nil {
- return fmt.Errorf("failed to start container: %w", err)
- }
-
- b.logger.Info("Docker VM booted", "vm_id", vmID)
- return nil
-}
-
-// ShutdownVM gracefully stops a Docker container VM
-func (b *Backend) ShutdownVM(ctx context.Context, vmID string) error {
- return b.ShutdownVMWithOptions(ctx, vmID, false, 30)
-}
-
-// ShutdownVMWithOptions gracefully stops a Docker container VM with options
-func (b *Backend) ShutdownVMWithOptions(ctx context.Context, vmID string, force bool, timeoutSeconds int32) error {
- timeout := int(timeoutSeconds)
- stopOptions := container.StopOptions{
- Timeout: &timeout,
- }
-
- if err := b.dockerClient.ContainerStop(ctx, vmID, stopOptions); err != nil {
- if force {
- // Force kill if graceful stop fails
- if killErr := b.dockerClient.ContainerKill(ctx, vmID, "KILL"); killErr != nil {
- return fmt.Errorf("failed to force kill container: %w", killErr)
- }
- } else {
- return fmt.Errorf("failed to stop container: %w", err)
- }
- }
-
- b.logger.Info("Docker VM shutdown", "vm_id", vmID, "force", force)
- return nil
-}
-
-// PauseVM pauses a Docker container VM
-func (b *Backend) PauseVM(ctx context.Context, vmID string) error {
- if err := b.dockerClient.ContainerPause(ctx, vmID); err != nil {
- return fmt.Errorf("failed to pause container: %w", err)
- }
-
- b.logger.Info("Docker VM paused", "vm_id", vmID)
- return nil
-}
-
-// ResumeVM resumes a paused Docker container VM
-func (b *Backend) ResumeVM(ctx context.Context, vmID string) error {
- if err := b.dockerClient.ContainerUnpause(ctx, vmID); err != nil {
- return fmt.Errorf("failed to unpause container: %w", err)
- }
-
- b.logger.Info("Docker VM resumed", "vm_id", vmID)
- return nil
-}
-
-// RebootVM restarts a Docker container VM
-func (b *Backend) RebootVM(ctx context.Context, vmID string) error {
- timeout := int(30)
- if err := b.dockerClient.ContainerRestart(ctx, vmID, container.StopOptions{Timeout: &timeout}); err != nil {
- return fmt.Errorf("failed to restart container: %w", err)
- }
-
- b.logger.Info("Docker VM rebooted", "vm_id", vmID)
- return nil
-}
-
-// GetVMInfo retrieves current VM state and configuration
-func (b *Backend) GetVMInfo(ctx context.Context, vmID string) (*types.VMInfo, error) {
- // Get current container state
- inspect, err := b.dockerClient.ContainerInspect(ctx, vmID)
- if err != nil {
- return nil, fmt.Errorf("failed to inspect container: %w", err)
- }
-
- // Determine state based on container state
- state := metaldv1.VmState_VM_STATE_UNSPECIFIED
- if inspect.State.Running {
- state = metaldv1.VmState_VM_STATE_RUNNING
- } else if inspect.State.Paused {
- state = metaldv1.VmState_VM_STATE_PAUSED
- } else if inspect.State.Dead || inspect.State.OOMKilled {
- state = metaldv1.VmState_VM_STATE_SHUTDOWN
- } else {
- state = metaldv1.VmState_VM_STATE_CREATED
- }
-
- // Reconstruct config from container labels and inspect data
- config := &metaldv1.VmConfig{
- Id: vmID,
- Boot: inspect.Config.Image,
- }
-
- // Extract resources if set
- if inspect.HostConfig.Resources.Memory > 0 {
- config.MemorySizeMib = uint64(inspect.HostConfig.Resources.Memory / (1024 * 1024))
- }
- if inspect.HostConfig.Resources.NanoCPUs > 0 {
- config.VcpuCount = uint32(inspect.HostConfig.Resources.NanoCPUs / 1000000000)
- }
-
- return &types.VMInfo{
- Config: config,
- State: state,
- }, nil
-}
-
-// GetVMMetrics retrieves current VM resource usage metrics
-func (b *Backend) GetVMMetrics(ctx context.Context, vmID string) (*types.VMMetrics, error) {
- // Get container stats
- stats, err := b.dockerClient.ContainerStats(ctx, vmID, false)
- if err != nil {
- return nil, fmt.Errorf("failed to get container stats: %w", err)
- }
- defer stats.Body.Close()
-
- // Parse stats from JSON stream
- var containerStats container.Stats
- if err := json.NewDecoder(stats.Body).Decode(&containerStats); err != nil {
- return nil, fmt.Errorf("failed to decode stats: %w", err)
- }
-
- // Calculate metrics
- metrics := &types.VMMetrics{
- Timestamp: time.Now(),
- CpuTimeNanos: int64(containerStats.CPUStats.CPUUsage.TotalUsage),
- MemoryUsageBytes: int64(containerStats.MemoryStats.Usage),
- }
-
- // Network stats (sum all interfaces)
- for _, network := range containerStats.Networks {
- metrics.NetworkRxBytes += int64(network.RxBytes)
- metrics.NetworkTxBytes += int64(network.TxBytes)
- }
-
- // Disk I/O stats (if available)
- for _, ioStats := range containerStats.BlkioStats.IoServiceBytesRecursive {
- switch strings.ToLower(ioStats.Op) {
- case "read":
- metrics.DiskReadBytes += int64(ioStats.Value)
- case "write":
- metrics.DiskWriteBytes += int64(ioStats.Value)
- }
- }
-
- return metrics, nil
-}
-
-// Ping checks if the Docker daemon is healthy and responsive
-func (b *Backend) Ping(ctx context.Context) error {
- _, err := b.dockerClient.Ping(ctx)
- if err != nil {
- return fmt.Errorf("Docker daemon ping failed: %w", err)
- }
-
- return nil
-}
-
-// ListVMs returns a list of all VMs managed by this backend
-func (b *Backend) ListVMs() []types.ListableVMInfo {
- ctx := context.Background()
- containers, err := b.dockerClient.ContainerList(ctx, container.ListOptions{
- All: true,
- Filters: filters.NewArgs(
- filters.Arg("label", "unkey.managed.by=metald"),
- ),
- })
- if err != nil {
- b.logger.Error("failed to list containers", "error", err)
- return []types.ListableVMInfo{}
- }
-
- vms := make([]types.ListableVMInfo, 0, len(containers))
- for _, c := range containers {
- vmID := c.Labels["unkey.vm.id"]
- if vmID == "" {
- // Fallback to container name since we set container name = VM ID
- vmID = c.Names[0]
- }
-
- // Determine state from container state
- state := metaldv1.VmState_VM_STATE_CREATED
- switch c.State {
- case "running":
- state = metaldv1.VmState_VM_STATE_RUNNING
- case "paused":
- state = metaldv1.VmState_VM_STATE_PAUSED
- case "exited", "dead":
- state = metaldv1.VmState_VM_STATE_SHUTDOWN
- default:
- state = metaldv1.VmState_VM_STATE_UNSPECIFIED
- }
-
- config := &metaldv1.VmConfig{
- Id: vmID,
- Boot: c.Image,
- }
-
- vms = append(vms, types.ListableVMInfo{
- ID: vmID,
- State: state,
- Config: config,
- })
- }
- return vms
-}
-
-// Type returns the backend type as a string for metrics
-func (b *Backend) Type() string {
- return string(types.BackendTypeDocker)
-}
-
-// Helper methods
-
-func (b *Backend) pullImageIfNeeded(ctx context.Context, imageName string) error {
- // Check if image exists locally
- _, _, inspectErr := b.dockerClient.ImageInspectWithRaw(ctx, imageName)
- if inspectErr == nil {
- b.logger.Info("image found locally", "image", imageName)
- return nil
- }
-
- // Only attempt to pull if the error indicates the image is not found
- if !errdefs.IsNotFound(inspectErr) {
- return fmt.Errorf("failed to inspect image %s: %w", imageName, inspectErr)
- }
-
- // Image not found locally, attempt to pull it
- b.logger.Info("pulling image", "image", imageName)
- reader, pullErr := b.dockerClient.ImagePull(ctx, imageName, image.PullOptions{})
- if pullErr != nil {
- return fmt.Errorf("image %s not found locally and pull failed: inspect error: %v, pull error: %w", imageName, inspectErr, pullErr)
- }
- defer reader.Close()
-
- // Read the output to ensure pull completes
- _, err := io.Copy(io.Discard, reader)
- if err != nil {
- return fmt.Errorf("failed to read pull response: %w", err)
- }
-
- b.logger.Info("image pulled successfully", "image", imageName)
- return nil
-}
diff --git a/go/apps/metald/internal/backend/firecracker/asset_manager.go b/go/apps/metald/internal/backend/firecracker/asset_manager.go
deleted file mode 100644
index afdb76d650..0000000000
--- a/go/apps/metald/internal/backend/firecracker/asset_manager.go
+++ /dev/null
@@ -1,513 +0,0 @@
-//go:build linux
-// +build linux
-
-package firecracker
-
-import (
- "context"
- "crypto/sha256"
- "fmt"
- "log/slog"
- "os"
- "path/filepath"
- "sort"
- "strings"
-
- assetv1 "github.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1"
- metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/codes"
- "go.opentelemetry.io/otel/trace"
-)
-
-// releaseAssetLeases releases all asset leases for a VM
-func (c *Client) releaseAssetLeases(ctx context.Context, vmID string) {
- if leaseIDs, ok := c.vmAssetLeases[vmID]; ok {
- c.logger.LogAttrs(ctx, slog.LevelInfo, "releasing asset leases",
- slog.String("vm_id", vmID),
- slog.Int("lease_count", len(leaseIDs)),
- )
-
- for _, leaseID := range leaseIDs {
- releaseCtx, releaseSpan := c.tracer.Start(ctx, "metald.firecracker.release_asset",
- trace.WithAttributes(
- attribute.String("vm.id", vmID),
- attribute.String("lease.id", leaseID),
- ),
- )
- err := c.assetClient.ReleaseAsset(releaseCtx, leaseID)
- if err != nil {
- releaseSpan.RecordError(err)
- releaseSpan.SetStatus(codes.Error, err.Error())
- c.logger.ErrorContext(ctx, "failed to release asset lease",
- "vm_id", vmID,
- "lease_id", leaseID,
- "error", err,
- )
- // Continue with other leases even if one fails
- }
- releaseSpan.End()
- }
- delete(c.vmAssetLeases, vmID)
- }
-}
-
-// acquireAssetLeases acquires leases for VM assets after successful boot
-func (c *Client) acquireAssetLeases(ctx context.Context, vmID string, assetMapping *assetMapping) {
- if assetMapping == nil || len(assetMapping.AssetIDs()) == 0 {
- return
- }
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "acquiring asset leases for VM",
- slog.String("vm_id", vmID),
- slog.Int("asset_count", len(assetMapping.AssetIDs())),
- )
-
- leaseIDs := []string{}
- for _, assetID := range assetMapping.AssetIDs() {
- acquireCtx, acquireSpan := c.tracer.Start(ctx, "metald.firecracker.acquire_asset",
- trace.WithAttributes(
- attribute.String("vm.id", vmID),
- attribute.String("asset.id", assetID),
- ),
- )
-
- leaseID, err := c.assetClient.AcquireAsset(acquireCtx, assetID, vmID)
- if err != nil {
- acquireSpan.RecordError(err)
- acquireSpan.SetStatus(codes.Error, err.Error())
- c.logger.ErrorContext(ctx, "failed to acquire asset lease",
- "vm_id", vmID,
- "asset_id", assetID,
- "error", err,
- )
- // Continue trying to acquire other leases even if one fails
- } else {
- acquireSpan.SetAttributes(attribute.String("lease.id", leaseID))
- leaseIDs = append(leaseIDs, leaseID)
- }
- acquireSpan.End()
- }
-
- // Store lease IDs for cleanup during VM deletion
- if len(leaseIDs) > 0 {
- c.vmAssetLeases[vmID] = leaseIDs
- c.logger.LogAttrs(ctx, slog.LevelInfo, "acquired asset leases",
- slog.String("vm_id", vmID),
- slog.Int("lease_count", len(leaseIDs)),
- )
- }
-}
-
-// generateAssetID generates a deterministic asset ID based on type and labels
-func (c *Client) generateAssetID(assetType assetv1.AssetType, labels map[string]string) string {
- // Create a deterministic string from sorted labels
- var parts []string
- parts = append(parts, fmt.Sprintf("type=%s", assetType.String()))
-
- // Sort label keys for deterministic ordering
- var keys []string
- for k := range labels {
- keys = append(keys, k)
- }
- sort.Strings(keys)
-
- // Add sorted labels
- for _, k := range keys {
- parts = append(parts, fmt.Sprintf("%s=%s", k, labels[k]))
- }
-
- // Create a hash of the combined string
- combined := strings.Join(parts, ",")
- hash := sha256.Sum256([]byte(combined))
-
- // Return a readable asset ID
- return fmt.Sprintf("asset-%x", hash[:8])
-}
-
-// prepareVMAssets prepares kernel and rootfs assets for the VM in the jailer chroot
-// Returns the asset mapping for lease acquisition after successful boot
-func (c *Client) prepareVMAssets(ctx context.Context, vmID string, config *metaldv1.VmConfig) (*assetMapping, map[string]string, error) {
- // Calculate the jailer chroot path
- jailerRoot := filepath.Join(
- c.jailerConfig.ChrootBaseDir,
- "firecracker",
- vmID,
- "root",
- )
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "preparing VM assets using assetmanager",
- slog.String("vm_id", vmID),
- slog.String("target_path", jailerRoot),
- )
-
- // Ensure the jailer root directory exists
- if err := os.MkdirAll(jailerRoot, 0o755); err != nil {
- return nil, nil, fmt.Errorf("failed to create jailer root directory: %w", err)
- }
-
- // Check if assetmanager is available, fallback to static if not
- // TODO: implement a check with backoff/deadline
-
- // Build asset requirements from VM configuration
- requiredAssets := c.buildAssetRequirements(config)
- c.logger.LogAttrs(ctx, slog.LevelDebug, "determined asset requirements",
- slog.String("vm_id", vmID),
- slog.Int("required_count", len(requiredAssets)),
- )
-
- // Query and build assets as needed
- allAssets, err := c.queryAndBuildAssets(ctx, vmID, config, requiredAssets)
- if err != nil {
- return nil, nil, fmt.Errorf("failed to query/build assets: %w", err)
- }
-
- // Match required assets with available ones
- assetMapping, err := c.matchAssets(requiredAssets, allAssets)
- if err != nil {
- c.logger.LogAttrs(ctx, slog.LevelError, "failed to match assets",
- slog.String("vm_id", vmID),
- slog.String("error", err.Error()),
- )
- return nil, nil, fmt.Errorf("asset matching failed: %w", err)
- }
-
- // Prepare assets in target location
- preparedPaths, err := c.prepareAssetsInLocation(ctx, vmID, assetMapping, jailerRoot)
- if err != nil {
- return nil, nil, fmt.Errorf("failed to prepare assets: %w", err)
- }
-
- // Copy metadata files alongside rootfs assets if they exist
- if err := c.copyMetadataFilesForAssets(ctx, vmID, config, preparedPaths, jailerRoot); err != nil {
- c.logger.WarnContext(ctx, "failed to copy metadata files",
- slog.String("vm_id", vmID),
- slog.String("error", err.Error()),
- )
- // Don't fail asset preparation for metadata issues
- }
-
- return assetMapping, preparedPaths, nil
-}
-
-// isAssetManagerAvailable checks if the asset manager service is available
-func (c *Client) isAssetManagerAvailable(ctx context.Context, vmID string) bool {
- ctx, checkSpan := c.tracer.Start(ctx, "metald.firecracker.check_assetmanager",
- trace.WithAttributes(
- attribute.String("vm.id", vmID),
- attribute.String("asset.type", "KERNEL"),
- ),
- )
- _, err := c.assetClient.QueryAssets(ctx, assetv1.AssetType_ASSET_TYPE_KERNEL, nil, nil)
- checkSpan.End()
- return err == nil
-}
-
-// queryAndBuildAssets queries assetmanager for available assets with automatic build support
-func (c *Client) queryAndBuildAssets(ctx context.Context, vmID string, config *metaldv1.VmConfig, requiredAssets []assetRequirement) ([]*assetv1.Asset, error) {
- allAssets := []*assetv1.Asset{}
-
- // Extract tenant_id from VM metadata if available
- tenantID := "cli-tenant" // Default tenant for CLI operations
- if tid, ok := config.GetMetadata()["tenant_id"]; ok {
- tenantID = tid
- }
-
- // Group requirements by type and labels for efficient querying
- queryGroups := c.groupAssetRequirements(requiredAssets)
-
- // Query each unique combination of type and labels
- for key, reqs := range queryGroups {
- assets, err := c.queryAssetGroup(ctx, vmID, config, key, reqs[0], tenantID)
- if err != nil {
- return nil, err
- }
- allAssets = append(allAssets, assets...)
- }
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "retrieved available assets",
- slog.String("vm_id", vmID),
- slog.Int("available_count", len(allAssets)),
- )
-
- // Log asset details for debugging
- for _, asset := range allAssets {
- c.logger.LogAttrs(ctx, slog.LevelInfo, "available asset",
- slog.String("asset_id", asset.GetId()),
- slog.String("asset_type", asset.GetType().String()),
- slog.Any("labels", asset.GetLabels()),
- )
- }
-
- return allAssets, nil
-}
-
-// groupAssetRequirements groups requirements by type and labels for efficient querying
-func (c *Client) groupAssetRequirements(requiredAssets []assetRequirement) map[queryKey][]assetRequirement {
- queryGroups := make(map[queryKey][]assetRequirement)
- for _, req := range requiredAssets {
- // Serialize labels for grouping
- labelStr := ""
- for k, v := range req.Labels {
- if labelStr != "" {
- labelStr += ","
- }
- labelStr += fmt.Sprintf("%s=%s", k, v)
- }
- key := queryKey{assetType: req.Type, labels: labelStr}
- queryGroups[key] = append(queryGroups[key], req)
- }
- return queryGroups
-}
-
-// queryAssetGroup queries a specific group of assets with the same type and labels
-func (c *Client) queryAssetGroup(ctx context.Context, vmID string, config *metaldv1.VmConfig, key queryKey, req assetRequirement, tenantID string) ([]*assetv1.Asset, error) {
- labels := req.Labels
-
- // Generate a deterministic asset ID
- assetID := c.generateAssetID(key.assetType, labels)
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "generated asset ID for query",
- slog.String("asset_id", assetID),
- slog.String("asset_type", key.assetType.String()),
- slog.Any("labels", labels),
- )
-
- // Configure build options
- buildOptions := c.createBuildOptions(config, labels, tenantID, assetID)
-
- // Record query initiation
- _, initSpan := c.tracer.Start(ctx, "metald.firecracker.query_assets",
- trace.WithAttributes(
- attribute.String("vm.id", vmID),
- attribute.String("asset.type", key.assetType.String()),
- attribute.StringSlice("asset.labels", formatLabels(labels)),
- attribute.String("tenant.id", tenantID),
- attribute.Bool("auto_build.enabled", buildOptions.GetEnableAutoBuild()),
- attribute.Int("build.timeout_seconds", int(buildOptions.GetBuildTimeoutSeconds())),
- ),
- )
- initSpan.End()
-
- // Query assets
- resp, queryErr := c.assetClient.QueryAssets(ctx, key.assetType, labels, buildOptions)
- if queryErr != nil {
- return nil, fmt.Errorf("failed to query assets of type %s with labels %v: %w",
- key.assetType.String(), labels, queryErr)
- }
-
- // Record results
- _, resultSpan := c.tracer.Start(ctx, "metald.firecracker.query_assets_complete",
- trace.WithAttributes(
- attribute.String("vm.id", vmID),
- attribute.String("asset.type", key.assetType.String()),
- attribute.Int("assets.found", len(resp.GetAssets())),
- attribute.Int("builds.triggered", len(resp.GetTriggeredBuilds())),
- ),
- )
- resultSpan.End()
-
- // Log triggered builds
- c.logTriggeredBuilds(ctx, vmID, resp.GetTriggeredBuilds())
-
- return resp.GetAssets(), nil
-}
-
-// createBuildOptions creates build options for asset queries
-func (c *Client) createBuildOptions(config *metaldv1.VmConfig, labels map[string]string, tenantID, assetID string) *assetv1.BuildOptions {
- // Create build labels (copy asset labels and add force_rebuild if needed)
- buildLabels := make(map[string]string)
- for k, v := range labels {
- buildLabels[k] = v
- }
-
- // Check for force_rebuild in VM config metadata
- if forceRebuild, ok := config.GetMetadata()["force_rebuild"]; ok && forceRebuild == "true" {
- buildLabels["force_rebuild"] = "true"
- }
-
- return &assetv1.BuildOptions{
- EnableAutoBuild: true,
- WaitForCompletion: true, // Block VM creation until build completes
- BuildTimeoutSeconds: 1800, // 30 minutes maximum wait time
- SuggestedAssetId: assetID,
- BuildLabels: buildLabels,
- }
-}
-
-// logTriggeredBuilds logs information about builds that were triggered
-func (c *Client) logTriggeredBuilds(ctx context.Context, vmID string, builds []*assetv1.BuildInfo) {
- for _, build := range builds {
- c.logger.LogAttrs(ctx, slog.LevelInfo, "automatic build triggered for missing asset",
- slog.String("vm_id", vmID),
- slog.String("build_id", build.GetBuildId()),
- slog.String("docker_image", build.GetDockerImage()),
- slog.String("status", build.GetStatus()),
- )
-
- if build.GetStatus() == "failed" {
- c.logger.LogAttrs(ctx, slog.LevelError, "automatic build failed",
- slog.String("vm_id", vmID),
- slog.String("build_id", build.GetBuildId()),
- slog.String("error", build.GetErrorMessage()),
- )
- }
- }
-}
-
-// prepareAssetsInLocation prepares assets in the target location
-func (c *Client) prepareAssetsInLocation(ctx context.Context, vmID string, assetMapping *assetMapping, jailerRoot string) (map[string]string, error) {
- ctx, prepareSpan := c.tracer.Start(ctx, "metald.firecracker.prepare_assets",
- trace.WithAttributes(
- attribute.String("vm.id", vmID),
- attribute.StringSlice("asset.ids", assetMapping.AssetIDs()),
- attribute.String("target.path", jailerRoot),
- ),
- )
-
- preparedPaths, err := c.assetClient.PrepareAssets(
- ctx,
- assetMapping.AssetIDs(),
- jailerRoot,
- vmID,
- )
-
- if err != nil {
- prepareSpan.RecordError(err)
- prepareSpan.SetStatus(codes.Error, err.Error())
- } else {
- prepareSpan.SetAttributes(
- attribute.Int("assets.prepared", len(preparedPaths)),
- )
- }
- prepareSpan.End()
-
- if err == nil {
- c.logger.LogAttrs(ctx, slog.LevelInfo, "assets prepared successfully",
- slog.String("vm_id", vmID),
- slog.Int("asset_count", len(preparedPaths)),
- )
- }
-
- return preparedPaths, err
-}
-
-// formatLabels formats labels for tracing attributes
-func formatLabels(labels map[string]string) []string {
- var labelPairs []string
- for k, v := range labels {
- labelPairs = append(labelPairs, fmt.Sprintf("%s=%s", k, v))
- }
- return labelPairs
-}
-
-// buildAssetRequirements analyzes VM config to determine required assets
-func (c *Client) buildAssetRequirements(config *metaldv1.VmConfig) []assetRequirement {
- var reqs []assetRequirement
-
- // // DEBUG: Log VM config for docker image troubleshooting
- // c.logger.Info("DEBUG: analyzing VM config for assets",
- // "storage_count", len(config.GetStorage()),
- // "metadata", config.GetMetadata(),
- // )
- // for i, disk := range config.GetStorage() {
- // c.logger.Info("DEBUG: storage device",
- // "index", i,
- // "id", disk.GetId(),
- // "path", disk.GetPath(),
- // "is_root", disk.GetIsRootDevice(),
- // "options", disk.GetOptions(),
- // )
- // }
-
- // // Kernel requirement
- // if config.GetBoot() != nil && config.GetBoot().GetKernelPath() != "" {
- // reqs = append(reqs, assetRequirement{
- // Type: assetv1.AssetType_ASSET_TYPE_KERNEL,
- // Required: true,
- // })
- // }
-
- // // Rootfs requirements from storage devices
- // for _, disk := range config.GetStorage() {
- // if disk.GetIsRootDevice() {
- // labels := make(map[string]string)
- // // Check for docker image in disk options first, then config metadata
- // if dockerImage, ok := disk.GetOptions()["docker_image"]; ok {
- // labels["docker_image"] = dockerImage
- // } else if dockerImage, ok := config.GetMetadata()["docker_image"]; ok {
- // labels["docker_image"] = dockerImage
- // }
-
- // reqs = append(reqs, assetRequirement{
- // Type: assetv1.AssetType_ASSET_TYPE_ROOTFS,
- // Labels: labels,
- // Required: true,
- // })
- // }
- // }
-
- // // Initrd requirement (optional)
- // if config.GetBoot() != nil && config.GetBoot().GetInitrdPath() != "" {
- // reqs = append(reqs, assetRequirement{
- // Type: assetv1.AssetType_ASSET_TYPE_INITRD,
- // Required: false,
- // })
- // }
-
- return reqs
-}
-
-// matchAssets matches available assets to requirements
-func (c *Client) matchAssets(reqs []assetRequirement, availableAssets []*assetv1.Asset) (*assetMapping, error) {
- mapping := &assetMapping{
- requirements: reqs,
- assets: make(map[string]*assetv1.Asset),
- assetIDs: []string{},
- }
-
- for i, req := range reqs {
- var matched *assetv1.Asset
-
- // Find best matching asset
- for _, asset := range availableAssets {
- if asset.GetType() != req.Type {
- continue
- }
-
- // Check if all required labels match
- labelMatch := true
- for k, v := range req.Labels {
- if assetLabel, ok := asset.GetLabels()[k]; !ok || assetLabel != v {
- labelMatch = false
- break
- }
- }
-
- if labelMatch {
- matched = asset
- break
- }
- }
-
- if matched == nil && req.Required {
- // Build helpful error message
- labelStr := ""
- for k, v := range req.Labels {
- if labelStr != "" {
- labelStr += ", "
- }
- labelStr += fmt.Sprintf("%s=%s", k, v)
- }
- return nil, fmt.Errorf("no matching asset found for type %s with labels {%s}",
- req.Type.String(), labelStr)
- }
-
- if matched != nil {
- mapping.assets[fmt.Sprintf("%d", i)] = matched
- mapping.assetIDs = append(mapping.assetIDs, matched.GetId())
- }
- }
-
- return mapping, nil
-}
diff --git a/go/apps/metald/internal/backend/firecracker/asset_static.go b/go/apps/metald/internal/backend/firecracker/asset_static.go
deleted file mode 100644
index c393a71156..0000000000
--- a/go/apps/metald/internal/backend/firecracker/asset_static.go
+++ /dev/null
@@ -1,104 +0,0 @@
-//go:build linux
-// +build linux
-
-package firecracker
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "log/slog"
- "os"
- "os/exec"
- "path/filepath"
- "strings"
-
- metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1"
-)
-
-// copyMetadataForRootDevice copies metadata and creates container.cmd for a root device
-func (c *Client) copyMetadataForRootDevice(ctx context.Context, vmID string, disk *metaldv1.StorageDevice, jailerRoot string, diskDst string) error {
- baseName := strings.TrimSuffix(filepath.Base(disk.GetPath()), filepath.Ext(disk.GetPath()))
- metadataSrc := filepath.Join(filepath.Dir(disk.GetPath()), baseName+".metadata.json")
-
- // Check if metadata file exists
- if _, err := os.Stat(metadataSrc); err != nil {
- if os.IsNotExist(err) {
- return nil // No metadata file is OK
- }
- return fmt.Errorf("failed to stat metadata file: %w", err)
- }
-
- // Copy metadata file
- metadataDst := filepath.Join(jailerRoot, filepath.Base(metadataSrc))
- if err := copyFileWithOwnership(metadataSrc, metadataDst, int(c.jailerConfig.UID), int(c.jailerConfig.GID)); err != nil {
- return fmt.Errorf("failed to copy metadata file: %w", err)
- }
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "copied metadata file to jailer root",
- slog.String("src", metadataSrc),
- slog.String("dst", metadataDst),
- )
-
- // Load and process metadata to create container.cmd
- metadata, err := c.loadContainerMetadata(ctx, disk.GetPath())
- if err != nil || metadata == nil {
- return nil // Can't create container.cmd without metadata
- }
-
- // Build the command array
- var fullCmd []string
- fullCmd = append(fullCmd, metadata.GetEntrypoint()...)
- fullCmd = append(fullCmd, metadata.GetCommand()...)
-
- if len(fullCmd) == 0 {
- return nil // No command to write
- }
-
- // Write command file to rootfs by mounting it temporarily
- return c.writeContainerCmdToRootfs(ctx, vmID, diskDst, fullCmd)
-}
-
-// writeContainerCmdToRootfs mounts the rootfs and writes the container.cmd file
-func (c *Client) writeContainerCmdToRootfs(ctx context.Context, vmID string, diskDst string, fullCmd []string) error {
- // Create temporary mount directory
- mountDir := filepath.Join("/tmp", fmt.Sprintf("mount-%s", vmID))
- if err := os.MkdirAll(mountDir, 0o755); err != nil {
- return fmt.Errorf("failed to create mount directory: %w", err)
- }
- defer os.RemoveAll(mountDir)
-
- // Mount the rootfs ext4 image
- mountCmd := exec.CommandContext(ctx, "mount", "-o", "loop", diskDst, mountDir)
- if err := mountCmd.Run(); err != nil {
- return fmt.Errorf("failed to mount rootfs: %w", err)
- }
- defer func() {
- // Always unmount
- umountCmd := exec.CommandContext(ctx, "umount", mountDir)
- if err := umountCmd.Run(); err != nil {
- c.logger.WarnContext(ctx, "failed to unmount rootfs",
- "error", err,
- "mountDir", mountDir,
- )
- }
- }()
-
- // Write the command file
- cmdFile := filepath.Join(mountDir, "container.cmd")
- cmdData, err := json.Marshal(fullCmd)
- if err != nil {
- return fmt.Errorf("failed to marshal command: %w", err)
- }
-
- if err := os.WriteFile(cmdFile, cmdData, 0o600); err != nil {
- return fmt.Errorf("failed to write command file: %w", err)
- }
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "wrote container command file to rootfs",
- slog.String("path", cmdFile),
- slog.String("command", string(cmdData)),
- )
-
- return nil
-}
diff --git a/go/apps/metald/internal/backend/firecracker/boot.go b/go/apps/metald/internal/backend/firecracker/boot.go
deleted file mode 100644
index f82c7a3d84..0000000000
--- a/go/apps/metald/internal/backend/firecracker/boot.go
+++ /dev/null
@@ -1,168 +0,0 @@
-//go:build linux
-// +build linux
-
-package firecracker
-
-import (
- "context"
- "fmt"
- "log/slog"
- "path/filepath"
-
- sdk "github.com/firecracker-microvm/firecracker-go-sdk"
- builderv1 "github.com/unkeyed/unkey/go/gen/proto/builderd/v1"
- metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/metric"
- "go.opentelemetry.io/otel/trace"
-)
-
-// BootVM starts a created VM using our integrated jailer
-func (c *Client) BootVM(ctx context.Context, vmID string) error {
- ctx, span := c.tracer.Start(ctx, "metald.firecracker.boot_vm",
- trace.WithAttributes(attribute.String("vm_id", vmID)),
- )
- defer span.End()
-
- vm, exists := c.vmRegistry[vmID]
- if !exists {
- err := fmt.Errorf("vm %s not found", vmID)
- span.RecordError(err)
- c.vmErrorCounter.Add(ctx, 1, metric.WithAttributes(
- attribute.String("operation", "boot"),
- attribute.String("error", "vm_not_found"),
- ))
- return err
- }
-
- // Validate VM state before boot operation
- // TODO: This should also boot stopped/paused VMs at some point
- if vm.State != metaldv1.VmState_VM_STATE_CREATED {
- err := fmt.Errorf("vm %s is in %s state, can only boot VMs in CREATED state", vmID, vm.State.String())
- span.RecordError(err)
- c.vmErrorCounter.Add(ctx, 1, metric.WithAttributes(
- attribute.String("operation", "boot"),
- attribute.String("error", "invalid_state_transition"),
- attribute.String("current_state", vm.State.String()),
- ))
- return err
- }
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "booting VM",
- slog.String("vm_id", vmID),
- slog.String("current_state", vm.State.String()),
- )
-
- // Load VM metadata
- metadata, err := c.prepareVMBootMetadata(ctx, vmID, vm)
- if err != nil {
- c.logger.WarnContext(ctx, "failed to prepare boot metadata",
- "vm_id", vmID,
- "error", err,
- )
- // Continue without metadata rather than failing the boot
- }
-
- // Build and configure firecracker
- fcConfig := c.configureFirecrackerForBoot(ctx, vmID, vm, metadata)
-
- // Create a context for this VM
- vmCtx, cancel := context.WithCancel(context.Background())
- vm.CancelFunc = cancel
-
- // Create and start the machine using SDK
- machine, err := c.startFirecrackerMachine(vmCtx, fcConfig)
- if err != nil {
- cancel()
- span.RecordError(err)
- c.vmErrorCounter.Add(ctx, 1, metric.WithAttributes(
- attribute.String("operation", "boot"),
- attribute.String("error", "start_machine"),
- ))
- return err
- }
-
- // Update VM state
- vm.Machine = machine
- vm.State = metaldv1.VmState_VM_STATE_RUNNING
-
- // Acquire asset leases after successful boot
- c.acquireAssetLeases(ctx, vmID, vm.AssetMapping)
-
- c.vmBootCounter.Add(ctx, 1, metric.WithAttributes(
- attribute.String("status", "success"),
- ))
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "VM booted successfully",
- slog.String("vm_id", vmID),
- )
-
- return nil
-}
-
-// prepareVMBootMetadata loads container metadata and prepares port mappings for VM boot
-func (c *Client) prepareVMBootMetadata(ctx context.Context, vmID string, vm *VM) (*builderv1.ImageMetadata, error) {
- var metadata *builderv1.ImageMetadata
-
- disk := vm.Config.GetStorage()
- if disk.GetIsRootDevice() {
- // Use chroot path for metadata loading since assets are copied there
- jailerRoot := filepath.Join(c.jailerConfig.ChrootBaseDir, "firecracker", vmID, "root")
- chrootRootfsPath := filepath.Join(jailerRoot, "rootfs.ext4")
-
- m, metadataErr := c.loadContainerMetadata(ctx, chrootRootfsPath)
- if metadataErr != nil {
- return nil, fmt.Errorf("failed to load container metadata: %w", metadataErr)
- }
-
- if m != nil {
- metadata = m
-
- // Create /container.cmd file for metald-init
- if cmdFileErr := c.createContainerCmdFile(ctx, vmID, metadata); cmdFileErr != nil {
- return nil, fmt.Errorf("failed to create container.cmd file: %w", cmdFileErr)
- }
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "loaded metadata for VM boot",
- slog.String("vm_id", vmID),
- slog.String("metadata", metadata.String()),
- )
- }
- }
-
- return metadata, nil
-}
-
-// configureFirecrackerForBoot builds and configures firecracker for VM boot
-func (c *Client) configureFirecrackerForBoot(ctx context.Context, vmID string, vm *VM, metadata *builderv1.ImageMetadata) sdk.Config {
- vmDir := filepath.Join(c.baseDir, vmID)
- socketPath := filepath.Join(vmDir, "firecracker.sock")
-
- // Build firecracker config
- fcConfig := c.buildFirecrackerConfig(ctx, vmID, vm.Config, vm.NetworkInfo, vm.AssetPaths)
- fcConfig.SocketPath = socketPath
-
- // Update kernel args with network configuration and metadata if available
- fcConfig.KernelArgs = c.BuildKernelArgs(ctx, vm.NetworkInfo, metadata)
-
- // Set the network namespace for the SDK to use
- if vm.NetworkInfo != nil && vm.NetworkInfo.Namespace != "" {
- fcConfig.NetNS = filepath.Join("/run/netns", vm.NetworkInfo.Namespace)
- }
-
- return fcConfig
-}
-
-// startFirecrackerMachine creates and starts the firecracker machine
-func (c *Client) startFirecrackerMachine(ctx context.Context, fcConfig sdk.Config) (*sdk.Machine, error) {
- machine, err := sdk.NewMachine(ctx, fcConfig)
- if err != nil {
- return nil, fmt.Errorf("failed to create firecracker machine: %w", err)
- }
-
- if err := machine.Start(ctx); err != nil {
- return nil, fmt.Errorf("failed to start firecracker machine: %w", err)
- }
-
- return machine, nil
-}
diff --git a/go/apps/metald/internal/backend/firecracker/client.go b/go/apps/metald/internal/backend/firecracker/client.go
deleted file mode 100644
index 081c64d8db..0000000000
--- a/go/apps/metald/internal/backend/firecracker/client.go
+++ /dev/null
@@ -1,101 +0,0 @@
-//go:build linux
-// +build linux
-
-package firecracker
-
-import (
- "context"
- "fmt"
- "log/slog"
-
- "github.com/unkeyed/unkey/go/apps/metald/internal/assetmanager"
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/types"
- "github.com/unkeyed/unkey/go/apps/metald/internal/config"
- "github.com/unkeyed/unkey/go/apps/metald/internal/jailer"
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/metric"
-)
-
-// NewClient creates a new SDK-based Firecracker backend client with integrated jailer
-func NewClient(logger *slog.Logger,
- assetClient assetmanager.Client,
- jailerConfig *config.JailerConfig,
- baseDir string,
-) (*Client, error) {
- tracer := otel.Tracer("metald.firecracker")
- meter := otel.Meter("metald.firecracker")
-
- vmCreateCounter, err := meter.Int64Counter("vm_create_total",
- metric.WithDescription("Total number of VM create operations"),
- metric.WithUnit("1"),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create vm_create counter: %w", err)
- }
-
- vmDeleteCounter, err := meter.Int64Counter("vm_delete_total",
- metric.WithDescription("Total number of VM delete operations"),
- metric.WithUnit("1"),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create vm_delete counter: %w", err)
- }
-
- vmBootCounter, err := meter.Int64Counter("vm_boot_total",
- metric.WithDescription("Total number of VM boot operations"),
- metric.WithUnit("1"),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create vm_boot counter: %w", err)
- }
-
- vmErrorCounter, err := meter.Int64Counter("vm_error_total",
- metric.WithDescription("Total number of VM operation errors"),
- metric.WithUnit("1"),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create vm_error counter: %w", err)
- }
-
- // Create integrated jailer
- integratedJailer := jailer.NewJailer(logger, jailerConfig)
-
- return &Client{
- logger: logger.With("backend", "firecracker"),
- assetClient: assetClient,
- vmAssetLeases: make(map[string][]string),
- jailer: integratedJailer,
- jailerConfig: jailerConfig,
- baseDir: baseDir,
- tracer: tracer,
- meter: meter,
- vmCreateCounter: vmCreateCounter,
- vmDeleteCounter: vmDeleteCounter,
- vmBootCounter: vmBootCounter,
- vmErrorCounter: vmErrorCounter,
- }, nil
-}
-
-// Ping verifies the backend is operational
-func (c *Client) Ping(ctx context.Context) error {
- c.logger.DebugContext(ctx, "pinging firecracker backend")
- return nil
-}
-
-// Shutdown gracefully shuts down the SDK client while preserving VMs
-func (c *Client) Shutdown(ctx context.Context) error {
- ctx, span := c.tracer.Start(ctx, "metald.firecracker.shutdown")
- defer span.End()
-
- c.logger.InfoContext(ctx, "shutting down firecracker backend")
-
- return nil
-}
-
-// Type returns the backend type as a string for metrics
-func (c *Client) Type() string {
- return string(types.BackendTypeFirecracker)
-}
-
-// Ensure Client implements Backend interface
-var _ types.Backend = (*Client)(nil)
diff --git a/go/apps/metald/internal/backend/firecracker/config_builder.go b/go/apps/metald/internal/backend/firecracker/config_builder.go
deleted file mode 100644
index 5edd3b53ce..0000000000
--- a/go/apps/metald/internal/backend/firecracker/config_builder.go
+++ /dev/null
@@ -1,163 +0,0 @@
-//go:build linux
-// +build linux
-
-package firecracker
-
-import (
- "context"
- "log/slog"
- "os"
- "path/filepath"
-
- sdk "github.com/firecracker-microvm/firecracker-go-sdk"
- "github.com/firecracker-microvm/firecracker-go-sdk/client/models"
- "github.com/unkeyed/unkey/go/apps/metald/internal/network"
- metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1"
- "golang.org/x/sys/unix"
-)
-
-// buildFirecrackerConfig builds the SDK configuration without jailer
-func (c *Client) buildFirecrackerConfig(ctx context.Context, vmID string, config *metaldv1.VmConfig, networkInfo *network.VMNetwork, preparedPaths map[string]string) sdk.Config {
- // For integrated jailer, we use absolute paths since we're not running inside chroot
- // The assets are still in the jailer directory structure for consistency
- jailerRoot := filepath.Join(
- c.jailerConfig.ChrootBaseDir,
- "firecracker",
- vmID,
- "root",
- )
-
- socketPath := "/firecracker.sock"
-
- // Determine kernel path - use prepared path if available, otherwise fallback to default
- kernelPath := filepath.Join(jailerRoot, "vmlinux")
- if len(preparedPaths) > 0 {
- // In a more sophisticated implementation, we'd track which asset ID
- // corresponds to which component (kernel vs rootfs). For now, we rely on the
- // assetmanager preparing files with standard names in the target directory.
- c.logger.LogAttrs(ctx, slog.LevelDebug, "using prepared asset paths",
- slog.String("vm_id", vmID),
- slog.Int("path_count", len(preparedPaths)),
- )
- }
-
- // Setup metrics FIFO for billaged
- metricsPath := c.setupMetricsFIFO(ctx, vmID, jailerRoot)
-
- // Setup console logging
- consoleLogPath := filepath.Join(jailerRoot, "console.log")
- consoleFifoPath := filepath.Join(jailerRoot, "console.fifo")
-
- // Use the kernel args as provided by the caller
- // Metadata handling is now done in BootVM
- kernelArgs := config.GetBoot()
-
- // Build the configuration
- cfg := c.buildSDKConfig(
- socketPath,
- consoleLogPath,
- consoleFifoPath,
- metricsPath,
- kernelPath,
- kernelArgs,
- config,
- jailerRoot,
- )
-
- // Add network interface
- if networkInfo != nil {
- c.addNetworkInterfaceToConfig(&cfg, networkInfo)
- }
-
- return cfg
-}
-
-// setupMetricsFIFO creates the metrics FIFO for billaged to read Firecracker stats
-func (c *Client) setupMetricsFIFO(ctx context.Context, vmID string, jailerRoot string) string {
- metricsPath := filepath.Join(jailerRoot, "metrics.fifo")
- hostMetricsPath := filepath.Join(jailerRoot, "metrics.fifo")
-
- // Create the metrics FIFO in the host filesystem
- if err := unix.Mkfifo(hostMetricsPath, 0o644); err != nil && !os.IsExist(err) {
- c.logger.ErrorContext(ctx, "failed to create metrics FIFO",
- slog.String("vm_id", vmID),
- slog.String("path", hostMetricsPath),
- slog.String("error", err.Error()),
- )
- } else {
- c.logger.InfoContext(ctx, "created metrics FIFO for billaged",
- slog.String("vm_id", vmID),
- slog.String("host_path", hostMetricsPath),
- slog.String("chroot_path", metricsPath),
- )
- }
-
- return metricsPath
-}
-
-// buildSDKConfig builds the base SDK configuration
-func (c *Client) buildSDKConfig(
- socketPath string,
- consoleLogPath string,
- consoleFifoPath string,
- metricsPath string,
- kernelPath string,
- kernelArgs string,
- config *metaldv1.VmConfig,
- jailerRoot string,
-) sdk.Config {
- // Create the console log file to capture guest output
- consoleLogFile, err := os.OpenFile(consoleLogPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644)
-
- var cfg sdk.Config
- if err != nil {
- // Fall back to LogPath only if console log file creation fails
- c.logger.Warn("failed to create console log file, falling back to LogPath only",
- slog.String("error", err.Error()),
- slog.String("console_log_path", consoleLogPath),
- )
- cfg = sdk.Config{
- SocketPath: socketPath,
- LogPath: consoleLogPath, // Captures Firecracker logs only
- LogLevel: "Debug",
- MetricsPath: metricsPath,
- KernelImagePath: kernelPath,
- KernelArgs: kernelArgs,
- MachineCfg: models.MachineConfiguration{
- VcpuCount: sdk.Int64(int64(config.GetVcpuCount())),
- MemSizeMib: sdk.Int64(536870912),
- Smt: sdk.Bool(false),
- },
- }
- } else {
- // Successful case - capture guest console output via FIFO
- cfg = sdk.Config{
- SocketPath: socketPath,
- LogPath: filepath.Join(jailerRoot, "firecracker.log"), // Firecracker's own logs
- LogFifo: consoleFifoPath, // FIFO for guest console output
- FifoLogWriter: consoleLogFile, // Writer to capture guest console to file
- LogLevel: "Debug",
- MetricsPath: metricsPath,
- KernelImagePath: kernelPath,
- KernelArgs: kernelArgs,
- MachineCfg: models.MachineConfiguration{
- VcpuCount: sdk.Int64(int64(config.GetVcpuCount())),
- MemSizeMib: sdk.Int64(536870912),
- Smt: sdk.Bool(false),
- },
- }
- }
-
- return cfg
-}
-
-// addNetworkInterfaceToConfig adds network interface to the Firecracker configuration
-func (c *Client) addNetworkInterfaceToConfig(cfg *sdk.Config, networkInfo *network.VMNetwork) {
- iface := sdk.NetworkInterface{
- StaticConfiguration: &sdk.StaticNetworkConfiguration{
- HostDevName: networkInfo.TapDevice,
- MacAddress: networkInfo.MacAddress,
- },
- }
- cfg.NetworkInterfaces = []sdk.NetworkInterface{iface}
-}
diff --git a/go/apps/metald/internal/backend/firecracker/create.go b/go/apps/metald/internal/backend/firecracker/create.go
deleted file mode 100644
index 97238e4c05..0000000000
--- a/go/apps/metald/internal/backend/firecracker/create.go
+++ /dev/null
@@ -1,63 +0,0 @@
-//go:build linux
-// +build linux
-
-package firecracker
-
-import (
- "context"
- "fmt"
- "log/slog"
- "os"
- "path/filepath"
-
- metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/metric"
- "go.opentelemetry.io/otel/trace"
-)
-
-// CreateVM creates a new VM using the SDK with integrated jailer
-func (c *Client) CreateVM(ctx context.Context, config *metaldv1.VmConfig) (string, error) {
- ctx, span := c.tracer.Start(ctx, "metald.firecracker.create_vm",
- trace.WithAttributes(
- attribute.Int("vcpus", int(config.GetVcpuCount())),
- attribute.Int64("memory_bytes", int64(config.GetMemorySizeMib())),
- ),
- )
- defer span.End()
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "creating VM",
- slog.String("vm_id", config.GetId()),
- slog.Int("vcpus", int(config.GetVcpuCount())),
- slog.Int64("memory_bytes", int64(config.GetMemorySizeMib())),
- )
-
- // Create VM directory
- vmDir := filepath.Join(c.baseDir, config.GetId())
- if err := os.MkdirAll(vmDir, 0o755); err != nil {
- return "", fmt.Errorf("failed to create VM directory: %w", err)
- }
-
- c.logger.DebugContext(ctx, "created VM directory",
- slog.String("directory", vmDir),
- )
-
- // Register the VM
- vm := &VM{
- ID: config.GetId(),
- Config: config,
- State: metaldv1.VmState_VM_STATE_CREATED,
- Machine: nil, // Will be set when we boot
- CancelFunc: nil, // Will be set when we boot
- }
-
- c.vmCreateCounter.Add(ctx, 1, metric.WithAttributes(
- attribute.String("status", "success"),
- ))
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "vm created",
- slog.String("vm_id", vm.ID),
- )
-
- return vm.ID, nil
-}
diff --git a/go/apps/metald/internal/backend/firecracker/delete.go b/go/apps/metald/internal/backend/firecracker/delete.go
deleted file mode 100644
index ef515bfa5d..0000000000
--- a/go/apps/metald/internal/backend/firecracker/delete.go
+++ /dev/null
@@ -1,90 +0,0 @@
-//go:build linux
-// +build linux
-
-package firecracker
-
-import (
- "context"
- "fmt"
- "log/slog"
- "os"
- "path/filepath"
-
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/metric"
- "go.opentelemetry.io/otel/trace"
-)
-
-// DeleteVM deletes a VM and cleans up all associated resources
-func (c *Client) DeleteVM(ctx context.Context, vmID string) error {
- ctx, span := c.tracer.Start(ctx, "metald.firecracker.delete_vm",
- trace.WithAttributes(attribute.String("vm_id", vmID)),
- )
- defer span.End()
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "deleting VM",
- slog.String("vm_id", vmID),
- )
-
- vm, exists := c.vmRegistry[vmID]
- if !exists {
- err := fmt.Errorf("vm %s not found", vmID)
- span.RecordError(err)
- c.vmErrorCounter.Add(ctx, 1, metric.WithAttributes(
- attribute.String("operation", "delete"),
- attribute.String("error", "vm_not_found"),
- ))
- return err
- }
-
- // Stop the VM if it's running
- if vm.Machine != nil {
- if err := vm.Machine.StopVMM(); err != nil {
- c.logger.WarnContext(ctx, "failed to stop VMM during delete",
- "vm_id", vmID,
- "error", err,
- )
- }
-
- // Cancel the VM context
- if vm.CancelFunc != nil {
- vm.CancelFunc()
- }
- }
-
- // Clean up VM directory
- vmDir := filepath.Join(c.baseDir, vmID)
- if err := os.RemoveAll(vmDir); err != nil {
- c.logger.WarnContext(ctx, "failed to remove VM directory",
- "vm_id", vmID,
- "path", vmDir,
- "error", err,
- )
- }
-
- // Clean up jailer chroot
- chrootPath := filepath.Join(c.jailerConfig.ChrootBaseDir, "firecracker", vmID)
- if err := os.RemoveAll(chrootPath); err != nil {
- c.logger.WarnContext(ctx, "failed to remove jailer chroot",
- "vm_id", vmID,
- "path", chrootPath,
- "error", err,
- )
- }
-
- // Release asset leases
- c.releaseAssetLeases(ctx, vmID)
-
- // Remove from registry
- delete(c.vmRegistry, vmID)
-
- c.vmDeleteCounter.Add(ctx, 1, metric.WithAttributes(
- attribute.String("status", "success"),
- ))
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "VM deleted successfully",
- slog.String("vm_id", vmID),
- )
-
- return nil
-}
diff --git a/go/apps/metald/internal/backend/firecracker/kernel_args.go b/go/apps/metald/internal/backend/firecracker/kernel_args.go
deleted file mode 100644
index f6334f8b17..0000000000
--- a/go/apps/metald/internal/backend/firecracker/kernel_args.go
+++ /dev/null
@@ -1,74 +0,0 @@
-//go:build linux
-// +build linux
-
-package firecracker
-
-import (
- "context"
- "fmt"
- "log/slog"
- "strings"
-
- "github.com/unkeyed/unkey/go/apps/metald/internal/network"
- builderv1 "github.com/unkeyed/unkey/go/gen/proto/builderd/v1"
-)
-
-const (
- // Static kernel parameters that never change for Firecracker VMs
- // AIDEV-BUSINESS_RULE: metald-init is always our init process
- staticKernelArgs = "console=ttyS0 reboot=k panic=1 pci=off init=/usr/bin/metald-init root=/dev/vda rw"
-
- // Debug parameters - only add when explicitly enabled
- debugKernelArgs = "loglevel=8 earlyprintk=serial,ttyS0,115200 debug ignore_loglevel printk.devkmsg=on"
-)
-
-// BuildKernelArgs generates kernel arguments for VM boot
-func (c *Client) BuildKernelArgs(ctx context.Context, networkInfo *network.VMNetwork, metadata *builderv1.ImageMetadata) string {
- args := []string{staticKernelArgs}
-
- // Format: ip=G::T:GM::GI:off
- // G = Guest IP, T = TAP IP, GM = Guest Mask, GI = Guest Interface
- ipArg := fmt.Sprintf("ip=%s::%s:%s:%s:off",
- networkInfo.IPAddress,
- networkInfo.Gateway,
- networkInfo.Netmask,
- "eth0",
- )
-
- args = append(args, ipArg)
-
- // Add container metadata if available (metald-init will use these)
- if metadata != nil {
- // Add container environment variables (if needed)
- for key, value := range metadata.GetEnv() {
- // Skip PATH and anything with spaces to avoid kernel cmdline parsing issues
- if key == "PATH" || strings.Contains(key, " ") || strings.Contains(value, " ") {
- continue
- }
- args = append(args, fmt.Sprintf("env.%s=%s", key, value))
- }
-
- // Add working directory if specified
- if workdir := metadata.GetWorkingDir(); workdir != "" {
- args = append(args, fmt.Sprintf("workdir=%s", workdir))
- }
- }
-
- finalArgs := strings.Join(args, " ")
-
- c.logger.LogAttrs(ctx, slog.LevelDebug, "built kernel args",
- slog.String("vm_id", getVMID(networkInfo)),
- slog.Bool("has_network", networkInfo != nil),
- slog.Bool("has_metadata", metadata != nil),
- slog.String("args", finalArgs),
- )
-
- return finalArgs
-}
-
-func getVMID(networkInfo *network.VMNetwork) string {
- if networkInfo != nil {
- return networkInfo.VMID
- }
- return "unknown"
-}
diff --git a/go/apps/metald/internal/backend/firecracker/metadata.go b/go/apps/metald/internal/backend/firecracker/metadata.go
deleted file mode 100644
index 41d4870192..0000000000
--- a/go/apps/metald/internal/backend/firecracker/metadata.go
+++ /dev/null
@@ -1,236 +0,0 @@
-//go:build linux
-// +build linux
-
-package firecracker
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "log/slog"
- "net"
- "os"
- "os/exec"
- "path/filepath"
- "strings"
-
- builderv1 "github.com/unkeyed/unkey/go/gen/proto/builderd/v1"
- metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1"
-)
-
-// loadContainerMetadata loads container metadata from the metadata file if it exists
-func (c *Client) loadContainerMetadata(ctx context.Context, rootfsPath string) (*builderv1.ImageMetadata, error) {
- // Load container metadata saved by builderd
- // The metadata file is named {buildID}.metadata.json and should be alongside the rootfs
-
- // Extract base name without extension
- baseName := strings.TrimSuffix(filepath.Base(rootfsPath), filepath.Ext(rootfsPath))
- metadataPath := filepath.Join(filepath.Dir(rootfsPath), baseName+".metadata.json")
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "looking for container metadata",
- slog.String("rootfs_path", rootfsPath),
- slog.String("metadata_path", metadataPath),
- )
-
- // Check if metadata file exists
- if _, err := os.Stat(metadataPath); os.IsNotExist(err) {
- // Fallback to check for metadata.json in VM chroot directory
- // When assets are copied to VM chroot by assetmanagerd, metadata file is renamed to metadata.json
- fallbackPath := filepath.Join(filepath.Dir(rootfsPath), "metadata.json")
- if _, err := os.Stat(fallbackPath); os.IsNotExist(err) {
- c.logger.LogAttrs(ctx, slog.LevelDebug, "no metadata file found in either location",
- slog.String("primary_path", metadataPath),
- slog.String("fallback_path", fallbackPath),
- )
- return nil, nil // No metadata is not an error
- }
- // Use fallback path
- metadataPath = fallbackPath
- c.logger.LogAttrs(ctx, slog.LevelInfo, "using fallback metadata path",
- slog.String("fallback_path", fallbackPath),
- )
- }
-
- // Read metadata file
- data, err := os.ReadFile(metadataPath)
- if err != nil {
- return nil, fmt.Errorf("failed to read metadata file: %w", err)
- }
-
- // Parse metadata
- var metadata builderv1.ImageMetadata
- if err := json.Unmarshal(data, &metadata); err != nil {
- return nil, fmt.Errorf("failed to parse metadata: %w", err)
- }
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "loaded container metadata",
- slog.String("image", metadata.GetOriginalImage()),
- slog.Int("entrypoint_len", len(metadata.GetEntrypoint())),
- slog.Int("cmd_len", len(metadata.GetCommand())),
- slog.Int("env_vars", len(metadata.GetEnv())),
- slog.Int("exposed_ports", len(metadata.GetExposedPorts())),
- )
-
- return &metadata, nil
-}
-
-// createContainerCmdFile creates /container.cmd file in VM chroot for metald-init
-func (c *Client) createContainerCmdFile(ctx context.Context, vmID string, metadata *builderv1.ImageMetadata) error {
- // Create container.cmd file containing the full command for metald-init
- // Combines entrypoint and command from container metadata into JSON array
-
- if metadata == nil {
- return fmt.Errorf("metadata is required")
- }
-
- // Build full command array: entrypoint + command
- var fullCmd []string
- fullCmd = append(fullCmd, metadata.GetEntrypoint()...)
- fullCmd = append(fullCmd, metadata.GetCommand()...)
-
- if len(fullCmd) == 0 {
- return fmt.Errorf("no entrypoint or command found in metadata")
- }
-
- // Convert to JSON
- cmdJSON, err := json.Marshal(fullCmd)
- if err != nil {
- return fmt.Errorf("failed to marshal command to JSON: %w", err)
- }
-
- // Write container.cmd into the rootfs.ext4 filesystem, not just chroot
- // Mount the rootfs.ext4 temporarily to inject the container.cmd file
- jailerRoot := filepath.Join(c.jailerConfig.ChrootBaseDir, "firecracker", vmID, "root")
- rootfsPath := filepath.Join(jailerRoot, "rootfs.ext4")
-
- // Create temporary mount point
- tmpMount := filepath.Join("/tmp", "rootfs-mount-"+vmID)
- if err := os.MkdirAll(tmpMount, 0o755); err != nil {
- return fmt.Errorf("failed to create temp mount dir: %w", err)
- }
- defer os.RemoveAll(tmpMount)
-
- // Mount the rootfs.ext4
- mountCmd := exec.Command("mount", "-o", "loop", rootfsPath, tmpMount)
- if err := mountCmd.Run(); err != nil {
- return fmt.Errorf("failed to mount rootfs: %w", err)
- }
- defer func() {
- umountCmd := exec.Command("umount", tmpMount)
- umountCmd.Run()
- }()
-
- // Write container.cmd into the mounted filesystem
- containerCmdPath := filepath.Join(tmpMount, "container.cmd")
- if err := os.WriteFile(containerCmdPath, cmdJSON, 0o600); err != nil {
- return fmt.Errorf("failed to write container.cmd to rootfs: %w", err)
- }
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "created container.cmd file",
- slog.String("vm_id", vmID),
- slog.String("path", containerCmdPath),
- slog.String("command", string(cmdJSON)),
- )
-
- return nil
-}
-
-// copyMetadataFilesForAssets copies metadata files alongside rootfs assets when using asset manager
-func (c *Client) copyMetadataFilesForAssets(ctx context.Context, vmID string, config *metaldv1.VmConfig, preparedPaths map[string]string, jailerRoot string) error {
- // When using asset manager, only rootfs files are copied, but we need metadata files too
- // This function finds the original metadata files and copies them to the jailer root
-
- disk := config.GetStorage()
- if !disk.GetIsRootDevice() || disk.GetPath() == "" {
- }
-
- // Find the original rootfs path before asset preparation
- originalRootfsPath := disk.GetPath()
-
- // Check if this disk was replaced by an asset
- var preparedRootfsPath string
- for _, path := range preparedPaths {
- if strings.HasSuffix(path, ".ext4") || strings.HasSuffix(path, ".img") {
- preparedRootfsPath = path
- break
- }
- }
-
- if preparedRootfsPath == "" {
- // No rootfs asset found, skip metadata copying
- }
-
- // Look for metadata file alongside the original rootfs
- originalDir := filepath.Dir(originalRootfsPath)
- originalBaseName := strings.TrimSuffix(filepath.Base(originalRootfsPath), filepath.Ext(originalRootfsPath))
- metadataSrcPath := filepath.Join(originalDir, originalBaseName+".metadata.json")
-
- // Check if metadata file exists
- if _, err := os.Stat(metadataSrcPath); os.IsNotExist(err) {
- c.logger.LogAttrs(ctx, slog.LevelDebug, "no metadata file found for asset",
- slog.String("vm_id", vmID),
- slog.String("original_rootfs", originalRootfsPath),
- slog.String("expected_metadata", metadataSrcPath),
- )
- }
-
- // Copy metadata file to jailer root with the same base name as the prepared rootfs
- preparedBaseName := strings.TrimSuffix(filepath.Base(preparedRootfsPath), filepath.Ext(preparedRootfsPath))
- metadataDstPath := filepath.Join(jailerRoot, preparedBaseName+".metadata.json")
-
- if err := copyFileWithOwnership(metadataSrcPath, metadataDstPath, int(c.jailerConfig.UID), int(c.jailerConfig.GID)); err != nil {
- c.logger.WarnContext(ctx, "failed to copy metadata file",
- slog.String("vm_id", vmID),
- slog.String("src", metadataSrcPath),
- slog.String("dst", metadataDstPath),
- slog.String("error", err.Error()),
- )
- return fmt.Errorf("failed to copy metadata file %s: %w", metadataSrcPath, err)
- }
-
- c.logger.InfoContext(ctx, "copied metadata file for asset",
- slog.String("vm_id", vmID),
- slog.String("src", metadataSrcPath),
- slog.String("dst", metadataDstPath),
- )
-
- return nil
-}
-
-// copyFileWithOwnership copies files with ownership
-func copyFileWithOwnership(src, dst string, uid, gid int) error {
- // Use cp command to handle large files efficiently
- cmd := exec.Command("cp", "-f", src, dst)
- if output, err := cmd.CombinedOutput(); err != nil {
- return fmt.Errorf("cp command failed: %w, output: %s", err, output)
- }
-
- // Set permissions
- if err := os.Chmod(dst, 0o644); err != nil {
- return fmt.Errorf("failed to set permissions on %s: %w", dst, err)
- }
-
- // Set ownership
- if err := os.Chown(dst, uid, gid); err != nil {
- // Log but don't fail - might work anyway
- return nil
- }
-
- return nil
-}
-
-// validateIPAddress validates an IP address to prevent command injection
-func validateIPAddress(ip string) error {
- if net.ParseIP(ip) == nil {
- return fmt.Errorf("invalid IP address: %s", ip)
- }
- return nil
-}
-
-// validatePortNumber validates a port number to prevent command injection
-func validatePortNumber(port int) error {
- if port < 1 || port > 65535 {
- return fmt.Errorf("invalid port number: %d, must be between 1-65535", port)
- }
- return nil
-}
diff --git a/go/apps/metald/internal/backend/firecracker/metrics.go b/go/apps/metald/internal/backend/firecracker/metrics.go
deleted file mode 100644
index 158872bd72..0000000000
--- a/go/apps/metald/internal/backend/firecracker/metrics.go
+++ /dev/null
@@ -1,152 +0,0 @@
-//go:build linux
-// +build linux
-
-package firecracker
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "log/slog"
- "os"
- "path/filepath"
-
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/types"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/trace"
-)
-
-// GetVMMetrics retrieves metrics for a specific VM
-func (c *Client) GetVMMetrics(ctx context.Context, vmID string) (*types.VMMetrics, error) {
- ctx, span := c.tracer.Start(ctx, "metald.firecracker.get_vm_metrics",
- trace.WithAttributes(attribute.String("vm_id", vmID)),
- )
- defer span.End()
-
- c.logger.LogAttrs(ctx, slog.LevelDebug, "retrieving VM metrics",
- slog.String("vm_id", vmID),
- )
-
- // Get the VM from registry
- vm, exists := c.vmRegistry[vmID]
- if !exists {
- err := fmt.Errorf("vm %s not found", vmID)
- span.RecordError(err)
- return nil, err
- }
-
- // Check if VM has a machine instance
- if vm.Machine == nil {
- err := fmt.Errorf("vm %s has no firecracker process", vmID)
- span.RecordError(err)
- return nil, err
- }
-
- // Calculate the jailer root path
- jailerRoot := filepath.Join(
- c.jailerConfig.ChrootBaseDir,
- "firecracker",
- vmID,
- "root",
- )
-
- // Read metrics from the FIFO
- metricsPath := filepath.Join(jailerRoot, "metrics.fifo")
- metrics, err := c.readFirecrackerMetrics(ctx, metricsPath)
- if err != nil {
- span.RecordError(err)
- return nil, fmt.Errorf("failed to read metrics: %w", err)
- }
-
- return metrics, nil
-}
-
-// readFirecrackerMetrics reads metrics from Firecracker's metrics FIFO
-func (c *Client) readFirecrackerMetrics(ctx context.Context, metricsPath string) (*types.VMMetrics, error) {
- c.logger.LogAttrs(ctx, slog.LevelDebug, "reading firecracker metrics",
- slog.String("metrics_path", metricsPath),
- )
-
- // Open the metrics FIFO (non-blocking read)
- file, err := os.OpenFile(metricsPath, os.O_RDONLY, 0)
- if err != nil {
- return nil, fmt.Errorf("failed to open metrics FIFO: %w", err)
- }
- defer file.Close()
-
- // Read all available data from the FIFO
- data, err := io.ReadAll(file)
- if err != nil && err != io.EOF {
- return nil, fmt.Errorf("failed to read metrics data: %w", err)
- }
-
- // If no data available, return empty metrics
- if len(data) == 0 {
- c.logger.LogAttrs(ctx, slog.LevelDebug, "no metrics data available",
- slog.String("metrics_path", metricsPath),
- )
- return &types.VMMetrics{}, nil
- }
-
- // Parse the JSON metrics
- var rawMetrics map[string]interface{}
- if err := json.Unmarshal(data, &rawMetrics); err != nil {
- return nil, fmt.Errorf("failed to parse metrics JSON: %w", err)
- }
-
- // Convert raw metrics to structured format
- metrics := c.parseRawMetrics(rawMetrics)
-
- c.logger.LogAttrs(ctx, slog.LevelDebug, "successfully read VM metrics",
- slog.Int64("cpu_time_ns", metrics.CpuTimeNanos),
- slog.Int64("memory_usage_bytes", metrics.MemoryUsageBytes),
- slog.Int64("disk_read_bytes", metrics.DiskReadBytes),
- slog.Int64("disk_write_bytes", metrics.DiskWriteBytes),
- slog.Int64("network_rx_bytes", metrics.NetworkRxBytes),
- slog.Int64("network_tx_bytes", metrics.NetworkTxBytes),
- )
-
- return metrics, nil
-}
-
-// parseRawMetrics converts raw Firecracker metrics to structured format
-func (c *Client) parseRawMetrics(raw map[string]interface{}) *types.VMMetrics {
- metrics := &types.VMMetrics{}
-
- // Extract CPU metrics
- if cpu, ok := raw["cpu"].(map[string]interface{}); ok {
- if cpuTimeNs, ok := cpu["cpu_time_ms"].(float64); ok {
- metrics.CpuTimeNanos = int64(cpuTimeNs)
- }
- }
-
- // Extract memory metrics
- if memory, ok := raw["memory"].(map[string]interface{}); ok {
- if memUsageBytes, ok := memory["memory_usage_bytes"].(float64); ok {
- metrics.MemoryUsageBytes = int64(memUsageBytes)
- }
- }
-
- // Extract disk metrics
- if disk, ok := raw["disk"].(map[string]interface{}); ok {
- if readBytes, ok := disk["read_bytes"].(float64); ok {
- metrics.DiskReadBytes = int64(readBytes)
- }
- if writeBytes, ok := disk["write_bytes"].(float64); ok {
- metrics.DiskWriteBytes = int64(writeBytes)
- }
- }
-
- // Extract network metrics
- if network, ok := raw["network"].(map[string]interface{}); ok {
- if rxBytes, ok := network["rx_bytes"].(float64); ok {
- metrics.NetworkRxBytes = int64(rxBytes)
- }
- if txBytes, ok := network["tx_bytes"].(float64); ok {
- metrics.NetworkTxBytes = int64(txBytes)
- }
- }
-
- return metrics
-}
diff --git a/go/apps/metald/internal/backend/firecracker/shutdown.go b/go/apps/metald/internal/backend/firecracker/shutdown.go
deleted file mode 100644
index 3dd6a75062..0000000000
--- a/go/apps/metald/internal/backend/firecracker/shutdown.go
+++ /dev/null
@@ -1,11 +0,0 @@
-//go:build linux
-// +build linux
-
-package firecracker
-
-import "context"
-
-// ShutdownVM gracefully shuts down a VM
-func (c *Client) ShutdownVM(ctx context.Context, vmID string) error {
- return c.ShutdownVMWithOptions(ctx, vmID, false, 30)
-}
diff --git a/go/apps/metald/internal/backend/firecracker/types.go b/go/apps/metald/internal/backend/firecracker/types.go
deleted file mode 100644
index 5bd9154b59..0000000000
--- a/go/apps/metald/internal/backend/firecracker/types.go
+++ /dev/null
@@ -1,78 +0,0 @@
-//go:build linux
-// +build linux
-
-package firecracker
-
-import (
- "context"
- "log/slog"
-
- sdk "github.com/firecracker-microvm/firecracker-go-sdk"
- "github.com/unkeyed/unkey/go/apps/metald/internal/assetmanager"
- "github.com/unkeyed/unkey/go/apps/metald/internal/config"
- "github.com/unkeyed/unkey/go/apps/metald/internal/jailer"
- "github.com/unkeyed/unkey/go/apps/metald/internal/network"
- assetv1 "github.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1"
- metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1"
- "go.opentelemetry.io/otel/metric"
- "go.opentelemetry.io/otel/trace"
-)
-
-// Client implements the Backend interface using firecracker-go-sdk
-// with integrated jailer functionality for secure VM isolation.
-type Client struct {
- logger *slog.Logger
- assetClient assetmanager.Client
- vmRegistry map[string]*VM
- vmAssetLeases map[string][]string // VM ID -> asset lease IDs
- jailer *jailer.Jailer
- jailerConfig *config.JailerConfig
- baseDir string
- tracer trace.Tracer
- meter metric.Meter
- vmCreateCounter metric.Int64Counter
- vmDeleteCounter metric.Int64Counter
- vmBootCounter metric.Int64Counter
- vmErrorCounter metric.Int64Counter
-}
-
-// VM represents a VM managed by the Firecracker backend
-type VM struct {
- ID string
- Config *metaldv1.VmConfig
- State metaldv1.VmState
- Machine *sdk.Machine
- NetworkInfo *network.VMNetwork
- CancelFunc context.CancelFunc
- AssetMapping *assetMapping // Asset mapping for lease acquisition
- AssetPaths map[string]string // Prepared asset paths
-}
-
-// assetRequirement represents a required asset for VM creation
-type assetRequirement struct {
- Type assetv1.AssetType
- Labels map[string]string
- Required bool
-}
-
-// assetMapping tracks the mapping between requirements and actual assets
-type assetMapping struct {
- requirements []assetRequirement
- assets map[string]*assetv1.Asset // requirement index -> asset
- assetIDs []string
- leaseIDs []string
-}
-
-func (am *assetMapping) AssetIDs() []string {
- return am.assetIDs
-}
-
-func (am *assetMapping) LeaseIDs() []string {
- return am.leaseIDs
-}
-
-// queryKey is used for grouping asset requirements by type and labels
-type queryKey struct {
- assetType assetv1.AssetType
- labels string // Serialized labels for grouping
-}
diff --git a/go/apps/metald/internal/backend/firecracker/validation.go b/go/apps/metald/internal/backend/firecracker/validation.go
deleted file mode 100644
index 79c15072ce..0000000000
--- a/go/apps/metald/internal/backend/firecracker/validation.go
+++ /dev/null
@@ -1,135 +0,0 @@
-//go:build linux
-// +build linux
-
-package firecracker
-
-import (
- "fmt"
- "net"
- "regexp"
-)
-
-// validatePath validates a file path to ensure it's safe to use
-func validatePath(path string) error {
- if path == "" {
- return fmt.Errorf("path cannot be empty")
- }
-
- // Check for path traversal attempts
- if containsPathTraversal(path) {
- return fmt.Errorf("path contains directory traversal: %s", path)
- }
-
- return nil
-}
-
-// containsPathTraversal checks if a path contains directory traversal patterns
-func containsPathTraversal(path string) bool {
- patterns := []string{
- "..",
- "..\\",
- "../",
- "..\\",
- }
-
- for _, pattern := range patterns {
- if regexp.MustCompile(pattern).MatchString(path) {
- return true
- }
- }
- return false
-}
-
-// validateMAC validates a MAC address format
-func validateMAC(mac string) error {
- if mac == "" {
- return fmt.Errorf("MAC address cannot be empty")
- }
-
- // Standard MAC address format: XX:XX:XX:XX:XX:XX
- macRegex := regexp.MustCompile(`^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$`)
- if !macRegex.MatchString(mac) {
- return fmt.Errorf("invalid MAC address format: %s", mac)
- }
-
- return nil
-}
-
-// validateCIDR validates a CIDR notation
-func validateCIDR(cidr string) error {
- _, _, err := net.ParseCIDR(cidr)
- if err != nil {
- return fmt.Errorf("invalid CIDR notation: %s", cidr)
- }
- return nil
-}
-
-// validateMemorySize validates memory size in bytes
-func validateMemorySize(sizeBytes int64) error {
- const (
- minMemory = 128 * 1024 * 1024 // 128 MB minimum
- maxMemory = 512 * 1024 * 1024 * 1024 // 512 GB maximum
- )
-
- if sizeBytes < minMemory {
- return fmt.Errorf("memory size %d bytes is below minimum of %d bytes (128MB)", sizeBytes, minMemory)
- }
-
- if sizeBytes > maxMemory {
- return fmt.Errorf("memory size %d bytes exceeds maximum of %d bytes (512GB)", sizeBytes, maxMemory)
- }
-
- // Check if memory is a multiple of 1MB (Firecracker requirement)
- if sizeBytes%(1024*1024) != 0 {
- return fmt.Errorf("memory size must be a multiple of 1MB")
- }
-
- return nil
-}
-
-// validateVCPUCount validates the number of vCPUs
-func validateVCPUCount(count int32) error {
- if count < 1 {
- return fmt.Errorf("vCPU count must be at least 1")
- }
-
- if count > 32 {
- return fmt.Errorf("vCPU count %d exceeds maximum of 32", count)
- }
-
- // Firecracker works best with power-of-2 vCPU counts
- if !isPowerOfTwo(int(count)) && count != 1 {
- // This is a warning, not an error
- // Log it but don't fail
- }
-
- return nil
-}
-
-// isPowerOfTwo checks if a number is a power of two
-func isPowerOfTwo(n int) bool {
- return n > 0 && (n&(n-1)) == 0
-}
-
-// validateDiskPath validates a disk image path
-func validateDiskPath(path string) error {
- if err := validatePath(path); err != nil {
- return err
- }
-
- // Check for supported disk image extensions
- validExtensions := []string{".ext4", ".img", ".raw", ".qcow2"}
- hasValidExt := false
- for _, ext := range validExtensions {
- if regexp.MustCompile(ext + "$").MatchString(path) {
- hasValidExt = true
- break
- }
- }
-
- if !hasValidExt {
- return fmt.Errorf("disk path %s does not have a supported extension (.ext4, .img, .raw, .qcow2)", path)
- }
-
- return nil
-}
diff --git a/go/apps/metald/internal/backend/firecracker/vm_operations.go b/go/apps/metald/internal/backend/firecracker/vm_operations.go
deleted file mode 100644
index 2ffc7c62a9..0000000000
--- a/go/apps/metald/internal/backend/firecracker/vm_operations.go
+++ /dev/null
@@ -1,227 +0,0 @@
-//go:build linux
-// +build linux
-
-package firecracker
-
-import (
- "context"
- "fmt"
- "log/slog"
- "time"
-
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/types"
- metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/trace"
-)
-
-// ShutdownVMWithOptions shuts down a VM with configurable options
-func (c *Client) ShutdownVMWithOptions(ctx context.Context, vmID string, force bool, timeoutSeconds int32) error {
- ctx, span := c.tracer.Start(ctx, "metald.firecracker.shutdown_vm",
- trace.WithAttributes(
- attribute.String("vm_id", vmID),
- attribute.Bool("force", force),
- attribute.Int("timeout_seconds", int(timeoutSeconds)),
- ),
- )
- defer span.End()
-
- vm, exists := c.vmRegistry[vmID]
- if !exists {
- err := fmt.Errorf("vm %s not found", vmID)
- span.RecordError(err)
- return err
- }
-
- // Validate VM state before shutdown operation
- if vm.State != metaldv1.VmState_VM_STATE_RUNNING {
- err := fmt.Errorf("vm %s is in %s state, can only shutdown VMs in RUNNING state", vmID, vm.State.String())
- span.RecordError(err)
- return err
- }
-
- if vm.Machine == nil {
- return fmt.Errorf("vm %s firecracker process not available", vmID)
- }
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "shutting down VM",
- slog.String("vm_id", vmID),
- slog.String("current_state", vm.State.String()),
- slog.Bool("force", force),
- slog.Int("timeout_seconds", int(timeoutSeconds)),
- )
-
- // Create a timeout context
- shutdownCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
- defer cancel()
-
- if force {
- // Force shutdown by pausing the VM to preserve the socket for resume
- // Note: Using PauseVM instead of StopVMM to allow resume operations
- if err := vm.Machine.PauseVM(shutdownCtx); err != nil {
- span.RecordError(err)
- return fmt.Errorf("failed to force shutdown VM: %w", err)
- }
- } else {
- // Try graceful shutdown first by pausing the VM
- // Note: Using PauseVM instead of Shutdown to preserve the firecracker process and socket
- if err := vm.Machine.PauseVM(shutdownCtx); err != nil {
- c.logger.WarnContext(ctx, "graceful shutdown failed",
- "vm_id", vmID,
- "error", err,
- )
- span.RecordError(err)
- return fmt.Errorf("failed to shutdown VM: %w", err)
- }
- }
-
- // Note: The firecracker process remains running to allow resume operations
-
- // Update state
- vm.State = metaldv1.VmState_VM_STATE_SHUTDOWN
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "VM shutdown successfully",
- slog.String("vm_id", vmID),
- )
-
- return nil
-}
-
-// PauseVM pauses a running VM
-func (c *Client) PauseVM(ctx context.Context, vmID string) error {
- ctx, span := c.tracer.Start(ctx, "metald.firecracker.pause_vm",
- trace.WithAttributes(attribute.String("vm_id", vmID)),
- )
- defer span.End()
-
- vm, exists := c.vmRegistry[vmID]
- if !exists {
- err := fmt.Errorf("vm %s not found", vmID)
- span.RecordError(err)
- return err
- }
-
- // Validate VM state before pause operation
- if vm.State != metaldv1.VmState_VM_STATE_RUNNING {
- err := fmt.Errorf("vm %s is in %s state, can only pause VMs in RUNNING state", vmID, vm.State.String())
- span.RecordError(err)
- return err
- }
-
- if vm.Machine == nil {
- return fmt.Errorf("vm %s firecracker process not available", vmID)
- }
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "pausing VM",
- slog.String("vm_id", vmID),
- slog.String("current_state", vm.State.String()),
- )
-
- if err := vm.Machine.PauseVM(ctx); err != nil {
- span.RecordError(err)
- return fmt.Errorf("failed to pause VM: %w", err)
- }
-
- vm.State = metaldv1.VmState_VM_STATE_PAUSED
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "VM paused successfully",
- slog.String("vm_id", vmID),
- )
-
- return nil
-}
-
-// ResumeVM resumes a paused or shutdown VM
-func (c *Client) ResumeVM(ctx context.Context, vmID string) error {
- ctx, span := c.tracer.Start(ctx, "metald.firecracker.resume_vm",
- trace.WithAttributes(attribute.String("vm_id", vmID)),
- )
- defer span.End()
-
- vm, exists := c.vmRegistry[vmID]
- if !exists {
- err := fmt.Errorf("vm %s not found", vmID)
- span.RecordError(err)
- return err
- }
-
- // Validate VM state before resume operation - allow both PAUSED and SHUTDOWN
- if vm.State != metaldv1.VmState_VM_STATE_PAUSED && vm.State != metaldv1.VmState_VM_STATE_SHUTDOWN {
- err := fmt.Errorf("vm %s is in %s state, can only resume VMs in PAUSED or SHUTDOWN state", vmID, vm.State.String())
- span.RecordError(err)
- return err
- }
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "resuming VM",
- slog.String("vm_id", vmID),
- slog.String("current_state", vm.State.String()),
- )
-
- if err := vm.Machine.ResumeVM(ctx); err != nil {
- span.RecordError(err)
- return fmt.Errorf("failed to resume VM: %w", err)
- }
-
- vm.State = metaldv1.VmState_VM_STATE_RUNNING
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "VM resumed successfully",
- slog.String("vm_id", vmID),
- )
-
- return nil
-}
-
-// RebootVM reboots a running VM
-func (c *Client) RebootVM(ctx context.Context, vmID string) error {
- ctx, span := c.tracer.Start(ctx, "metald.firecracker.reboot_vm",
- trace.WithAttributes(attribute.String("vm_id", vmID)),
- )
- defer span.End()
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "rebooting VM",
- slog.String("vm_id", vmID),
- )
-
- // Shutdown the VM
- if err := c.ShutdownVMWithOptions(ctx, vmID, false, 30); err != nil {
- span.RecordError(err)
- return fmt.Errorf("failed to shutdown VM for reboot: %w", err)
- }
-
- // Wait a moment
- time.Sleep(1 * time.Second)
-
- // Resume the VM (since we paused it in shutdown)
- if err := c.ResumeVM(ctx, vmID); err != nil {
- span.RecordError(err)
- return fmt.Errorf("failed to resume VM after shutdown: %w", err)
- }
-
- c.logger.LogAttrs(ctx, slog.LevelInfo, "VM rebooted successfully",
- slog.String("vm_id", vmID),
- )
-
- return nil
-}
-
-// GetVMInfo returns information about a VM
-func (c *Client) GetVMInfo(ctx context.Context, vmID string) (*types.VMInfo, error) {
- _, span := c.tracer.Start(ctx, "metald.firecracker.get_vm_info",
- trace.WithAttributes(attribute.String("vm_id", vmID)),
- )
- defer span.End()
-
- vm, exists := c.vmRegistry[vmID]
- if !exists {
- err := fmt.Errorf("vm %s not found", vmID)
- span.RecordError(err)
- return nil, err
- }
-
- info := &types.VMInfo{
- Config: vm.Config,
- State: vm.State,
- }
-
- return info, nil
-}
diff --git a/go/apps/metald/internal/backend/kubernetes/kubernetes.go b/go/apps/metald/internal/backend/kubernetes/kubernetes.go
deleted file mode 100644
index 69cfa9a886..0000000000
--- a/go/apps/metald/internal/backend/kubernetes/kubernetes.go
+++ /dev/null
@@ -1,516 +0,0 @@
-package kubernetes
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "os"
- "strings"
- "time"
-
- batchv1 "k8s.io/api/batch/v1"
- corev1 "k8s.io/api/core/v1"
- "k8s.io/apimachinery/pkg/api/resource"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/util/intstr"
- "k8s.io/client-go/kubernetes"
- "k8s.io/client-go/rest"
-
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/types"
- metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1"
- "github.com/unkeyed/unkey/go/pkg/otel/logging"
- "github.com/unkeyed/unkey/go/pkg/ptr"
-)
-
-// Compile-time interface checks
-var _ types.Backend = (*Backend)(nil)
-var _ types.VMListProvider = (*Backend)(nil)
-
-// Backend implements types.Backend using Kubernetes pods
-type Backend struct {
- logger logging.Logger
- clientset *kubernetes.Clientset
- namespace string
- ttlSeconds int32 // TTL for auto-termination (0 = no TTL)
-}
-
-// New creates a new Kubernetes backend
-func New(logger logging.Logger) (*Backend, error) {
- // Create in-cluster config
- config, err := rest.InClusterConfig()
- if err != nil {
- return nil, fmt.Errorf("failed to create in-cluster config: %w", err)
- }
-
- // Create clientset
- clientset, err := kubernetes.NewForConfig(config)
- if err != nil {
- return nil, fmt.Errorf("failed to create Kubernetes clientset: %w", err)
- }
-
- // Get namespace from service account or use default
- namespace := "default"
- if ns, err := readNamespaceFromServiceAccount(); err == nil && ns != "" {
- namespace = ns
- }
-
- return &Backend{
- logger: logger.With("backend", "kubernetes"),
- clientset: clientset,
- namespace: namespace,
- ttlSeconds: 7_200, // 2 hours default TTL for auto-cleanup
- }, nil
-}
-
-// CreateVM creates a new Kubernetes pod as a VM
-func (b *Backend) CreateVM(ctx context.Context, config *metaldv1.VmConfig) (string, error) {
- // Use the provided VM ID from config
- vmID := config.Id
- if vmID == "" {
- return "", fmt.Errorf("VM ID is required")
- }
-
- b.logger.Info("creating Kubernetes VM",
- "vm_id", vmID,
- "image", config.Boot,
- "namespace", b.namespace)
-
- // Use boot configuration as container image
- imageName := config.Boot
- if imageName == "" {
- return "", fmt.Errorf("boot image is required")
- }
-
- // Parse network configuration
- var networkInfo map[string]string
- if config.NetworkConfig != "" {
- if err := json.Unmarshal([]byte(config.NetworkConfig), &networkInfo); err != nil {
- return "", fmt.Errorf("failed to parse network config: %w", err)
- }
- }
-
- // Sanitize VM ID for Kubernetes resources (RFC 1123)
- sanitizedVMID := strings.ToLower(vmID)
- podName := sanitizedVMID
- serviceName := sanitizedVMID
-
- // Use Jobs with TTL for auto-cleanup
- useJob := b.ttlSeconds > 0
-
- // Convert VM config to Kubernetes resources
- resources := corev1.ResourceRequirements{
- Requests: corev1.ResourceList{
- corev1.ResourceCPU: resource.MustParse("100m"),
- corev1.ResourceMemory: resource.MustParse("128Mi"),
- },
- Limits: corev1.ResourceList{
- corev1.ResourceCPU: resource.MustParse("1000m"),
- corev1.ResourceMemory: resource.MustParse("1Gi"),
- },
- }
-
- // Apply VM configuration to resources if specified
- if config.VcpuCount > 0 {
- cpuRequest := fmt.Sprintf("%dm", config.VcpuCount*100) // 100m per vCPU as request
- cpuLimit := fmt.Sprintf("%d", config.VcpuCount) // 1 CPU per vCPU as limit
- resources.Requests[corev1.ResourceCPU] = resource.MustParse(cpuRequest)
- resources.Limits[corev1.ResourceCPU] = resource.MustParse(cpuLimit)
- }
-
- if config.MemorySizeMib > 0 {
- memRequest := fmt.Sprintf("%dMi", config.MemorySizeMib/2) // Half as request
- memLimit := fmt.Sprintf("%dMi", config.MemorySizeMib) // Full as limit
- resources.Requests[corev1.ResourceMemory] = resource.MustParse(memRequest)
- resources.Limits[corev1.ResourceMemory] = resource.MustParse(memLimit)
- }
-
- // Create pod template with optional specific IP
- podAnnotations := map[string]string{}
- podLabels := map[string]string{
- "unkey.vm.id": vmID,
- "unkey.managed.by": "metald",
- }
- podTemplate := corev1.PodTemplateSpec{
- ObjectMeta: metav1.ObjectMeta{
- Labels: podLabels,
- Annotations: podAnnotations,
- },
- Spec: corev1.PodSpec{
- RestartPolicy: corev1.RestartPolicyNever, // Required for Jobs
- Containers: []corev1.Container{
- {
- Name: "vm",
- Image: imageName,
- Ports: []corev1.ContainerPort{
- {
- ContainerPort: 8080,
- Protocol: corev1.ProtocolTCP,
- },
- },
- Resources: resources,
- },
- },
- },
- }
-
- // Create owner reference for service cleanup
- var ownerRef *metav1.OwnerReference
-
- // This creates a job to kill the VM after the given TTL
- if useJob {
- // Create Job with TTL for auto-termination
- job := &batchv1.Job{
- ObjectMeta: metav1.ObjectMeta{
- Name: sanitizedVMID,
- Namespace: b.namespace,
- Labels: map[string]string{
- "unkey.vm.id": vmID,
- "unkey.managed.by": "metald",
- },
- },
- Spec: batchv1.JobSpec{
- TTLSecondsAfterFinished: &b.ttlSeconds, // Auto-cleanup after completion
- ActiveDeadlineSeconds: ptr.P(int64(b.ttlSeconds)), // Max runtime
- Parallelism: ptr.P(int32(1)), // One pod
- Completions: ptr.P(int32(1)), // One completion
- Template: podTemplate,
- },
- }
-
- createdJob, err := b.clientset.BatchV1().Jobs(b.namespace).Create(ctx, job, metav1.CreateOptions{})
- if err != nil {
- return "", fmt.Errorf("failed to create job: %w", err)
- }
-
- // Set owner reference so service gets deleted with job
- ownerRef = &metav1.OwnerReference{
- APIVersion: "batch/v1",
- Kind: "Job",
- Name: createdJob.Name,
- UID: createdJob.UID,
- Controller: ptr.P(true),
- }
- } else {
- // Create regular Pod
- pod := &corev1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: podName,
- Namespace: b.namespace,
- Labels: map[string]string{
- "unkey.vm.id": vmID,
- "unkey.managed.by": "metald",
- },
- },
- Spec: podTemplate.Spec,
- }
-
- createdPod, err := b.clientset.CoreV1().Pods(b.namespace).Create(ctx, pod, metav1.CreateOptions{})
- if err != nil {
- return "", fmt.Errorf("failed to create pod: %w", err)
- }
-
- // Set owner reference so service gets deleted with pod
- ownerRef = &metav1.OwnerReference{
- APIVersion: "v1",
- Kind: "Pod",
- Name: createdPod.Name,
- UID: createdPod.UID,
- Controller: ptr.P(true),
- }
- }
-
- // Create service to expose the VM with specific ClusterIP
- serviceSpec := corev1.ServiceSpec{
- Type: corev1.ServiceTypeClusterIP, // Use ClusterIP for internal communication
- Selector: map[string]string{
- "unkey.vm.id": vmID,
- },
- Ports: []corev1.ServicePort{
- {
- Port: 8080,
- TargetPort: intstr.FromInt(8080),
- Protocol: corev1.ProtocolTCP,
- },
- },
- }
-
- // Set specific ClusterIP if provided in network config
- if allocatedIP, ok := networkInfo["allocated_ip"]; ok && allocatedIP != "" {
- serviceSpec.ClusterIP = allocatedIP
- b.logger.Info("setting service ClusterIP to allocated IP",
- "vm_id", vmID,
- "cluster_ip", allocatedIP)
- }
-
- service := &corev1.Service{
- ObjectMeta: metav1.ObjectMeta{
- Name: serviceName,
- Namespace: b.namespace,
- Labels: map[string]string{
- "unkey.vm.id": vmID,
- "unkey.managed.by": "metald",
- },
- Annotations: map[string]string{
- // Add annotation to help gateway discovery
- "unkey.deployment.id": networkInfo["deployment_id"],
- },
- },
- Spec: serviceSpec,
- }
-
- // Add owner reference so service is garbage collected with the job/pod
- if ownerRef != nil {
- service.ObjectMeta.OwnerReferences = []metav1.OwnerReference{*ownerRef}
- }
-
- _, err := b.clientset.CoreV1().Services(b.namespace).Create(ctx, service, metav1.CreateOptions{})
- if err != nil {
- // Clean up pod/job if service creation fails
- if useJob {
- propagationPolicy := metav1.DeletePropagationBackground
- b.clientset.BatchV1().Jobs(b.namespace).Delete(ctx, sanitizedVMID, metav1.DeleteOptions{
- PropagationPolicy: &propagationPolicy,
- })
- } else {
- propagationPolicy := metav1.DeletePropagationBackground
- b.clientset.CoreV1().Pods(b.namespace).Delete(ctx, podName, metav1.DeleteOptions{
- PropagationPolicy: &propagationPolicy,
- })
- }
-
- return "", fmt.Errorf("failed to create service: %w", err)
- }
-
- b.logger.Info("Kubernetes VM created",
- "vm_id", vmID,
- "pod_name", podName,
- "service_name", serviceName,
- )
-
- return vmID, nil
-}
-
-// DeleteVM removes a Kubernetes pod VM
-func (b *Backend) DeleteVM(ctx context.Context, vmID string) error {
- // Sanitize VM ID for Kubernetes resources
- sanitizedVMID := strings.ToLower(vmID)
-
- // Delete service
- propagationPolicy := metav1.DeletePropagationBackground
- if err := b.clientset.CoreV1().Services(b.namespace).Delete(ctx, sanitizedVMID, metav1.DeleteOptions{
- PropagationPolicy: &propagationPolicy,
- }); err != nil {
- b.logger.Error("failed to delete service",
- "vm_id", vmID,
- "error", err)
- }
-
- // Try deleting as job first, then as pod
- if err := b.clientset.BatchV1().Jobs(b.namespace).Delete(ctx, sanitizedVMID, metav1.DeleteOptions{
- PropagationPolicy: &propagationPolicy,
- }); err != nil {
- // Not a job, try deleting as pod
- if err := b.clientset.CoreV1().Pods(b.namespace).Delete(ctx, sanitizedVMID, metav1.DeleteOptions{}); err != nil {
- return fmt.Errorf("failed to delete pod: %w", err)
- }
- }
-
- b.logger.Info("Kubernetes VM deleted", "vm_id", vmID)
- return nil
-}
-
-// BootVM starts a Kubernetes pod VM (no-op since pods auto-start)
-func (b *Backend) BootVM(ctx context.Context, vmID string) error {
- // Kubernetes pods start automatically, nothing to do
- b.logger.Info("Kubernetes VM boot requested", "vm_id", vmID)
- return nil
-}
-
-// ShutdownVM gracefully stops a Kubernetes pod VM
-func (b *Backend) ShutdownVM(ctx context.Context, vmID string) error {
- return b.ShutdownVMWithOptions(ctx, vmID, false, 30)
-}
-
-// ShutdownVMWithOptions gracefully stops a Kubernetes pod VM with options
-func (b *Backend) ShutdownVMWithOptions(ctx context.Context, vmID string, force bool, timeoutSeconds int32) error {
- gracePeriodSeconds := int64(timeoutSeconds)
- deleteOptions := metav1.DeleteOptions{
- GracePeriodSeconds: &gracePeriodSeconds,
- }
-
- if force {
- // Immediate deletion
- gracePeriodSeconds = 0
- deleteOptions.GracePeriodSeconds = &gracePeriodSeconds
- }
-
- // Sanitize VM ID for Kubernetes resources
- sanitizedVMID := strings.ToLower(vmID)
-
- // Try deleting as job first, then as pod
- propagationPolicy := metav1.DeletePropagationBackground
- deleteOptions.PropagationPolicy = &propagationPolicy
- if err := b.clientset.BatchV1().Jobs(b.namespace).Delete(ctx, sanitizedVMID, deleteOptions); err != nil {
- // Not a job, try deleting as pod
- if err := b.clientset.CoreV1().Pods(b.namespace).Delete(ctx, sanitizedVMID, deleteOptions); err != nil {
- return fmt.Errorf("failed to delete pod: %w", err)
- }
- }
-
- b.logger.Info("Kubernetes VM shutdown", "vm_id", vmID, "force", force)
- return nil
-}
-
-// PauseVM pauses a Kubernetes pod VM (not supported - return error)
-func (b *Backend) PauseVM(ctx context.Context, vmID string) error {
- return fmt.Errorf("pause operation not supported by Kubernetes backend")
-}
-
-// ResumeVM resumes a paused Kubernetes pod VM (not supported - return error)
-func (b *Backend) ResumeVM(ctx context.Context, vmID string) error {
- return fmt.Errorf("resume operation not supported by Kubernetes backend")
-}
-
-// RebootVM restarts a Kubernetes pod VM
-func (b *Backend) RebootVM(ctx context.Context, vmID string) error {
- // Sanitize VM ID for Kubernetes resources
- sanitizedVMID := strings.ToLower(vmID)
-
- // For Kubernetes, we delete the pod/job (it will be recreated if desired)
- propagationPolicy := metav1.DeletePropagationBackground
- if err := b.clientset.BatchV1().Jobs(b.namespace).Delete(ctx, sanitizedVMID, metav1.DeleteOptions{
- PropagationPolicy: &propagationPolicy,
- }); err != nil {
- // Not a job, try deleting as pod
- if err := b.clientset.CoreV1().Pods(b.namespace).Delete(ctx, sanitizedVMID, metav1.DeleteOptions{}); err != nil {
- return fmt.Errorf("failed to delete pod for reboot: %w", err)
- }
- }
-
- b.logger.Info("Kubernetes VM rebooted", "vm_id", vmID)
- return nil
-}
-
-// GetVMInfo retrieves current VM state and configuration
-func (b *Backend) GetVMInfo(ctx context.Context, vmID string) (*types.VMInfo, error) {
- // Sanitize VM ID for Kubernetes resources
- sanitizedVMID := strings.ToLower(vmID)
-
- // First try to get the pod by the VM ID (direct pod creation)
- pod, err := b.clientset.CoreV1().Pods(b.namespace).Get(ctx, sanitizedVMID, metav1.GetOptions{})
- if err != nil {
- // If not found, try to find pod created by the job using labels
- pods, listErr := b.clientset.CoreV1().Pods(b.namespace).List(ctx, metav1.ListOptions{
- LabelSelector: fmt.Sprintf("unkey.vm.id=%s", vmID),
- })
-
- if listErr != nil {
- return nil, fmt.Errorf("failed to find pod for VM: %w", listErr)
- }
-
- if len(pods.Items) == 0 {
- return nil, fmt.Errorf("no pod found for VM %s", vmID)
- }
- // Use the first pod found (should only be one)
- pod = &pods.Items[0]
- }
-
- // Determine state based on pod phase
- state := metaldv1.VmState_VM_STATE_UNSPECIFIED
- switch pod.Status.Phase {
- case "Pending":
- state = metaldv1.VmState_VM_STATE_CREATED
- case "Running":
- state = metaldv1.VmState_VM_STATE_RUNNING
- case "Succeeded", "Failed":
- state = metaldv1.VmState_VM_STATE_SHUTDOWN
- }
-
- // Reconstruct config from pod spec
- config := &metaldv1.VmConfig{
- Id: vmID,
- }
- if len(pod.Spec.Containers) > 0 {
- config.Boot = pod.Spec.Containers[0].Image
- }
-
- return &types.VMInfo{
- Config: config,
- State: state,
- }, nil
-}
-
-// GetVMMetrics retrieves current VM resource usage metrics
-func (b *Backend) GetVMMetrics(ctx context.Context, vmID string) (*types.VMMetrics, error) {
- return &types.VMMetrics{
- Timestamp: time.Now(),
- DiskReadBytes: 0, // Not available from standard K8s metrics
- DiskWriteBytes: 0, // Not available from standard K8s metrics
- NetworkRxBytes: 0, // Not available from standard K8s metrics
- NetworkTxBytes: 0, // Not available from standard K8s metrics
- }, nil
-}
-
-// Ping checks if the Kubernetes API server is healthy and responsive
-func (b *Backend) Ping(ctx context.Context) error {
- return nil
-}
-
-// ListVMs returns a list of all VMs managed by this backend
-func (b *Backend) ListVMs() []types.ListableVMInfo {
- ctx := context.Background()
- pods, err := b.clientset.CoreV1().Pods(b.namespace).List(ctx, metav1.ListOptions{
- LabelSelector: "unkey.managed.by=metald",
- })
- if err != nil {
- b.logger.Error("failed to list pods", "error", err)
- return []types.ListableVMInfo{}
- }
-
- vms := make([]types.ListableVMInfo, 0, len(pods.Items))
- for _, pod := range pods.Items {
- vmID := pod.Labels["unkey.vm.id"]
- if vmID == "" {
- vmID = pod.Name
- }
-
- // Determine state from pod phase
- state := metaldv1.VmState_VM_STATE_CREATED
- switch pod.Status.Phase {
- case "Running":
- state = metaldv1.VmState_VM_STATE_RUNNING
- case "Succeeded", "Failed":
- state = metaldv1.VmState_VM_STATE_SHUTDOWN
- }
-
- config := &metaldv1.VmConfig{
- Id: vmID,
- }
- if len(pod.Spec.Containers) > 0 {
- config.Boot = pod.Spec.Containers[0].Image
- }
-
- vms = append(vms, types.ListableVMInfo{
- ID: vmID,
- State: state,
- Config: config,
- })
- }
-
- return vms
-}
-
-// Type returns the backend type as a string for metrics
-func (b *Backend) Type() string {
- return string(types.BackendTypeKubernetes)
-}
-
-// Helper methods
-func readNamespaceFromServiceAccount() (string, error) {
- data, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
- if err != nil {
- return "", err
- }
-
- return strings.TrimSpace(string(data)), nil
-}
diff --git a/go/apps/metald/internal/backend/types/backend.go b/go/apps/metald/internal/backend/types/backend.go
deleted file mode 100644
index 82073430b9..0000000000
--- a/go/apps/metald/internal/backend/types/backend.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package types
-
-import (
- "context"
- "time"
-
- metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1"
-)
-
-// Backend defines the interface for hypervisor backends
-type Backend interface {
- // CreateVM creates a new VM instance with the given configuration
- CreateVM(ctx context.Context, config *metaldv1.VmConfig) (string, error)
-
- // DeleteVM removes a VM instance
- DeleteVM(ctx context.Context, vmID string) error
-
- // BootVM starts a created VM
- BootVM(ctx context.Context, vmID string) error
-
- // ShutdownVM gracefully stops a running VM
- ShutdownVM(ctx context.Context, vmID string) error
-
- // ShutdownVMWithOptions gracefully stops a running VM with force and timeout options
- ShutdownVMWithOptions(ctx context.Context, vmID string, force bool, timeoutSeconds int32) error
-
- // PauseVM pauses a running VM
- PauseVM(ctx context.Context, vmID string) error
-
- // ResumeVM resumes a paused VM
- ResumeVM(ctx context.Context, vmID string) error
-
- // RebootVM restarts a running VM
- RebootVM(ctx context.Context, vmID string) error
-
- // GetVMInfo retrieves current VM state and configuration
- GetVMInfo(ctx context.Context, vmID string) (*VMInfo, error)
-
- // GetVMMetrics retrieves current VM resource usage metrics
- GetVMMetrics(ctx context.Context, vmID string) (*VMMetrics, error)
-
- // Ping checks if the backend is healthy and responsive
- Ping(ctx context.Context) error
-
- // Type returns the backend type as a string for metrics
- Type() string
-}
-
-// VMInfo contains VM state and configuration information
-type VMInfo struct {
- Config *metaldv1.VmConfig
- State metaldv1.VmState
-}
-
-// ListableVMInfo represents VM information for listing operations
-type ListableVMInfo struct {
- ID string
- State metaldv1.VmState
- Config *metaldv1.VmConfig
-}
-
-// VMListProvider defines interface for backends that support VM listing
-type VMListProvider interface {
- ListVMs() []ListableVMInfo
-}
-
-// BackendType represents the type of hypervisor backend
-type BackendType string
-
-const (
- BackendTypeFirecracker BackendType = "firecracker"
- BackendTypeDocker BackendType = "docker"
- BackendTypeKubernetes BackendType = "k8s"
-)
-
-func (s *BackendType) String() string {
- return string(*s)
-}
-
-// VMMetrics contains VM resource usage data for billing
-type VMMetrics struct {
- Timestamp time.Time `json:"timestamp"`
- CpuTimeNanos int64 `json:"cpu_time_nanos"`
- MemoryUsageBytes int64 `json:"memory_usage_bytes"`
- DiskReadBytes int64 `json:"disk_read_bytes"`
- DiskWriteBytes int64 `json:"disk_write_bytes"`
- NetworkRxBytes int64 `json:"network_rx_bytes"`
- NetworkTxBytes int64 `json:"network_tx_bytes"`
-}
diff --git a/go/apps/metald/internal/billing/client.go b/go/apps/metald/internal/billing/client.go
deleted file mode 100644
index 81daf000b0..0000000000
--- a/go/apps/metald/internal/billing/client.go
+++ /dev/null
@@ -1,385 +0,0 @@
-package billing
-
-import (
- "context"
- "errors"
- "fmt"
- "log/slog"
- "net/http"
- "time"
-
- "connectrpc.com/connect"
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/types"
- "github.com/unkeyed/unkey/go/apps/metald/internal/observability"
- "github.com/unkeyed/unkey/go/deploy/pkg/observability/interceptors"
- billingv1 "github.com/unkeyed/unkey/go/gen/proto/billaged/v1"
- "github.com/unkeyed/unkey/go/gen/proto/billaged/v1/billagedv1connect"
- "google.golang.org/protobuf/types/known/timestamppb"
-)
-
-// BillingClient defines the interface for communicating with billaged service
-type BillingClient interface {
- // SendMetricsBatch sends a batch of metrics to billaged
- SendMetricsBatch(ctx context.Context, vmID, customerID string, metrics []*types.VMMetrics) error
-
- // SendHeartbeat sends a heartbeat with active VM list
- SendHeartbeat(ctx context.Context, instanceID string, activeVMs []string) error
-
- // NotifyVmStarted notifies billaged that a VM has started
- NotifyVmStarted(ctx context.Context, vmID, customerID string, startTime int64) error
-
- // NotifyVmStopped notifies billaged that a VM has stopped
- NotifyVmStopped(ctx context.Context, vmID string, stopTime int64) error
-
- // NotifyPossibleGap notifies billaged of a potential data gap
- NotifyPossibleGap(ctx context.Context, vmID string, lastSent, resumeTime int64) error
-}
-
-// MockBillingClient provides a mock implementation for development and testing
-type MockBillingClient struct {
- logger *slog.Logger
-}
-
-// NewMockBillingClient creates a new mock billing client
-func NewMockBillingClient(logger *slog.Logger) *MockBillingClient {
- return &MockBillingClient{
- logger: logger.With("component", "mock_billing_client"),
- }
-}
-
-func (m *MockBillingClient) SendMetricsBatch(ctx context.Context, vmID, customerID string, metrics []*types.VMMetrics) error {
- m.logger.InfoContext(ctx, "MOCK: sending metrics batch",
- "vm_id", vmID,
- "customer_id", customerID,
- "metrics_count", len(metrics),
- )
-
- if len(metrics) > 0 {
- first := metrics[0]
- last := metrics[len(metrics)-1]
- m.logger.DebugContext(ctx, "MOCK: batch details",
- "first_timestamp", first.Timestamp.Format("15:04:05.000"),
- "last_timestamp", last.Timestamp.Format("15:04:05.000"),
- "first_cpu_nanos", first.CpuTimeNanos,
- "last_cpu_nanos", last.CpuTimeNanos,
- )
- }
-
- return nil
-}
-
-func (m *MockBillingClient) SendHeartbeat(ctx context.Context, instanceID string, activeVMs []string) error {
- m.logger.DebugContext(ctx, "MOCK: sending heartbeat",
- "instance_id", instanceID,
- "active_vms_count", len(activeVMs),
- "active_vms", activeVMs,
- )
- return nil
-}
-
-func (m *MockBillingClient) NotifyVmStarted(ctx context.Context, vmID, customerID string, startTime int64) error {
- m.logger.InfoContext(ctx, "MOCK: VM started notification",
- "vm_id", vmID,
- "customer_id", customerID,
- "start_time", startTime,
- )
- return nil
-}
-
-func (m *MockBillingClient) NotifyVmStopped(ctx context.Context, vmID string, stopTime int64) error {
- m.logger.InfoContext(ctx, "MOCK: VM stopped notification",
- "vm_id", vmID,
- "stop_time", stopTime,
- )
- return nil
-}
-
-func (m *MockBillingClient) NotifyPossibleGap(ctx context.Context, vmID string, lastSent, resumeTime int64) error {
- m.logger.WarnContext(ctx, "MOCK: possible data gap notification",
- "vm_id", vmID,
- "last_sent", lastSent,
- "resume_time", resumeTime,
- "gap_duration_ms", (resumeTime-lastSent)/1_000_000,
- )
- return nil
-}
-
-// Ensure MockBillingClient implements BillingClient interface
-var _ BillingClient = (*MockBillingClient)(nil)
-
-// ConnectRPCBillingClient implements real ConnectRPC client for billaged
-type ConnectRPCBillingClient struct {
- endpoint string
- logger *slog.Logger
- client billagedv1connect.BillingServiceClient
-}
-
-func NewConnectRPCBillingClient(endpoint string, logger *slog.Logger) *ConnectRPCBillingClient {
- httpClient := &http.Client{
- Timeout: 30 * time.Second,
- }
-
- // AIDEV-NOTE: Using debug interceptor for comprehensive error tracking
- billingClient := billagedv1connect.NewBillingServiceClient(
- httpClient,
- endpoint,
- connect.WithInterceptors(
- observability.DebugInterceptor(logger, "billaged"),
- ),
- )
-
- return &ConnectRPCBillingClient{
- endpoint: endpoint,
- logger: logger.With("component", "connectrpc_billing_client"),
- client: billingClient,
- }
-}
-
-// NewConnectRPCBillingClientWithHTTP creates a billing client with a custom HTTP client (for TLS)
-func NewConnectRPCBillingClientWithHTTP(endpoint string, logger *slog.Logger, httpClient *http.Client) *ConnectRPCBillingClient {
- // Use provided HTTP client which may have TLS configuration
- // AIDEV-NOTE: Using shared client interceptors for consistency across services
- clientInterceptors := interceptors.NewDefaultClientInterceptors("metald", logger)
- // Add debug interceptor for detailed error tracking
- clientInterceptors = append(clientInterceptors,
- observability.DebugInterceptor(logger, "billaged"),
- )
-
- // Convert UnaryInterceptorFunc to Interceptor
- var interceptorList []connect.Interceptor
- for _, interceptor := range clientInterceptors {
- interceptorList = append(interceptorList, connect.Interceptor(interceptor))
- }
-
- billingClient := billagedv1connect.NewBillingServiceClient(
- httpClient,
- endpoint,
- connect.WithInterceptors(interceptorList...),
- )
-
- return &ConnectRPCBillingClient{
- endpoint: endpoint,
- logger: logger.With("component", "connectrpc_billing_client"),
- client: billingClient,
- }
-}
-
-func (c *ConnectRPCBillingClient) SendMetricsBatch(ctx context.Context, vmID, customerID string, metrics []*types.VMMetrics) error {
- // Convert metald VMMetrics to billaged VMMetrics
- billingMetrics := make([]*billingv1.VMMetrics, len(metrics))
- for i, m := range metrics {
- billingMetrics[i] = &billingv1.VMMetrics{
- Timestamp: timestamppb.New(m.Timestamp),
- CpuTimeNanos: m.CpuTimeNanos,
- MemoryUsageBytes: m.MemoryUsageBytes,
- DiskReadBytes: m.DiskReadBytes,
- DiskWriteBytes: m.DiskWriteBytes,
- NetworkRxBytes: m.NetworkRxBytes,
- NetworkTxBytes: m.NetworkTxBytes,
- }
- }
-
- req := &billingv1.SendMetricsBatchRequest{
- VmId: vmID,
- CustomerId: customerID,
- Metrics: billingMetrics,
- }
-
- resp, err := c.client.SendMetricsBatch(ctx, connect.NewRequest(req))
- if err != nil {
- // AIDEV-NOTE: Enhanced debug logging for connection errors
- var connectErr *connect.Error
- if errors.As(err, &connectErr) {
- c.logger.ErrorContext(ctx, "billaged connection error",
- "error", err.Error(),
- "code", connectErr.Code().String(),
- "message", connectErr.Message(),
- "vm_id", vmID,
- "customer_id", customerID,
- "metrics_count", len(metrics),
- "operation", "SendMetricsBatch",
- )
- } else {
- c.logger.ErrorContext(ctx, "failed to send metrics batch",
- "error", err.Error(),
- "vm_id", vmID,
- "customer_id", customerID,
- "metrics_count", len(metrics),
- "operation", "SendMetricsBatch",
- )
- }
- return fmt.Errorf("failed to send metrics batch: %w", err)
- }
-
- if !resp.Msg.GetSuccess() {
- return fmt.Errorf("billaged rejected metrics batch: %s", resp.Msg.GetMessage())
- }
-
- c.logger.DebugContext(ctx, "sent metrics batch to billaged",
- "vm_id", vmID,
- "customer_id", customerID,
- "metrics_count", len(metrics),
- "message", resp.Msg.GetMessage(),
- )
-
- return nil
-}
-
-func (c *ConnectRPCBillingClient) SendHeartbeat(ctx context.Context, instanceID string, activeVMs []string) error {
- req := &billingv1.SendHeartbeatRequest{
- InstanceId: instanceID,
- ActiveVms: activeVMs,
- }
-
- resp, err := c.client.SendHeartbeat(ctx, connect.NewRequest(req))
- if err != nil {
- // AIDEV-NOTE: Enhanced debug logging for connection errors
- var connectErr *connect.Error
- if errors.As(err, &connectErr) {
- c.logger.ErrorContext(ctx, "billaged connection error",
- "error", err.Error(),
- "code", connectErr.Code().String(),
- "message", connectErr.Message(),
- "instance_id", instanceID,
- "active_vms_count", len(activeVMs),
- "operation", "SendHeartbeat",
- )
- } else {
- c.logger.ErrorContext(ctx, "failed to send heartbeat",
- "error", err.Error(),
- "instance_id", instanceID,
- "active_vms_count", len(activeVMs),
- "operation", "SendHeartbeat",
- )
- }
- return fmt.Errorf("failed to send heartbeat: %w", err)
- }
-
- if !resp.Msg.GetSuccess() {
- return fmt.Errorf("billaged rejected heartbeat")
- }
-
- return nil
-}
-
-func (c *ConnectRPCBillingClient) NotifyVmStarted(ctx context.Context, vmID, customerID string, startTime int64) error {
- req := &billingv1.NotifyVmStartedRequest{
- VmId: vmID,
- CustomerId: customerID,
- StartTime: startTime,
- }
-
- resp, err := c.client.NotifyVmStarted(ctx, connect.NewRequest(req))
- if err != nil {
- // AIDEV-NOTE: Enhanced debug logging for connection errors
- var connectErr *connect.Error
- if errors.As(err, &connectErr) {
- c.logger.ErrorContext(ctx, "billaged connection error",
- "error", err.Error(),
- "code", connectErr.Code().String(),
- "message", connectErr.Message(),
- "vm_id", vmID,
- "customer_id", customerID,
- "start_time", startTime,
- "operation", "NotifyVmStarted",
- )
- } else {
- c.logger.ErrorContext(ctx, "failed to notify VM started",
- "error", err.Error(),
- "vm_id", vmID,
- "customer_id", customerID,
- "start_time", startTime,
- "operation", "NotifyVmStarted",
- )
- }
- return fmt.Errorf("failed to notify VM started: %w", err)
- }
-
- if !resp.Msg.GetSuccess() {
- return fmt.Errorf("billaged rejected VM started notification")
- }
-
- return nil
-}
-
-func (c *ConnectRPCBillingClient) NotifyVmStopped(ctx context.Context, vmID string, stopTime int64) error {
- req := &billingv1.NotifyVmStoppedRequest{
- VmId: vmID,
- StopTime: stopTime,
- }
-
- resp, err := c.client.NotifyVmStopped(ctx, connect.NewRequest(req))
- if err != nil {
- // AIDEV-NOTE: Enhanced debug logging for connection errors
- var connectErr *connect.Error
- if errors.As(err, &connectErr) {
- c.logger.ErrorContext(ctx, "billaged connection error",
- "error", err.Error(),
- "code", connectErr.Code().String(),
- "message", connectErr.Message(),
- "vm_id", vmID,
- "stop_time", stopTime,
- "operation", "NotifyVmStopped",
- )
- } else {
- c.logger.ErrorContext(ctx, "failed to notify VM stopped",
- "error", err.Error(),
- "vm_id", vmID,
- "stop_time", stopTime,
- "operation", "NotifyVmStopped",
- )
- }
- return fmt.Errorf("failed to notify VM stopped: %w", err)
- }
-
- if !resp.Msg.GetSuccess() {
- return fmt.Errorf("billaged rejected VM stopped notification")
- }
-
- return nil
-}
-
-func (c *ConnectRPCBillingClient) NotifyPossibleGap(ctx context.Context, vmID string, lastSent, resumeTime int64) error {
- req := &billingv1.NotifyPossibleGapRequest{
- VmId: vmID,
- LastSent: lastSent,
- ResumeTime: resumeTime,
- }
-
- resp, err := c.client.NotifyPossibleGap(ctx, connect.NewRequest(req))
- if err != nil {
- // AIDEV-NOTE: Enhanced debug logging for connection errors
- var connectErr *connect.Error
- if errors.As(err, &connectErr) {
- c.logger.ErrorContext(ctx, "billaged connection error",
- "error", err.Error(),
- "code", connectErr.Code().String(),
- "message", connectErr.Message(),
- "vm_id", vmID,
- "last_sent", lastSent,
- "resume_time", resumeTime,
- "gap_duration_ms", (resumeTime-lastSent)/1_000_000,
- "operation", "NotifyPossibleGap",
- )
- } else {
- c.logger.ErrorContext(ctx, "failed to notify possible gap",
- "error", err.Error(),
- "vm_id", vmID,
- "last_sent", lastSent,
- "resume_time", resumeTime,
- "gap_duration_ms", (resumeTime-lastSent)/1_000_000,
- "operation", "NotifyPossibleGap",
- )
- }
- return fmt.Errorf("failed to notify possible gap: %w", err)
- }
-
- if !resp.Msg.GetSuccess() {
- return fmt.Errorf("billaged rejected possible gap notification")
- }
-
- return nil
-}
-
-// Ensure ConnectRPCBillingClient implements BillingClient interface
-var _ BillingClient = (*ConnectRPCBillingClient)(nil)
diff --git a/go/apps/metald/internal/billing/collector.go b/go/apps/metald/internal/billing/collector.go
deleted file mode 100644
index 4dc81d62e7..0000000000
--- a/go/apps/metald/internal/billing/collector.go
+++ /dev/null
@@ -1,347 +0,0 @@
-package billing
-
-import (
- "context"
- "fmt"
- "log/slog"
- "sync"
- "time"
-
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/types"
- "github.com/unkeyed/unkey/go/apps/metald/internal/observability"
-)
-
-// MetricsCollector manages high-frequency metrics collection for billing
-type MetricsCollector struct {
- backend types.Backend
- billingClient BillingClient
- logger *slog.Logger
- billingMetrics *observability.BillingMetrics
-
- // State management
- mu sync.RWMutex
- activeVMs map[string]*VMMetricsTracker
-
- // Configuration
- collectionInterval time.Duration
- batchSize int
- instanceID string
-}
-
-// VMMetricsTracker tracks metrics collection for a single VM
-type VMMetricsTracker struct {
- vmID string
- customerID string
- startTime time.Time
- lastSent time.Time
- buffer []*types.VMMetrics
- ticker *time.Ticker
- stopCh chan struct{}
- doneCh chan struct{} // Signals when goroutine has completely stopped
- ctx context.Context
- cancel context.CancelFunc
- mu sync.Mutex
-
- // Error tracking
- consecutiveErrors int
- lastError time.Time
-}
-
-// NewMetricsCollector creates a new metrics collector instance
-func NewMetricsCollector(backend types.Backend, billingClient BillingClient, logger *slog.Logger, instanceID string, billingMetrics *observability.BillingMetrics) *MetricsCollector {
- //exhaustruct:ignore
- return &MetricsCollector{
- backend: backend,
- billingClient: billingClient,
- logger: logger.With("component", "metrics_collector"),
- billingMetrics: billingMetrics,
- activeVMs: make(map[string]*VMMetricsTracker),
- collectionInterval: 5 * time.Minute,
- batchSize: 1, // Very small batch size for 5min intervals
- instanceID: instanceID,
- }
-}
-
-// StartCollection begins metrics collection for a VM
-func (mc *MetricsCollector) StartCollection(vmID, customerID string) error {
- mc.mu.Lock()
- defer mc.mu.Unlock()
-
- if _, exists := mc.activeVMs[vmID]; exists {
- return fmt.Errorf("metrics collection already active for vm %s", vmID)
- }
-
- ctx, cancel := context.WithCancel(context.Background())
- //exhaustruct:ignore
- tracker := &VMMetricsTracker{
- vmID: vmID,
- customerID: customerID,
- startTime: time.Now(),
- lastSent: time.Now(),
- buffer: make([]*types.VMMetrics, 0, mc.batchSize),
- ticker: time.NewTicker(mc.collectionInterval),
- stopCh: make(chan struct{}),
- doneCh: make(chan struct{}),
- ctx: ctx,
- cancel: cancel,
- }
-
- mc.activeVMs[vmID] = tracker
-
- // Notify billaged that VM started
- go func() {
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
-
- if err := mc.billingClient.NotifyVmStarted(ctx, vmID, customerID, tracker.startTime.UnixNano()); err != nil {
- mc.logger.Error("failed to notify VM started",
- "vm_id", vmID,
- "error", err,
- )
- }
- }()
-
- // Start collection goroutine
- go mc.runCollection(tracker)
-
- mc.logger.Info("started metrics collection",
- "vm_id", vmID,
- "customer_id", customerID,
- "interval", mc.collectionInterval,
- )
-
- return nil
-}
-
-// StopCollection stops metrics collection for a VM with proper timeout and cleanup
-func (mc *MetricsCollector) StopCollection(vmID string) {
- mc.mu.Lock()
- tracker, exists := mc.activeVMs[vmID]
- if !exists {
- mc.mu.Unlock()
- mc.logger.Debug("metrics collection not active for vm", "vm_id", vmID)
- return
- }
- delete(mc.activeVMs, vmID)
- mc.mu.Unlock()
-
- mc.logger.Info("stopping metrics collection", "vm_id", vmID)
-
- // Cancel the context to interrupt any blocking operations
- tracker.cancel()
-
- // Signal stop to the collection goroutine
- close(tracker.stopCh)
-
- // Wait for the goroutine to finish with a timeout
- timeout := time.NewTimer(5 * time.Second)
- defer timeout.Stop()
-
- select {
- case <-tracker.doneCh:
- mc.logger.Debug("metrics collection goroutine stopped gracefully", "vm_id", vmID)
- case <-timeout.C:
- mc.logger.Warn("metrics collection goroutine did not stop within timeout",
- "vm_id", vmID,
- "timeout", "5s")
- }
-
- // Notify billaged that VM stopped
- go func() {
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
-
- if err := mc.billingClient.NotifyVmStopped(ctx, vmID, time.Now().UnixNano()); err != nil {
- mc.logger.Error("failed to notify VM stopped",
- "vm_id", vmID,
- "error", err,
- )
- }
- }()
-
- mc.logger.Info("stopped metrics collection",
- "vm_id", vmID,
- "duration", time.Since(tracker.startTime),
- )
-}
-
-// GetActiveVMs returns a list of VMs currently being tracked
-func (mc *MetricsCollector) GetActiveVMs() []string {
- mc.mu.RLock()
- defer mc.mu.RUnlock()
-
- vms := make([]string, 0, len(mc.activeVMs))
- for vmID := range mc.activeVMs {
- vms = append(vms, vmID)
- }
- return vms
-}
-
-// StartHeartbeat begins sending periodic heartbeats to billaged
-func (mc *MetricsCollector) StartHeartbeat() {
- ticker := time.NewTicker(30 * time.Second)
-
- go func() {
- defer ticker.Stop()
-
- for range ticker.C {
- activeVMs := mc.GetActiveVMs()
-
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- err := mc.billingClient.SendHeartbeat(ctx, mc.instanceID, activeVMs)
- cancel()
-
- if err != nil {
- mc.logger.Error("heartbeat failed",
- "instance_id", mc.instanceID,
- "active_vms_count", len(activeVMs),
- "error", err,
- )
- } else {
- // Record successful heartbeat
- if mc.billingMetrics != nil {
- mc.billingMetrics.RecordHeartbeatSent(ctx, mc.instanceID)
- }
- mc.logger.Debug("heartbeat sent successfully",
- "instance_id", mc.instanceID,
- "active_vms_count", len(activeVMs),
- )
- }
- }
- }()
-
- mc.logger.Info("started heartbeat service",
- "instance_id", mc.instanceID,
- "interval", "30s",
- )
-}
-
-// runCollection performs the metrics collection loop for a single VM
-func (mc *MetricsCollector) runCollection(tracker *VMMetricsTracker) {
- defer func() {
- tracker.ticker.Stop()
- close(tracker.doneCh) // Signal that goroutine has completed
- }()
-
- for {
- select {
- case <-tracker.ctx.Done():
- // Context cancelled - stop immediately
- mc.logger.Debug("metrics collection context cancelled", "vm_id", tracker.vmID)
- return
- case <-tracker.ticker.C:
- // Collect metrics with cancellable context and timeout
- start := time.Now()
- ctx, cancel := context.WithTimeout(tracker.ctx, 2*time.Second)
- metrics, err := mc.backend.GetVMMetrics(ctx, tracker.vmID)
- cancel()
- collectDuration := time.Since(start)
-
- // Record VM metrics request
- if mc.billingMetrics != nil {
- mc.billingMetrics.RecordVMMetricsRequest(ctx, tracker.vmID)
- }
-
- if err != nil {
- tracker.consecutiveErrors++
- tracker.lastError = time.Now()
-
- mc.logger.Error("failed to collect metrics",
- "vm_id", tracker.vmID,
- "consecutive_errors", tracker.consecutiveErrors,
- "error", err,
- )
-
- // Skip this collection cycle but continue
- continue
- }
-
- // Reset error tracking on success
- if tracker.consecutiveErrors > 0 {
- mc.logger.Info("metrics collection recovered",
- "vm_id", tracker.vmID,
- "previous_errors", tracker.consecutiveErrors,
- )
- tracker.consecutiveErrors = 0
- }
-
- tracker.mu.Lock()
- tracker.buffer = append(tracker.buffer, metrics)
-
- // Record metrics collected
- if mc.billingMetrics != nil {
- mc.billingMetrics.RecordMetricsCollected(ctx, tracker.vmID, 1, collectDuration)
- }
-
- mc.logger.Debug("collected metrics",
- "vm_id", tracker.vmID,
- "collect_duration_ms", collectDuration.Milliseconds(),
- "buffer_size", len(tracker.buffer),
- "cpu_time_nanos", metrics.CpuTimeNanos,
- "memory_bytes", metrics.MemoryUsageBytes,
- )
-
- // Send batch when full
- if len(tracker.buffer) >= mc.batchSize {
- mc.sendBatch(tracker)
- tracker.buffer = tracker.buffer[:0] // Reset buffer
- tracker.lastSent = time.Now()
- }
- tracker.mu.Unlock()
-
- case <-tracker.stopCh:
- // Send final batch
- tracker.mu.Lock()
- if len(tracker.buffer) > 0 {
- mc.logger.Info("sending final metrics batch",
- "vm_id", tracker.vmID,
- "final_batch_size", len(tracker.buffer),
- )
- mc.sendBatch(tracker)
- }
- tracker.mu.Unlock()
- return
- }
- }
-}
-
-// sendBatch sends a batch of metrics to billaged
-func (mc *MetricsCollector) sendBatch(tracker *VMMetricsTracker) {
- if len(tracker.buffer) == 0 {
- return
- }
-
- batchCopy := make([]*types.VMMetrics, len(tracker.buffer))
- copy(batchCopy, tracker.buffer)
-
- start := time.Now()
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
-
- err := mc.billingClient.SendMetricsBatch(ctx, tracker.vmID, tracker.customerID, batchCopy)
- sendDuration := time.Since(start)
-
- if err != nil {
- mc.logger.Error("failed to send metrics batch",
- "vm_id", tracker.vmID,
- "customer_id", tracker.customerID,
- "batch_size", len(batchCopy),
- "send_duration_ms", sendDuration.Milliseconds(),
- "error", err,
- )
- // TODO: Implement retry logic with local queuing
- return
- }
-
- // Record successful batch send
- if mc.billingMetrics != nil {
- mc.billingMetrics.RecordBillingBatchSent(ctx, tracker.vmID, tracker.customerID, len(batchCopy), sendDuration)
- }
-
- mc.logger.Debug("sent metrics batch successfully",
- "vm_id", tracker.vmID,
- "customer_id", tracker.customerID,
- "batch_size", len(batchCopy),
- "send_duration_ms", sendDuration.Milliseconds(),
- )
-}
diff --git a/go/apps/metald/internal/config/config.go b/go/apps/metald/internal/config/config.go
deleted file mode 100644
index f37ebe628b..0000000000
--- a/go/apps/metald/internal/config/config.go
+++ /dev/null
@@ -1,372 +0,0 @@
-package config
-
-import (
- "fmt"
- "log/slog"
- "os"
- "strconv"
-
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/types"
-)
-
-// Config holds the application configuration
-type Config struct {
- // Server configuration
- Server ServerConfig
-
- // Backend configuration
- Backend BackendConfig
-
- // Billing configuration
- Billing BillingConfig
-
- // OpenTelemetry configuration
- OpenTelemetry OpenTelemetryConfig
-
- // Database configuration
- Database DatabaseConfig
-
- // AssetManager configuration
- AssetManager AssetManagerConfig
-
- // TLS configuration
- TLS *TLSConfig
-}
-
-// ServerConfig holds server-specific configuration
-type ServerConfig struct {
- // Port to listen on
- Port string
-
- // Address to bind to
- Address string
-}
-
-// BackendConfig holds backend-specific configuration
-type BackendConfig struct {
- // Type of backend
- Type types.BackendType
-
- // Jailer configuration
- Jailer JailerConfig
-}
-
-// JailerConfig holds Firecracker jailer configuration
-type JailerConfig struct {
- // UID for jailer process isolation
- UID uint32
-
- // GID for jailer process isolation
- GID uint32
-
- // Chroot directory for jailer isolation
- ChrootBaseDir string
-}
-
-// BillingConfig holds billing service configuration
-type BillingConfig struct {
- // Enabled indicates if billing integration is enabled
- Enabled bool
-
- // Endpoint is the billaged service endpoint (e.g., http://localhost:8081)
- Endpoint string
-
- // MockMode uses mock client instead of real ConnectRPC client
- MockMode bool
-}
-
-// OpenTelemetryConfig holds OpenTelemetry configuration
-type OpenTelemetryConfig struct {
- // Enabled indicates if OpenTelemetry is enabled
- Enabled bool
-
- // ServiceName for resource attributes
- ServiceName string
-
- // ServiceVersion for resource attributes
- ServiceVersion string
-
- // TracingSamplingRate from 0.0 to 1.0
- TracingSamplingRate float64
-
- // OTLPEndpoint for sending traces and metrics
- OTLPEndpoint string
-
- // PrometheusEnabled enables Prometheus metrics endpoint
- PrometheusEnabled bool
-
- // PrometheusPort for scraping metrics
- PrometheusPort string
-
- // PrometheusInterface controls the binding interface for metrics endpoint
- // Default "127.0.0.1" for localhost only (secure)
- // Set to "0.0.0.0" if remote access needed (not recommended)
- PrometheusInterface string
-
- // HighCardinalityLabelsEnabled allows high-cardinality labels like vm_id and process_id
- // Set to false in production to reduce cardinality
- HighCardinalityLabelsEnabled bool
-}
-
-// DatabaseConfig holds database configuration
-type DatabaseConfig struct {
- // DataDir is the directory where the SQLite database file is stored
- DataDir string
-}
-
-// AssetManagerConfig holds assetmanagerd service configuration
-type AssetManagerConfig struct {
- // Enabled indicates if assetmanagerd integration is enabled
- Enabled bool
-
- // Endpoint is the assetmanagerd service endpoint (e.g., http://localhost:8082)
- Endpoint string
-
- // CacheDir is the local directory for caching assets
- CacheDir string
-}
-
-// TLSConfig holds TLS configuration
-type TLSConfig struct {
- // Mode can be "file" or "spiffe" (default: "spiffe")
- Mode string `json:"mode,omitempty"`
-
- // File-based TLS options
- CertFile string `json:"cert_file,omitempty"`
- KeyFile string `json:"-"` // AIDEV-NOTE: Never serialize private key paths
- CAFile string `json:"ca_file,omitempty"`
-
- // SPIFFE options
- SPIFFESocketPath string `json:"spiffe_socket_path,omitempty"`
-
- // Performance options
- EnableCertCaching bool `json:"enable_cert_caching,omitempty"`
- CertCacheTTL string `json:"cert_cache_ttl,omitempty"`
-}
-
-// LoadConfig loads configuration from environment variables
-func LoadConfig() (*Config, error) {
- return LoadConfigWithSocketPath("")
-}
-
-// LoadConfigWithSocketPath loads configuration with an optional socket path override
-func LoadConfigWithSocketPath(socketPath string) (*Config, error) {
- // Use default logger for backward compatibility
- return LoadConfigWithSocketPathAndLogger(socketPath, slog.Default())
-}
-
-// LoadConfigWithSocketPathAndLogger loads configuration with optional socket path override and custom logger
-func LoadConfigWithSocketPathAndLogger(socketPath string, logger *slog.Logger) (*Config, error) {
- // Parse sampling rate
- samplingRate := 1.0
- if samplingStr := os.Getenv("UNKEY_METALD_OTEL_SAMPLING_RATE"); samplingStr != "" {
- if parsed, err := strconv.ParseFloat(samplingStr, 64); err == nil {
- samplingRate = parsed
- } else {
- logger.Warn("invalid UNKEY_METALD_OTEL_SAMPLING_RATE, using default 1.0",
- slog.String("value", samplingStr),
- slog.String("error", err.Error()),
- )
- }
- }
-
- // Parse enabled flag
- otelEnabled := false
- if enabledStr := os.Getenv("UNKEY_METALD_OTEL_ENABLED"); enabledStr != "" {
- if parsed, err := strconv.ParseBool(enabledStr); err == nil {
- otelEnabled = parsed
- } else {
- logger.Warn("invalid UNKEY_METALD_OTEL_ENABLED, using default false",
- slog.String("value", enabledStr),
- slog.String("error", err.Error()),
- )
- }
- }
-
- // Parse Prometheus enabled flag
- prometheusEnabled := true // Default to true when OTEL is enabled
- if promStr := os.Getenv("UNKEY_METALD_OTEL_PROMETHEUS_ENABLED"); promStr != "" {
- if parsed, err := strconv.ParseBool(promStr); err == nil {
- prometheusEnabled = parsed
- } else {
- logger.Warn("invalid UNKEY_METALD_OTEL_PROMETHEUS_ENABLED, using default true",
- slog.String("value", promStr),
- slog.String("error", err.Error()),
- )
- }
- }
-
- // Parse high cardinality labels flag
- highCardinalityLabelsEnabled := false // Default to false for production safety
- if highCardStr := os.Getenv("UNKEY_METALD_OTEL_HIGH_CARDINALITY_ENABLED"); highCardStr != "" {
- if parsed, err := strconv.ParseBool(highCardStr); err == nil {
- highCardinalityLabelsEnabled = parsed
- } else {
- logger.Warn("invalid UNKEY_METALD_OTEL_HIGH_CARDINALITY_ENABLED, using default false",
- slog.String("value", highCardStr),
- slog.String("error", err.Error()),
- )
- }
- }
-
- // Parse jailer UID/GID
- jailerUID := uint32(1000)
- if uidStr := os.Getenv("UNKEY_METALD_JAILER_UID"); uidStr != "" {
- if parsed, err := strconv.ParseUint(uidStr, 10, 32); err == nil {
- jailerUID = uint32(parsed)
- } else {
- logger.Warn("invalid UNKEY_METALD_JAILER_UID, using default 1000",
- slog.String("value", uidStr),
- slog.String("error", err.Error()),
- )
- }
- }
-
- jailerGID := uint32(1000)
- if gidStr := os.Getenv("UNKEY_METALD_JAILER_GID"); gidStr != "" {
- if parsed, err := strconv.ParseUint(gidStr, 10, 32); err == nil {
- jailerGID = uint32(parsed)
- } else {
- logger.Warn("invalid UNKEY_METALD_JAILER_GID, using default 1000",
- slog.String("value", gidStr),
- slog.String("error", err.Error()),
- )
- }
- }
-
- // Parse billing configuration
- billingEnabled := true // Default to enabled
- if enabledStr := os.Getenv("UNKEY_METALD_BILLING_ENABLED"); enabledStr != "" {
- if parsed, err := strconv.ParseBool(enabledStr); err == nil {
- billingEnabled = parsed
- }
- }
-
- billingMockMode := false // Default to real client
- if mockStr := os.Getenv("UNKEY_METALD_BILLING_MOCK_MODE"); mockStr != "" {
- if parsed, err := strconv.ParseBool(mockStr); err == nil {
- billingMockMode = parsed
- }
- }
-
- // Parse assetmanager configuration
- assetManagerEnabled := true // Default to enabled
- if enabledStr := os.Getenv("UNKEY_METALD_ASSETMANAGER_ENABLED"); enabledStr != "" {
- if parsed, err := strconv.ParseBool(enabledStr); err == nil {
- assetManagerEnabled = parsed
- } else {
- logger.Warn("invalid UNKEY_METALD_ASSETMANAGER_ENABLED, using default true",
- slog.String("value", enabledStr),
- slog.String("error", err.Error()),
- )
- }
- }
-
- cfg := &Config{
- Server: ServerConfig{
- Port: getEnvOrDefault("UNKEY_METALD_PORT", "8080"),
- Address: getEnvOrDefault("UNKEY_METALD_ADDRESS", "0.0.0.0"),
- },
- Backend: BackendConfig{
- Type: types.BackendType(getEnvOrDefault("UNKEY_METALD_BACKEND", string(types.BackendTypeFirecracker))),
- Jailer: JailerConfig{
- UID: jailerUID,
- GID: jailerGID,
- ChrootBaseDir: getEnvOrDefault("UNKEY_METALD_JAILER_CHROOT_DIR", "/srv/jailer"),
- },
- },
- Billing: BillingConfig{
- Enabled: billingEnabled,
- Endpoint: getEnvOrDefault("UNKEY_METALD_BILLING_ENDPOINT", "http://localhost:8081"),
- MockMode: billingMockMode,
- },
- OpenTelemetry: OpenTelemetryConfig{
- Enabled: otelEnabled,
- ServiceName: getEnvOrDefault("UNKEY_METALD_OTEL_SERVICE_NAME", "metald"),
- ServiceVersion: getEnvOrDefault("UNKEY_METALD_OTEL_SERVICE_VERSION", "0.1.0"),
- TracingSamplingRate: samplingRate,
- OTLPEndpoint: getEnvOrDefault("UNKEY_METALD_OTEL_ENDPOINT", "localhost:4318"),
- PrometheusEnabled: prometheusEnabled,
- PrometheusPort: getEnvOrDefault("UNKEY_METALD_OTEL_PROMETHEUS_PORT", "9464"),
- PrometheusInterface: getEnvOrDefault("UNKEY_METALD_OTEL_PROMETHEUS_INTERFACE", "127.0.0.1"),
- HighCardinalityLabelsEnabled: highCardinalityLabelsEnabled,
- },
- Database: DatabaseConfig{
- DataDir: getEnvOrDefault("UNKEY_METALD_DATA_DIR", "/opt/metald/data"),
- },
- AssetManager: AssetManagerConfig{
- Enabled: assetManagerEnabled,
- Endpoint: getEnvOrDefault("UNKEY_METALD_ASSETMANAGER_ENDPOINT", "http://localhost:8083"),
- CacheDir: getEnvOrDefault("UNKEY_METALD_ASSETMANAGER_CACHE_DIR", "/opt/metald/assets"),
- },
- TLS: &TLSConfig{
- // AIDEV-BUSINESS_RULE: mTLS/SPIFFE is required for production security
- Mode: getEnvOrDefault("UNKEY_METALD_TLS_MODE", "spiffe"),
- CertFile: getEnvOrDefault("UNKEY_METALD_TLS_CERT_FILE", ""),
- KeyFile: getEnvOrDefault("UNKEY_METALD_TLS_KEY_FILE", ""),
- CAFile: getEnvOrDefault("UNKEY_METALD_TLS_CA_FILE", ""),
- SPIFFESocketPath: getEnvOrDefault("UNKEY_METALD_SPIFFE_SOCKET", "/var/lib/spire/agent/agent.sock"),
- EnableCertCaching: getEnvBoolOrDefault("UNKEY_METALD_TLS_ENABLE_CERT_CACHING"),
- CertCacheTTL: getEnvOrDefault("UNKEY_METALD_TLS_CERT_CACHE_TTL", "5s"),
- },
- }
-
- // Validate configuration
- if err := cfg.Validate(); err != nil {
- return nil, fmt.Errorf("invalid configuration: %w", err)
- }
-
- return cfg, nil
-}
-
-// Validate validates the configuration
-func (c *Config) Validate() error {
- // AIDEV-BUSINESS_RULE: Support Firecracker and Docker backends
- if c.Backend.Type != types.BackendTypeFirecracker && c.Backend.Type != types.BackendTypeDocker {
- return fmt.Errorf("only firecracker and docker backends are supported, got: %s", c.Backend.Type)
- }
-
- if c.OpenTelemetry.Enabled {
- if c.OpenTelemetry.TracingSamplingRate < 0.0 || c.OpenTelemetry.TracingSamplingRate > 1.0 {
- return fmt.Errorf("tracing sampling rate must be between 0.0 and 1.0, got %f", c.OpenTelemetry.TracingSamplingRate)
- }
- if c.OpenTelemetry.OTLPEndpoint == "" {
- return fmt.Errorf("OTLP endpoint is required when OpenTelemetry is enabled")
- }
- if c.OpenTelemetry.ServiceName == "" {
- return fmt.Errorf("service name is required when OpenTelemetry is enabled")
- }
- }
-
- return nil
-}
-
-// getEnvOrDefault gets an environment variable or returns a default value
-func getEnvOrDefault(key, defaultValue string) string {
- if value := os.Getenv(key); value != "" {
- return value
- }
- return defaultValue
-}
-
-func getEnvBoolOrDefault(key string) bool {
- if value := os.Getenv(key); value != "" {
- boolValue, err := strconv.ParseBool(value)
- if err != nil {
- return true
- }
- return boolValue
- }
- return true
-}
-
-func getEnvIntOrDefault(key string, defaultValue int) int {
- if value := os.Getenv(key); value != "" {
- intValue, err := strconv.Atoi(value)
- if err != nil {
- return defaultValue
- }
- return intValue
- }
- return defaultValue
-}
diff --git a/go/apps/metald/internal/database/db.go b/go/apps/metald/internal/database/db.go
deleted file mode 100644
index ef3e100691..0000000000
--- a/go/apps/metald/internal/database/db.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// Code generated by sqlc. DO NOT EDIT.
-// versions:
-// sqlc v1.29.0
-
-package database
-
-import (
- "context"
- "database/sql"
-)
-
-type DBTX interface {
- ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
- PrepareContext(context.Context, string) (*sql.Stmt, error)
- QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
- QueryRowContext(context.Context, string, ...interface{}) *sql.Row
-}
-
-func New(db DBTX) *Queries {
- return &Queries{db: db}
-}
-
-type Queries struct {
- db DBTX
-}
-
-func (q *Queries) WithTx(tx *sql.Tx) *Queries {
- return &Queries{
- db: tx,
- }
-}
diff --git a/go/apps/metald/internal/database/handler.go b/go/apps/metald/internal/database/handler.go
deleted file mode 100644
index 950af7f511..0000000000
--- a/go/apps/metald/internal/database/handler.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package database
-
-import (
- "database/sql"
- "fmt"
- "log/slog"
- "os"
- "path/filepath"
-
- _ "github.com/unkeyed/unkey/go/apps/metald/migrations"
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/trace"
- _ "modernc.org/sqlite"
-)
-
-type Database struct {
- Queries Querier
- db *sql.DB
- tracer trace.Tracer
- logger *slog.Logger
-}
-
-func NewDatabase(dataDir string) (*Database, error) {
- return NewDatabaseWithLogger(dataDir, slog.Default())
-}
-
-func NewDatabaseWithLogger(dataDir string, logger *slog.Logger) (*Database, error) {
- if err := os.MkdirAll(dataDir, 0o700); err != nil {
- return nil, fmt.Errorf("failed to create data directory: %w", err)
- }
-
- dbPath := filepath.Join(dataDir, "metald.db")
- db, err := sql.Open("sqlite", dbPath+"?_journal_mode=WAL&_synchronous=NORMAL&_cache_size=-64000&_foreign_keys=ON")
- if err != nil {
- return nil, fmt.Errorf("failed to open database: %w", err)
- }
-
- logger.Info("database opened", slog.String("path", dbPath))
-
- queries := New(db)
- database := &Database{
- Queries: queries,
- db: db,
- tracer: otel.Tracer("metald/database"),
- logger: logger,
- }
-
- return database, nil
-}
-
-// Expose Close for graceful shutdown with migrations
-func (db *Database) Close() error {
- return db.db.Close()
-}
-
-// Expose underlying database connection for migrations
-func (db *Database) DB() *sql.DB {
- return db.db
-}
diff --git a/go/apps/metald/internal/database/models.go b/go/apps/metald/internal/database/models.go
deleted file mode 100644
index 8d4aed9bad..0000000000
--- a/go/apps/metald/internal/database/models.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// Code generated by sqlc. DO NOT EDIT.
-// versions:
-// sqlc v1.29.0
-
-package database
-
-import (
- "database/sql"
-)
-
-type IpAllocation struct {
- ID int64 `db:"id" json:"id"`
- VmID string `db:"vm_id" json:"vm_id"`
- IpAddr string `db:"ip_addr" json:"ip_addr"`
- NetworkAllocationID int64 `db:"network_allocation_id" json:"network_allocation_id"`
- AllocatedAt sql.NullTime `db:"allocated_at" json:"allocated_at"`
-}
-
-type Network struct {
- ID int64 `db:"id" json:"id"`
- BaseNetwork string `db:"base_network" json:"base_network"`
- IsAllocated int64 `db:"is_allocated" json:"is_allocated"`
-}
-
-type NetworkAllocation struct {
- ID int64 `db:"id" json:"id"`
- DeploymentID string `db:"deployment_id" json:"deployment_id"`
- NetworkID int64 `db:"network_id" json:"network_id"`
- BridgeName string `db:"bridge_name" json:"bridge_name"`
- AvailableIps string `db:"available_ips" json:"available_ips"`
- AllocatedAt sql.NullTime `db:"allocated_at" json:"allocated_at"`
-}
diff --git a/go/apps/metald/internal/database/querier.go b/go/apps/metald/internal/database/querier.go
deleted file mode 100644
index 413d9617ce..0000000000
--- a/go/apps/metald/internal/database/querier.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// Code generated by sqlc. DO NOT EDIT.
-// versions:
-// sqlc v1.29.0
-
-package database
-
-import (
- "context"
- "database/sql"
-)
-
-type Querier interface {
- AllocateIP(ctx context.Context, arg AllocateIPParams) (IpAllocation, error)
- // queries.sql
- AllocateNetwork(ctx context.Context) (Network, error)
- CleanupStaleIPAllocations(ctx context.Context, allocatedAt sql.NullTime) error
- CreateNetworkAllocation(ctx context.Context, arg CreateNetworkAllocationParams) (NetworkAllocation, error)
- DeleteIPAllocationsForNetwork(ctx context.Context, networkAllocationID int64) error
- DeleteNetworkAllocation(ctx context.Context, deploymentID string) error
- DeleteStaleNetworkAllocations(ctx context.Context, allocatedAt sql.NullTime) error
- GetAvailableIPCount(ctx context.Context, deploymentID string) (interface{}, error)
- GetIPAllocation(ctx context.Context, vmID string) (IpAllocation, error)
- GetNetworkAllocation(ctx context.Context, deploymentID string) (GetNetworkAllocationRow, error)
- GetNetworkStats(ctx context.Context) (GetNetworkStatsRow, error)
- PopAvailableIPJSON(ctx context.Context, deploymentID string) (PopAvailableIPJSONRow, error)
- ReleaseIP(ctx context.Context, vmID string) error
- ReleaseNetwork(ctx context.Context, id int64) error
- ReleaseStaleNetworks(ctx context.Context, allocatedAt sql.NullTime) error
- ReturnIPJSON(ctx context.Context, arg ReturnIPJSONParams) error
-}
-
-var _ Querier = (*Queries)(nil)
diff --git a/go/apps/metald/internal/database/queries.sql.go b/go/apps/metald/internal/database/queries.sql.go
deleted file mode 100644
index bff6e28365..0000000000
--- a/go/apps/metald/internal/database/queries.sql.go
+++ /dev/null
@@ -1,291 +0,0 @@
-// Code generated by sqlc. DO NOT EDIT.
-// versions:
-// sqlc v1.29.0
-// source: queries.sql
-
-package database
-
-import (
- "context"
- "database/sql"
-)
-
-const allocateIP = `-- name: AllocateIP :one
-INSERT INTO ip_allocations (vm_id, ip_addr, network_allocation_id)
-VALUES (?, ?, ?)
-RETURNING id, vm_id, ip_addr, network_allocation_id, allocated_at
-`
-
-type AllocateIPParams struct {
- VmID string `db:"vm_id" json:"vm_id"`
- IpAddr string `db:"ip_addr" json:"ip_addr"`
- NetworkAllocationID int64 `db:"network_allocation_id" json:"network_allocation_id"`
-}
-
-func (q *Queries) AllocateIP(ctx context.Context, arg AllocateIPParams) (IpAllocation, error) {
- row := q.db.QueryRowContext(ctx, allocateIP, arg.VmID, arg.IpAddr, arg.NetworkAllocationID)
- var i IpAllocation
- err := row.Scan(
- &i.ID,
- &i.VmID,
- &i.IpAddr,
- &i.NetworkAllocationID,
- &i.AllocatedAt,
- )
- return i, err
-}
-
-const allocateNetwork = `-- name: AllocateNetwork :one
-
-UPDATE networks
-SET is_allocated = 1
-WHERE id = (
- SELECT id FROM networks
- WHERE is_allocated = 0
- ORDER BY id
- LIMIT 1
-)
-RETURNING id, base_network, is_allocated
-`
-
-// queries.sql
-func (q *Queries) AllocateNetwork(ctx context.Context) (Network, error) {
- row := q.db.QueryRowContext(ctx, allocateNetwork)
- var i Network
- err := row.Scan(&i.ID, &i.BaseNetwork, &i.IsAllocated)
- return i, err
-}
-
-const cleanupStaleIPAllocations = `-- name: CleanupStaleIPAllocations :exec
-DELETE FROM ip_allocations
-WHERE network_allocation_id IN (
- SELECT id FROM network_allocations na
- WHERE na.allocated_at < ?
-)
-`
-
-func (q *Queries) CleanupStaleIPAllocations(ctx context.Context, allocatedAt sql.NullTime) error {
- _, err := q.db.ExecContext(ctx, cleanupStaleIPAllocations, allocatedAt)
- return err
-}
-
-const createNetworkAllocation = `-- name: CreateNetworkAllocation :one
-INSERT INTO network_allocations (deployment_id, network_id, available_ips, bridge_name)
-VALUES (?, ?, ?, ?)
-RETURNING id, deployment_id, network_id, bridge_name, available_ips, allocated_at
-`
-
-type CreateNetworkAllocationParams struct {
- DeploymentID string `db:"deployment_id" json:"deployment_id"`
- NetworkID int64 `db:"network_id" json:"network_id"`
- AvailableIps string `db:"available_ips" json:"available_ips"`
- BridgeName string `db:"bridge_name" json:"bridge_name"`
-}
-
-func (q *Queries) CreateNetworkAllocation(ctx context.Context, arg CreateNetworkAllocationParams) (NetworkAllocation, error) {
- row := q.db.QueryRowContext(ctx, createNetworkAllocation,
- arg.DeploymentID,
- arg.NetworkID,
- arg.AvailableIps,
- arg.BridgeName,
- )
- var i NetworkAllocation
- err := row.Scan(
- &i.ID,
- &i.DeploymentID,
- &i.NetworkID,
- &i.BridgeName,
- &i.AvailableIps,
- &i.AllocatedAt,
- )
- return i, err
-}
-
-const deleteIPAllocationsForNetwork = `-- name: DeleteIPAllocationsForNetwork :exec
-DELETE FROM ip_allocations
-WHERE network_allocation_id = ?
-`
-
-func (q *Queries) DeleteIPAllocationsForNetwork(ctx context.Context, networkAllocationID int64) error {
- _, err := q.db.ExecContext(ctx, deleteIPAllocationsForNetwork, networkAllocationID)
- return err
-}
-
-const deleteNetworkAllocation = `-- name: DeleteNetworkAllocation :exec
-DELETE FROM network_allocations
-WHERE deployment_id = ?
-`
-
-func (q *Queries) DeleteNetworkAllocation(ctx context.Context, deploymentID string) error {
- _, err := q.db.ExecContext(ctx, deleteNetworkAllocation, deploymentID)
- return err
-}
-
-const deleteStaleNetworkAllocations = `-- name: DeleteStaleNetworkAllocations :exec
-DELETE FROM network_allocations
-WHERE allocated_at < ?
-`
-
-func (q *Queries) DeleteStaleNetworkAllocations(ctx context.Context, allocatedAt sql.NullTime) error {
- _, err := q.db.ExecContext(ctx, deleteStaleNetworkAllocations, allocatedAt)
- return err
-}
-
-const getAvailableIPCount = `-- name: GetAvailableIPCount :one
-SELECT json_array_length(available_ips) as count
-FROM network_allocations
-WHERE deployment_id = ?
-`
-
-func (q *Queries) GetAvailableIPCount(ctx context.Context, deploymentID string) (interface{}, error) {
- row := q.db.QueryRowContext(ctx, getAvailableIPCount, deploymentID)
- var count interface{}
- err := row.Scan(&count)
- return count, err
-}
-
-const getIPAllocation = `-- name: GetIPAllocation :one
-SELECT id, vm_id, ip_addr, network_allocation_id, allocated_at FROM ip_allocations WHERE vm_id = ?
-`
-
-func (q *Queries) GetIPAllocation(ctx context.Context, vmID string) (IpAllocation, error) {
- row := q.db.QueryRowContext(ctx, getIPAllocation, vmID)
- var i IpAllocation
- err := row.Scan(
- &i.ID,
- &i.VmID,
- &i.IpAddr,
- &i.NetworkAllocationID,
- &i.AllocatedAt,
- )
- return i, err
-}
-
-const getNetworkAllocation = `-- name: GetNetworkAllocation :one
-SELECT na.id, na.deployment_id, na.network_id, na.bridge_name, na.available_ips, na.allocated_at, n.base_network
-FROM network_allocations na
-JOIN networks n ON na.network_id = n.id
-WHERE na.deployment_id = ?
-`
-
-type GetNetworkAllocationRow struct {
- ID int64 `db:"id" json:"id"`
- DeploymentID string `db:"deployment_id" json:"deployment_id"`
- NetworkID int64 `db:"network_id" json:"network_id"`
- BridgeName string `db:"bridge_name" json:"bridge_name"`
- AvailableIps string `db:"available_ips" json:"available_ips"`
- AllocatedAt sql.NullTime `db:"allocated_at" json:"allocated_at"`
- BaseNetwork string `db:"base_network" json:"base_network"`
-}
-
-func (q *Queries) GetNetworkAllocation(ctx context.Context, deploymentID string) (GetNetworkAllocationRow, error) {
- row := q.db.QueryRowContext(ctx, getNetworkAllocation, deploymentID)
- var i GetNetworkAllocationRow
- err := row.Scan(
- &i.ID,
- &i.DeploymentID,
- &i.NetworkID,
- &i.BridgeName,
- &i.AvailableIps,
- &i.AllocatedAt,
- &i.BaseNetwork,
- )
- return i, err
-}
-
-const getNetworkStats = `-- name: GetNetworkStats :one
-SELECT
- (SELECT COUNT(*) FROM networks) as total_networks,
- (SELECT COUNT(*) FROM networks WHERE is_allocated = 0) as available_networks,
- (SELECT COUNT(*) FROM network_allocations) as active_deployments,
- (SELECT COUNT(*) FROM ip_allocations) as allocated_ips
-`
-
-type GetNetworkStatsRow struct {
- TotalNetworks int64 `db:"total_networks" json:"total_networks"`
- AvailableNetworks int64 `db:"available_networks" json:"available_networks"`
- ActiveDeployments int64 `db:"active_deployments" json:"active_deployments"`
- AllocatedIps int64 `db:"allocated_ips" json:"allocated_ips"`
-}
-
-func (q *Queries) GetNetworkStats(ctx context.Context) (GetNetworkStatsRow, error) {
- row := q.db.QueryRowContext(ctx, getNetworkStats)
- var i GetNetworkStatsRow
- err := row.Scan(
- &i.TotalNetworks,
- &i.AvailableNetworks,
- &i.ActiveDeployments,
- &i.AllocatedIps,
- )
- return i, err
-}
-
-const popAvailableIPJSON = `-- name: PopAvailableIPJSON :one
-UPDATE network_allocations
-SET available_ips = json_remove(available_ips, '$[0]')
-WHERE deployment_id = ?
-AND json_array_length(available_ips) > 0
-RETURNING CAST(json_extract(available_ips, '$[0]') AS TEXT) AS ip, id
-`
-
-type PopAvailableIPJSONRow struct {
- Column1 string `db:"column_1" json:"column_1"`
- ID int64 `db:"id" json:"id"`
-}
-
-func (q *Queries) PopAvailableIPJSON(ctx context.Context, deploymentID string) (PopAvailableIPJSONRow, error) {
- row := q.db.QueryRowContext(ctx, popAvailableIPJSON, deploymentID)
- var i PopAvailableIPJSONRow
- err := row.Scan(&i.Column1, &i.ID)
- return i, err
-}
-
-const releaseIP = `-- name: ReleaseIP :exec
-DELETE FROM ip_allocations WHERE vm_id = ?
-`
-
-func (q *Queries) ReleaseIP(ctx context.Context, vmID string) error {
- _, err := q.db.ExecContext(ctx, releaseIP, vmID)
- return err
-}
-
-const releaseNetwork = `-- name: ReleaseNetwork :exec
-UPDATE networks
-SET is_allocated = 0
-WHERE id = ?
-`
-
-func (q *Queries) ReleaseNetwork(ctx context.Context, id int64) error {
- _, err := q.db.ExecContext(ctx, releaseNetwork, id)
- return err
-}
-
-const releaseStaleNetworks = `-- name: ReleaseStaleNetworks :exec
-UPDATE networks
-SET is_allocated = 0
-WHERE id IN (
- SELECT network_id FROM network_allocations na
- WHERE na.allocated_at < ?
-)
-`
-
-func (q *Queries) ReleaseStaleNetworks(ctx context.Context, allocatedAt sql.NullTime) error {
- _, err := q.db.ExecContext(ctx, releaseStaleNetworks, allocatedAt)
- return err
-}
-
-const returnIPJSON = `-- name: ReturnIPJSON :exec
-UPDATE network_allocations
-SET available_ips = json_insert(available_ips, '$[#]', ?)
-WHERE deployment_id = ?
-`
-
-type ReturnIPJSONParams struct {
- JsonInsert interface{} `db:"json_insert" json:"json_insert"`
- DeploymentID string `db:"deployment_id" json:"deployment_id"`
-}
-
-func (q *Queries) ReturnIPJSON(ctx context.Context, arg ReturnIPJSONParams) error {
- _, err := q.db.ExecContext(ctx, returnIPJSON, arg.JsonInsert, arg.DeploymentID)
- return err
-}
diff --git a/go/apps/metald/internal/health/handler.go b/go/apps/metald/internal/health/handler.go
deleted file mode 100644
index 578bb8c2d3..0000000000
--- a/go/apps/metald/internal/health/handler.go
+++ /dev/null
@@ -1,183 +0,0 @@
-package health
-
-import (
- "context"
- "encoding/json"
- "log/slog"
- "net/http"
- "time"
-
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/types"
-)
-
-// Health status constants
-const (
- StatusHealthy = "healthy"
- StatusUnhealthy = "unhealthy"
- StatusDegraded = "degraded"
-)
-
-// Handler provides health check endpoints
-type Handler struct {
- backend types.Backend
- logger *slog.Logger
- startTime time.Time
-}
-
-// NewHandler creates a new health check handler
-func NewHandler(backend types.Backend, logger *slog.Logger, startTime time.Time) *Handler {
- return &Handler{
- backend: backend,
- logger: logger.With("component", "health"),
- startTime: startTime,
- }
-}
-
-// HealthResponse represents the health check response
-type HealthResponse struct {
- Status string `json:"status"`
- Timestamp time.Time `json:"timestamp"`
- Version string `json:"version"`
- Backend BackendHealth `json:"backend"`
- Checks map[string]Check `json:"checks"`
-}
-
-// BackendHealth contains backend-specific health information
-type BackendHealth struct {
- Type string `json:"type"`
- Status string `json:"status"`
- Error string `json:"error,omitempty"`
-}
-
-// Check represents an individual health check result
-type Check struct {
- Status string `json:"status"`
- Duration time.Duration `json:"duration_ms"`
- Error string `json:"error,omitempty"`
- Timestamp time.Time `json:"timestamp"`
-}
-
-// ServeHTTP handles health check requests
-func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- ctx := r.Context()
- startTime := time.Now()
-
- h.logger.LogAttrs(ctx, slog.LevelInfo, "health check requested",
- slog.String("path", r.URL.Path),
- slog.String("method", r.Method),
- slog.String("user_agent", r.Header.Get("User-Agent")),
- )
-
- // Perform health checks
- response := h.performHealthChecks(ctx)
-
- // Set response headers
- w.Header().Set("Content-Type", "application/json")
- w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
-
- // Determine HTTP status code based on overall health
- statusCode := http.StatusOK
- if response.Status != StatusHealthy {
- statusCode = http.StatusServiceUnavailable
- }
-
- w.WriteHeader(statusCode)
-
- // Encode and send response
- if err := json.NewEncoder(w).Encode(response); err != nil {
- h.logger.LogAttrs(ctx, slog.LevelError, "failed to encode health response",
- slog.String("error", err.Error()),
- )
- return
- }
-
- duration := time.Since(startTime)
- h.logger.LogAttrs(ctx, slog.LevelInfo, "health check completed",
- slog.String("status", response.Status),
- slog.Duration("duration", duration),
- slog.Int("status_code", statusCode),
- )
-}
-
-// performHealthChecks executes all health checks and returns the result
-func (h *Handler) performHealthChecks(ctx context.Context) *HealthResponse {
- timestamp := time.Now()
- checks := make(map[string]Check)
-
- // Check backend health
- backendHealth := h.checkBackendHealth(ctx, checks)
-
- //exhaustruct:ignore
- checks["system_info"] = Check{
- Status: StatusHealthy,
- Timestamp: timestamp,
- }
-
- // Determine overall status
- overallStatus := StatusHealthy
- if backendHealth.Status != StatusHealthy {
- overallStatus = StatusUnhealthy
- }
-
- for _, check := range checks {
- if check.Status != StatusHealthy {
- overallStatus = StatusDegraded
- break
- }
- }
-
- return &HealthResponse{
- Status: overallStatus,
- Timestamp: timestamp,
- Version: "dev", // AIDEV-TODO: Get from build info
- Backend: backendHealth,
- Checks: checks,
- }
-}
-
-// checkBackendHealth checks the health of the hypervisor backend
-func (h *Handler) checkBackendHealth(ctx context.Context, checks map[string]Check) BackendHealth {
- checkStart := time.Now()
-
- // Create a timeout context for the backend ping
- pingCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
- defer cancel()
-
- err := h.backend.Ping(pingCtx)
- duration := time.Since(checkStart)
-
- backendHealth := BackendHealth{ //nolint:exhaustruct // Status and Error fields are set conditionally based on backend response
- Type: "firecracker", // Only Firecracker is supported
- }
-
- if err != nil {
- h.logger.LogAttrs(ctx, slog.LevelWarn, "backend ping failed",
- slog.String("error", err.Error()),
- slog.Duration("duration", duration),
- )
-
- backendHealth.Status = StatusUnhealthy
- backendHealth.Error = err.Error()
-
- checks["backend_ping"] = Check{
- Status: StatusUnhealthy,
- Duration: duration,
- Error: err.Error(),
- Timestamp: time.Now(),
- }
- } else {
- h.logger.LogAttrs(ctx, slog.LevelDebug, "backend ping successful",
- slog.Duration("duration", duration),
- )
-
- backendHealth.Status = StatusHealthy
-
- checks["backend_ping"] = Check{ //nolint:exhaustruct // Error field is set conditionally based on check result
- Status: StatusHealthy,
- Duration: duration,
- Timestamp: time.Now(),
- }
- }
-
- return backendHealth
-}
diff --git a/go/apps/metald/internal/health/handler_test.go b/go/apps/metald/internal/health/handler_test.go
deleted file mode 100644
index 215ae8eea1..0000000000
--- a/go/apps/metald/internal/health/handler_test.go
+++ /dev/null
@@ -1,333 +0,0 @@
-package health
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "errors"
- "log/slog"
- "net/http"
- "net/http/httptest"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/stretchr/testify/require"
-
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/types"
- metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1"
-)
-
-// mockBackend is a mock implementation of types.Backend for testing
-type mockBackend struct {
- mock.Mock
-}
-
-func (m *mockBackend) CreateVM(ctx context.Context, config *metaldv1.VmConfig) (string, error) {
- args := m.Called(ctx, config)
- return args.String(0), args.Error(1)
-}
-
-func (m *mockBackend) DeleteVM(ctx context.Context, vmID string) error {
- args := m.Called(ctx, vmID)
- return args.Error(0)
-}
-
-func (m *mockBackend) BootVM(ctx context.Context, vmID string) error {
- args := m.Called(ctx, vmID)
- return args.Error(0)
-}
-
-func (m *mockBackend) ShutdownVM(ctx context.Context, vmID string) error {
- args := m.Called(ctx, vmID)
- return args.Error(0)
-}
-
-func (m *mockBackend) ShutdownVMWithOptions(ctx context.Context, vmID string, force bool, timeoutSeconds int32) error {
- args := m.Called(ctx, vmID, force, timeoutSeconds)
- return args.Error(0)
-}
-
-func (m *mockBackend) PauseVM(ctx context.Context, vmID string) error {
- args := m.Called(ctx, vmID)
- return args.Error(0)
-}
-
-func (m *mockBackend) ResumeVM(ctx context.Context, vmID string) error {
- args := m.Called(ctx, vmID)
- return args.Error(0)
-}
-
-func (m *mockBackend) RebootVM(ctx context.Context, vmID string) error {
- args := m.Called(ctx, vmID)
- return args.Error(0)
-}
-
-func (m *mockBackend) GetVMInfo(ctx context.Context, vmID string) (*types.VMInfo, error) {
- args := m.Called(ctx, vmID)
- if args.Get(0) == nil {
- return nil, args.Error(1)
- }
- return args.Get(0).(*types.VMInfo), args.Error(1)
-}
-
-func (m *mockBackend) GetVMMetrics(ctx context.Context, vmID string) (*types.VMMetrics, error) {
- args := m.Called(ctx, vmID)
- if args.Get(0) == nil {
- return nil, args.Error(1)
- }
- return args.Get(0).(*types.VMMetrics), args.Error(1)
-}
-
-func (m *mockBackend) Ping(ctx context.Context) error {
- args := m.Called(ctx)
- return args.Error(0)
-}
-
-func (m *mockBackend) Type() string {
- return "mock"
-}
-
-func TestNewHandler(t *testing.T) {
- backend := &mockBackend{}
- logger := slog.New(slog.NewTextHandler(&bytes.Buffer{}, nil))
- startTime := time.Now()
-
- handler := NewHandler(backend, logger, startTime)
-
- assert.NotNil(t, handler)
- assert.Equal(t, backend, handler.backend)
- assert.Equal(t, startTime, handler.startTime)
- assert.NotNil(t, handler.logger)
-}
-
-func TestHandler_ServeHTTP_Healthy(t *testing.T) {
- backend := &mockBackend{}
- backend.On("Ping", mock.Anything).Return(nil)
-
- logger := slog.New(slog.NewTextHandler(&bytes.Buffer{}, nil))
- handler := NewHandler(backend, logger, time.Now())
-
- req := httptest.NewRequest("GET", "/health", nil)
- w := httptest.NewRecorder()
-
- handler.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusOK, w.Code)
- assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
- assert.Equal(t, "no-cache, no-store, must-revalidate", w.Header().Get("Cache-Control"))
-
- var response HealthResponse
- err := json.Unmarshal(w.Body.Bytes(), &response)
- require.NoError(t, err)
-
- assert.Equal(t, StatusHealthy, response.Status)
- assert.Equal(t, "dev", response.Version)
- assert.Equal(t, "firecracker", response.Backend.Type)
- assert.Equal(t, StatusHealthy, response.Backend.Status)
- assert.Empty(t, response.Backend.Error)
- assert.Contains(t, response.Checks, "backend_ping")
- assert.Contains(t, response.Checks, "system_info")
-
- backend.AssertExpectations(t)
-}
-
-func TestHandler_ServeHTTP_Unhealthy(t *testing.T) {
- backend := &mockBackend{}
- backendErr := errors.New("backend unavailable")
- backend.On("Ping", mock.Anything).Return(backendErr)
-
- logger := slog.New(slog.NewTextHandler(&bytes.Buffer{}, nil))
- handler := NewHandler(backend, logger, time.Now())
-
- req := httptest.NewRequest("GET", "/health", nil)
- w := httptest.NewRecorder()
-
- handler.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusServiceUnavailable, w.Code)
- assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
-
- var response HealthResponse
- err := json.Unmarshal(w.Body.Bytes(), &response)
- require.NoError(t, err)
-
- // The status will be "degraded" because backend failure creates a check that fails
- // which overrides the backend unhealthy status with degraded
- assert.Equal(t, StatusDegraded, response.Status)
- assert.Equal(t, "firecracker", response.Backend.Type)
- assert.Equal(t, StatusUnhealthy, response.Backend.Status)
- assert.Equal(t, "backend unavailable", response.Backend.Error)
-
- backend.AssertExpectations(t)
-}
-
-func TestHandler_performHealthChecks(t *testing.T) {
- tests := []struct {
- name string
- backendPingErr error
- expectedStatus string
- expectedChecks int
- }{
- {
- name: "all healthy",
- backendPingErr: nil,
- expectedStatus: StatusHealthy,
- expectedChecks: 2, // backend_ping + system_info
- },
- {
- name: "backend unhealthy",
- backendPingErr: errors.New("ping failed"),
- expectedStatus: StatusDegraded, // Backend failure creates unhealthy check which sets status to degraded
- expectedChecks: 2,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- backend := &mockBackend{}
- backend.On("Ping", mock.Anything).Return(tt.backendPingErr)
-
- logger := slog.New(slog.NewTextHandler(&bytes.Buffer{}, nil))
- handler := NewHandler(backend, logger, time.Now())
-
- ctx := context.Background()
- response := handler.performHealthChecks(ctx)
-
- assert.Equal(t, tt.expectedStatus, response.Status)
- assert.Equal(t, "dev", response.Version)
- assert.Equal(t, "firecracker", response.Backend.Type)
- assert.Len(t, response.Checks, tt.expectedChecks)
- assert.Contains(t, response.Checks, "backend_ping")
- assert.Contains(t, response.Checks, "system_info")
-
- backend.AssertExpectations(t)
- })
- }
-}
-
-func TestHandler_checkBackendHealth(t *testing.T) {
- tests := []struct {
- name string
- backendPingErr error
- expectedStatus string
- expectError bool
- }{
- {
- name: "backend healthy",
- backendPingErr: nil,
- expectedStatus: StatusHealthy,
- expectError: false,
- },
- {
- name: "backend unhealthy",
- backendPingErr: errors.New("connection failed"),
- expectedStatus: StatusUnhealthy,
- expectError: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- backend := &mockBackend{}
- backend.On("Ping", mock.Anything).Return(tt.backendPingErr)
-
- logger := slog.New(slog.NewTextHandler(&bytes.Buffer{}, nil))
- handler := NewHandler(backend, logger, time.Now())
-
- ctx := context.Background()
- checks := make(map[string]Check)
-
- backendHealth := handler.checkBackendHealth(ctx, checks)
-
- assert.Equal(t, "firecracker", backendHealth.Type)
- assert.Equal(t, tt.expectedStatus, backendHealth.Status)
-
- if tt.expectError {
- assert.NotEmpty(t, backendHealth.Error)
- assert.Equal(t, tt.backendPingErr.Error(), backendHealth.Error)
- } else {
- assert.Empty(t, backendHealth.Error)
- }
-
- // Verify check was added
- require.Contains(t, checks, "backend_ping")
- pingCheck := checks["backend_ping"]
- assert.Equal(t, tt.expectedStatus, pingCheck.Status)
-
- if tt.expectError {
- assert.NotEmpty(t, pingCheck.Error)
- } else {
- assert.Empty(t, pingCheck.Error)
- }
-
- backend.AssertExpectations(t)
- })
- }
-}
-
-func TestHandler_checkBackendHealth_Timeout(t *testing.T) {
- backend := &mockBackend{}
-
- // Mock returns context deadline exceeded error
- timeoutErr := context.DeadlineExceeded
- backend.On("Ping", mock.Anything).Return(timeoutErr)
-
- logger := slog.New(slog.NewTextHandler(&bytes.Buffer{}, nil))
- handler := NewHandler(backend, logger, time.Now())
-
- ctx := context.Background()
- checks := make(map[string]Check)
-
- backendHealth := handler.checkBackendHealth(ctx, checks)
-
- assert.Equal(t, "firecracker", backendHealth.Type)
- assert.Equal(t, StatusUnhealthy, backendHealth.Status)
- assert.Contains(t, backendHealth.Error, "context deadline exceeded")
-
- require.Contains(t, checks, "backend_ping")
- pingCheck := checks["backend_ping"]
- assert.Equal(t, StatusUnhealthy, pingCheck.Status)
- assert.Contains(t, pingCheck.Error, "context deadline exceeded")
-
- backend.AssertExpectations(t)
-}
-
-func TestHealthResponse_JSON(t *testing.T) {
- response := &HealthResponse{
- Status: StatusHealthy,
- Timestamp: time.Now(),
- Version: "1.0.0",
- Backend: BackendHealth{
- Type: "firecracker",
- Status: StatusHealthy,
- },
- Checks: map[string]Check{
- "backend_ping": {
- Status: StatusHealthy,
- Duration: 100 * time.Millisecond,
- Timestamp: time.Now(),
- },
- },
- }
-
- jsonData, err := json.Marshal(response)
- require.NoError(t, err)
-
- var unmarshaled HealthResponse
- err = json.Unmarshal(jsonData, &unmarshaled)
- require.NoError(t, err)
-
- assert.Equal(t, response.Status, unmarshaled.Status)
- assert.Equal(t, response.Version, unmarshaled.Version)
- assert.Equal(t, response.Backend.Type, unmarshaled.Backend.Type)
- assert.Equal(t, response.Backend.Status, unmarshaled.Backend.Status)
- assert.Contains(t, unmarshaled.Checks, "backend_ping")
-}
-
-func TestStatusConstants(t *testing.T) {
- assert.Equal(t, "healthy", StatusHealthy)
- assert.Equal(t, "unhealthy", StatusUnhealthy)
- assert.Equal(t, "degraded", StatusDegraded)
-}
diff --git a/go/apps/metald/internal/health/system.go b/go/apps/metald/internal/health/system.go
deleted file mode 100644
index 1e4adca26a..0000000000
--- a/go/apps/metald/internal/health/system.go
+++ /dev/null
@@ -1,145 +0,0 @@
-package health
-
-import (
- "context"
- "fmt"
- "os"
- "runtime"
- "strings"
- "time"
-)
-
-// SystemInfo contains system information for health checks
-type SystemInfo struct {
- Hostname string `json:"hostname"`
- CPU CPU `json:"cpu"`
- Memory Memory `json:"memory"`
- Uptime string `json:"uptime"`
-}
-
-// CPU contains CPU information
-type CPU struct {
- Architecture string `json:"architecture"`
- Cores int `json:"cores"`
- Model string `json:"model,omitempty"`
-}
-
-// Memory contains memory information in bytes
-type Memory struct {
- Total uint64 `json:"total_bytes"`
- Used uint64 `json:"used_bytes"`
- Available uint64 `json:"available_bytes"`
- UsedPct float64 `json:"used_percent"`
-}
-
-// GetSystemInfo collects current system information
-func GetSystemInfo(ctx context.Context, startTime time.Time) (*SystemInfo, error) {
- hostname, err := os.Hostname()
- if err != nil {
- hostname = "unknown"
- }
-
- // Get CPU information
- cpu := CPU{ //nolint:exhaustruct // Model field is populated conditionally below if available
- Architecture: runtime.GOARCH,
- Cores: runtime.NumCPU(),
- }
-
- // Try to get CPU model from /proc/cpuinfo on Linux
- if model := getCPUModel(); model != "" {
- cpu.Model = model
- }
-
- // Get memory information
- memory := getMemoryInfo()
-
- // Calculate uptime
- uptime := time.Since(startTime).String()
-
- return &SystemInfo{
- Hostname: hostname,
- CPU: cpu,
- Memory: memory,
- Uptime: uptime,
- }, nil
-}
-
-// getCPUModel attempts to read CPU model from /proc/cpuinfo
-func getCPUModel() string {
- // AIDEV-NOTE: This is Linux-specific, could be extended for other OSes
- data, err := os.ReadFile("/proc/cpuinfo")
- if err != nil {
- return ""
- }
-
- lines := strings.Split(string(data), "\n")
- for _, line := range lines {
- if strings.HasPrefix(line, "model name") {
- parts := strings.SplitN(line, ":", 2)
- if len(parts) == 2 {
- return strings.TrimSpace(parts[1])
- }
- }
- }
- return ""
-}
-
-// getMemoryInfo gets memory information using Go runtime stats
-func getMemoryInfo() Memory {
- var m runtime.MemStats
- runtime.ReadMemStats(&m)
-
- // AIDEV-NOTE: This provides Go runtime memory stats, not system memory
- // For system memory, we'd need to read /proc/meminfo on Linux
- systemMem := getSystemMemory()
- if systemMem.Total > 0 {
- return systemMem
- }
-
- // Fallback to runtime memory stats
- return Memory{
- Total: m.Sys,
- Used: m.Alloc,
- Available: m.Sys - m.Alloc,
- UsedPct: float64(m.Alloc) / float64(m.Sys) * 100,
- }
-}
-
-// getSystemMemory attempts to read system memory from /proc/meminfo
-func getSystemMemory() Memory {
- data, err := os.ReadFile("/proc/meminfo")
- if err != nil {
- return Memory{} //exhaustruct:ignore
- }
-
- var total, available uint64
- lines := strings.Split(string(data), "\n")
-
- for _, line := range lines {
- if strings.HasPrefix(line, "MemTotal:") {
- if _, err := fmt.Sscanf(line, "MemTotal: %d kB", &total); err != nil {
- continue
- }
- total *= 1024 // Convert to bytes
- } else if strings.HasPrefix(line, "MemAvailable:") {
- if _, err := fmt.Sscanf(line, "MemAvailable: %d kB", &available); err != nil {
- continue
- }
- available *= 1024 // Convert to bytes
- }
- }
-
- if total == 0 {
- return Memory{} //exhaustruct:ignore
- }
-
- used := total - available
- usedPct := float64(used) / float64(total) * 100
-
- return Memory{
- Total: total,
- Used: used,
- Available: available,
- UsedPct: usedPct,
- }
-}
diff --git a/go/apps/metald/internal/health/system_test.go b/go/apps/metald/internal/health/system_test.go
deleted file mode 100644
index 04c5f7b193..0000000000
--- a/go/apps/metald/internal/health/system_test.go
+++ /dev/null
@@ -1,227 +0,0 @@
-package health
-
-import (
- "context"
- "os"
- "runtime"
- "strings"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestGetSystemInfo(t *testing.T) {
- startTime := time.Now().Add(-1 * time.Hour)
- ctx := context.Background()
-
- systemInfo, err := GetSystemInfo(ctx, startTime)
- require.NoError(t, err)
- require.NotNil(t, systemInfo)
-
- // Test hostname (should not be empty unless there's an error)
- assert.NotEmpty(t, systemInfo.Hostname)
-
- // Test CPU info
- assert.Equal(t, runtime.GOARCH, systemInfo.CPU.Architecture)
- assert.Equal(t, runtime.NumCPU(), systemInfo.CPU.Cores)
- assert.GreaterOrEqual(t, systemInfo.CPU.Cores, 1)
-
- // Test memory info
- assert.Greater(t, systemInfo.Memory.Total, uint64(0))
- assert.GreaterOrEqual(t, systemInfo.Memory.Used, uint64(0))
- assert.GreaterOrEqual(t, systemInfo.Memory.Available, uint64(0))
- assert.GreaterOrEqual(t, systemInfo.Memory.UsedPct, 0.0)
- assert.LessOrEqual(t, systemInfo.Memory.UsedPct, 100.0)
-
- // Test uptime (should be positive duration string)
- assert.NotEmpty(t, systemInfo.Uptime)
- assert.True(t, strings.Contains(systemInfo.Uptime, "h") || strings.Contains(systemInfo.Uptime, "m") || strings.Contains(systemInfo.Uptime, "s"))
-}
-
-func TestGetSystemInfo_Uptime(t *testing.T) {
- // Test with start time 2 hours ago
- startTime := time.Now().Add(-2 * time.Hour)
- ctx := context.Background()
-
- systemInfo, err := GetSystemInfo(ctx, startTime)
- require.NoError(t, err)
-
- // Uptime should reflect approximately 2 hours
- assert.Contains(t, systemInfo.Uptime, "h")
-}
-
-func TestCPU_Structure(t *testing.T) {
- cpu := CPU{
- Architecture: "amd64",
- Cores: 8,
- Model: "Intel Core i7",
- }
-
- assert.Equal(t, "amd64", cpu.Architecture)
- assert.Equal(t, 8, cpu.Cores)
- assert.Equal(t, "Intel Core i7", cpu.Model)
-}
-
-func TestMemory_Structure(t *testing.T) {
- memory := Memory{
- Total: 8589934592, // 8GB
- Used: 2147483648, // 2GB
- Available: 6442450944, // 6GB
- UsedPct: 25.0,
- }
-
- assert.Equal(t, uint64(8589934592), memory.Total)
- assert.Equal(t, uint64(2147483648), memory.Used)
- assert.Equal(t, uint64(6442450944), memory.Available)
- assert.Equal(t, 25.0, memory.UsedPct)
-}
-
-func TestSystemInfo_Structure(t *testing.T) {
- systemInfo := SystemInfo{
- Hostname: "test-host",
- CPU: CPU{
- Architecture: "amd64",
- Cores: 4,
- Model: "Test CPU",
- },
- Memory: Memory{
- Total: 4294967296,
- Used: 1073741824,
- Available: 3221225472,
- UsedPct: 25.0,
- },
- Uptime: "2h30m15s",
- }
-
- assert.Equal(t, "test-host", systemInfo.Hostname)
- assert.Equal(t, "amd64", systemInfo.CPU.Architecture)
- assert.Equal(t, 4, systemInfo.CPU.Cores)
- assert.Equal(t, "Test CPU", systemInfo.CPU.Model)
- assert.Equal(t, uint64(4294967296), systemInfo.Memory.Total)
- assert.Equal(t, "2h30m15s", systemInfo.Uptime)
-}
-
-func TestGetCPUModel(t *testing.T) {
- // This test is environment-dependent and might not work on all systems
- // We'll test the function but accept empty results on non-Linux systems
- model := getCPUModel()
-
- // On Linux systems with /proc/cpuinfo, we might get a model
- // On other systems or containers, this might be empty
- // Both are valid outcomes
- if runtime.GOOS == "linux" {
- // Model might be available on Linux, but not guaranteed in containers
- t.Logf("CPU Model detected: %q", model)
- } else {
- // On non-Linux systems, this will likely be empty
- assert.Equal(t, "", model)
- }
-}
-
-func TestGetMemoryInfo(t *testing.T) {
- memory := getMemoryInfo()
-
- // Basic validation - memory values should be reasonable
- assert.Greater(t, memory.Total, uint64(0))
- assert.GreaterOrEqual(t, memory.Used, uint64(0))
- assert.GreaterOrEqual(t, memory.Available, uint64(0))
- assert.GreaterOrEqual(t, memory.UsedPct, 0.0)
- assert.LessOrEqual(t, memory.UsedPct, 100.0)
-
- // Total should be greater than or equal to used
- assert.GreaterOrEqual(t, memory.Total, memory.Used)
-}
-
-func TestGetSystemMemory(t *testing.T) {
- memory := getSystemMemory()
-
- // This test is Linux-specific since it reads /proc/meminfo
- if runtime.GOOS == "linux" {
- if _, err := os.Stat("/proc/meminfo"); err == nil {
- // /proc/meminfo exists, we should get some data
- assert.Greater(t, memory.Total, uint64(0))
- assert.GreaterOrEqual(t, memory.Used, uint64(0))
- assert.GreaterOrEqual(t, memory.Available, uint64(0))
- assert.GreaterOrEqual(t, memory.UsedPct, 0.0)
- assert.LessOrEqual(t, memory.UsedPct, 100.0)
- }
- }
- // On non-Linux systems or when /proc/meminfo is not available,
- // the function returns zero values, which is expected behavior
-}
-
-func TestMemoryCalculations(t *testing.T) {
- // Test memory percentage calculation logic
- tests := []struct {
- name string
- total uint64
- available uint64
- wantUsed uint64
- wantPct float64
- }{
- {
- name: "50% usage",
- total: 1000,
- available: 500,
- wantUsed: 500,
- wantPct: 50.0,
- },
- {
- name: "25% usage",
- total: 2000,
- available: 1500,
- wantUsed: 500,
- wantPct: 25.0,
- },
- {
- name: "0% usage",
- total: 1000,
- available: 1000,
- wantUsed: 0,
- wantPct: 0.0,
- },
- {
- name: "100% usage",
- total: 1000,
- available: 0,
- wantUsed: 1000,
- wantPct: 100.0,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- used := tt.total - tt.available
- usedPct := float64(used) / float64(tt.total) * 100
-
- assert.Equal(t, tt.wantUsed, used)
- assert.Equal(t, tt.wantPct, usedPct)
- })
- }
-}
-
-func TestGetSystemInfo_ErrorHandling(t *testing.T) {
- // Test with a very recent start time (basically now)
- startTime := time.Now()
- ctx := context.Background()
-
- systemInfo, err := GetSystemInfo(ctx, startTime)
- require.NoError(t, err)
- require.NotNil(t, systemInfo)
-
- // Even if hostname fails, the function should not return an error
- // It should use "unknown" as fallback
- assert.NotEmpty(t, systemInfo.Hostname)
-
- // CPU info should still be populated from runtime
- assert.Equal(t, runtime.GOARCH, systemInfo.CPU.Architecture)
- assert.Equal(t, runtime.NumCPU(), systemInfo.CPU.Cores)
-
- // Memory should be populated (either from system or runtime)
- assert.Greater(t, systemInfo.Memory.Total, uint64(0))
-
- // Uptime should be very small but formatted as string
- assert.NotEmpty(t, systemInfo.Uptime)
-}
diff --git a/go/apps/metald/internal/health/vm_health.go b/go/apps/metald/internal/health/vm_health.go
deleted file mode 100644
index 34b8840c08..0000000000
--- a/go/apps/metald/internal/health/vm_health.go
+++ /dev/null
@@ -1,480 +0,0 @@
-package health
-
-import (
- "context"
- "fmt"
- "log/slog"
- "net"
- "net/http"
- "os"
- "sync"
- "time"
-
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/metric"
- "go.opentelemetry.io/otel/trace"
-)
-
-// VMHealthStatus represents the health status of a VM
-type VMHealthStatus struct {
- VMId string `json:"vm_id"`
- ProcessID string `json:"process_id"`
- IsHealthy bool `json:"is_healthy"`
- LastCheck time.Time `json:"last_check"`
- LastHealthy time.Time `json:"last_healthy"`
- ProcessPID int `json:"process_pid"`
- SocketPath string `json:"socket_path"`
- ErrorMsg string `json:"error_msg,omitempty"`
- CheckCount int64 `json:"check_count"`
- FailureCount int64 `json:"failure_count"`
-}
-
-// HealthCheckConfig configures VM health checking behavior
-type HealthCheckConfig struct {
- Interval time.Duration `json:"interval"` // How often to check (default: 30s)
- Timeout time.Duration `json:"timeout"` // Per-check timeout (default: 5s)
- FailureThreshold int `json:"failure_threshold"` // Consecutive failures before unhealthy (default: 3)
- RecoveryThreshold int `json:"recovery_threshold"` // Consecutive successes before healthy (default: 2)
- Enabled bool `json:"enabled"` // Enable/disable health checking
-}
-
-// DefaultHealthCheckConfig returns sensible defaults
-func DefaultHealthCheckConfig() *HealthCheckConfig {
- return &HealthCheckConfig{
- Interval: 30 * time.Second,
- Timeout: 5 * time.Second,
- FailureThreshold: 3,
- RecoveryThreshold: 2,
- Enabled: true,
- }
-}
-
-// VMHealthChecker manages health checking for VMs
-type VMHealthChecker struct {
- logger *slog.Logger
- config *HealthCheckConfig
- httpClient *http.Client
-
- // Metrics
- meter metric.Meter
- healthCheckTotal metric.Int64Counter
- healthCheckFailed metric.Int64Counter
- healthCheckDuration metric.Float64Histogram
-
- // State tracking
- mu sync.RWMutex
- vmStatus map[string]*VMHealthStatus // vmID -> status
- activeChecks map[string]context.CancelFunc // vmID -> cancel function
-
- // Callbacks
- onVMUnhealthy func(vmID string, status *VMHealthStatus)
- onVMRecovered func(vmID string, status *VMHealthStatus)
-}
-
-// NewVMHealthChecker creates a new VM health checker
-func NewVMHealthChecker(logger *slog.Logger, config *HealthCheckConfig) (*VMHealthChecker, error) {
- if config == nil {
- config = DefaultHealthCheckConfig()
- }
-
- // Create HTTP client with Unix socket transport
- httpClient := &http.Client{
- Timeout: config.Timeout,
- Transport: &http.Transport{
- DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
- // This will be overridden per-request for different socket paths
- return nil, fmt.Errorf("socket not configured")
- },
- },
- }
-
- // Initialize metrics
- meter := otel.Meter("unkey.metald.vm.health")
-
- healthCheckTotal, err := meter.Int64Counter(
- "unkey_metald_vm_health_checks_total",
- metric.WithDescription("Total number of VM health checks performed"),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create health check counter: %w", err)
- }
-
- healthCheckFailed, err := meter.Int64Counter(
- "unkey_metald_vm_health_check_failures_total",
- metric.WithDescription("Total number of failed VM health checks"),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create health check failure counter: %w", err)
- }
-
- healthCheckDuration, err := meter.Float64Histogram(
- "unkey_metald_vm_health_check_duration_seconds",
- metric.WithDescription("Duration of VM health checks in seconds"),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create health check duration histogram: %w", err)
- }
-
- //exhaustruct:ignore
- return &VMHealthChecker{
- logger: logger.With("component", "vm_health_checker"),
- config: config,
- httpClient: httpClient,
- meter: meter,
- healthCheckTotal: healthCheckTotal,
- healthCheckFailed: healthCheckFailed,
- healthCheckDuration: healthCheckDuration,
- vmStatus: make(map[string]*VMHealthStatus),
- activeChecks: make(map[string]context.CancelFunc),
- }, nil
-}
-
-// SetCallbacks sets callback functions for health state changes
-func (hc *VMHealthChecker) SetCallbacks(
- onUnhealthy func(vmID string, status *VMHealthStatus),
- onRecovered func(vmID string, status *VMHealthStatus),
-) {
- hc.onVMUnhealthy = onUnhealthy
- hc.onVMRecovered = onRecovered
-}
-
-// StartMonitoring begins health checking for a VM
-func (hc *VMHealthChecker) StartMonitoring(vmID, processID, socketPath string, processPID int) error {
- if !hc.config.Enabled {
- hc.logger.Debug("health checking disabled", "vm_id", vmID)
- return nil
- }
-
- hc.mu.Lock()
- defer hc.mu.Unlock()
-
- // Stop existing monitoring if any
- if cancel, exists := hc.activeChecks[vmID]; exists {
- cancel()
- delete(hc.activeChecks, vmID)
- }
-
- // Initialize status
- //exhaustruct:ignore
- status := &VMHealthStatus{
- VMId: vmID,
- ProcessID: processID,
- IsHealthy: true, // Assume healthy initially
- LastCheck: time.Now(),
- LastHealthy: time.Now(),
- ProcessPID: processPID,
- SocketPath: socketPath,
- CheckCount: 0,
- FailureCount: 0,
- }
- hc.vmStatus[vmID] = status
-
- // Start monitoring goroutine
- ctx, cancel := context.WithCancel(context.Background())
- hc.activeChecks[vmID] = cancel
-
- go hc.monitorVM(ctx, vmID)
-
- hc.logger.Info("started vm health monitoring",
- "vm_id", vmID,
- "process_id", processID,
- "socket_path", socketPath,
- "interval", hc.config.Interval,
- )
-
- return nil
-}
-
-// StopMonitoring stops health checking for a VM
-func (hc *VMHealthChecker) StopMonitoring(vmID string) {
- hc.mu.Lock()
- defer hc.mu.Unlock()
-
- if cancel, exists := hc.activeChecks[vmID]; exists {
- cancel()
- delete(hc.activeChecks, vmID)
- }
-
- delete(hc.vmStatus, vmID)
-
- hc.logger.Info("stopped vm health monitoring", "vm_id", vmID)
-}
-
-// GetVMHealth returns the current health status of a VM
-func (hc *VMHealthChecker) GetVMHealth(vmID string) (*VMHealthStatus, bool) {
- hc.mu.RLock()
- defer hc.mu.RUnlock()
-
- status, exists := hc.vmStatus[vmID]
- if !exists {
- return nil, false
- }
-
- // Return a copy to avoid race conditions
- statusCopy := *status
- return &statusCopy, true
-}
-
-// GetAllVMHealth returns health status for all monitored VMs
-func (hc *VMHealthChecker) GetAllVMHealth() map[string]*VMHealthStatus {
- hc.mu.RLock()
- defer hc.mu.RUnlock()
-
- result := make(map[string]*VMHealthStatus)
- for vmID, status := range hc.vmStatus {
- statusCopy := *status
- result[vmID] = &statusCopy
- }
-
- return result
-}
-
-// monitorVM runs the health checking loop for a single VM
-func (hc *VMHealthChecker) monitorVM(ctx context.Context, vmID string) {
- ticker := time.NewTicker(hc.config.Interval)
- defer ticker.Stop()
-
- // Perform initial check immediately
- hc.performHealthCheck(ctx, vmID)
-
- for {
- select {
- case <-ctx.Done():
- hc.logger.DebugContext(ctx, "health monitoring stopped", "vm_id", vmID)
- return
- case <-ticker.C:
- hc.performHealthCheck(ctx, vmID)
- }
- }
-}
-
-// performHealthCheck performs a single health check for a VM
-func (hc *VMHealthChecker) performHealthCheck(ctx context.Context, vmID string) {
- start := time.Now()
-
- // Create trace span for observability
- tracer := otel.Tracer("unkey.metald.vm.health")
- ctx, span := tracer.Start(ctx, "vm_health_check",
- trace.WithAttributes(
- attribute.String("vm_id", vmID),
- ),
- )
- defer span.End()
-
- hc.mu.Lock()
- status, exists := hc.vmStatus[vmID]
- if !exists {
- hc.mu.Unlock()
- return
- }
-
- // Create local copy for thread safety
- socketPath := status.SocketPath
- processPID := status.ProcessPID
- hc.mu.Unlock()
-
- // Perform the actual health check
- checkCtx, cancel := context.WithTimeout(ctx, hc.config.Timeout)
- defer cancel()
-
- isHealthy, errorMsg := hc.checkVMHealth(checkCtx, socketPath, processPID)
- duration := time.Since(start)
-
- // Record metrics
- hc.healthCheckTotal.Add(ctx, 1,
- metric.WithAttributes(
- attribute.String("vm_id", vmID),
- attribute.Bool("healthy", isHealthy),
- ),
- )
-
- if !isHealthy {
- hc.healthCheckFailed.Add(ctx, 1,
- metric.WithAttributes(
- attribute.String("vm_id", vmID),
- attribute.String("error", errorMsg),
- ),
- )
- }
-
- hc.healthCheckDuration.Record(ctx, duration.Seconds(),
- metric.WithAttributes(
- attribute.String("vm_id", vmID),
- ),
- )
-
- // Update status and check for state transitions
- hc.updateVMHealthStatus(vmID, isHealthy, errorMsg, duration)
-
- span.SetAttributes(
- attribute.Bool("healthy", isHealthy),
- attribute.Float64("duration_seconds", duration.Seconds()),
- )
-
- if !isHealthy {
- span.RecordError(fmt.Errorf("health check failed: %s", errorMsg))
- }
-}
-
-// checkVMHealth performs the actual health check logic
-func (hc *VMHealthChecker) checkVMHealth(ctx context.Context, socketPath string, processPID int) (bool, string) {
- // 1. Check if socket file exists
- if _, err := os.Stat(socketPath); err != nil {
- return false, fmt.Sprintf("socket file missing: %v", err)
- }
-
- // 2. Check if process is still running
- if !hc.isProcessRunning(processPID) {
- return false, "process not running"
- }
-
- // 3. Test socket connectivity
- conn, err := net.DialTimeout("unix", socketPath, hc.config.Timeout)
- if err != nil {
- return false, fmt.Sprintf("socket unreachable: %v", err)
- }
- defer conn.Close()
-
- // 4. Test Firecracker API endpoint
- client := &http.Client{
- Timeout: hc.config.Timeout,
- Transport: &http.Transport{
- DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
- return net.Dial("unix", socketPath)
- },
- },
- }
-
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://unix/", nil)
- if err != nil {
- return false, fmt.Sprintf("failed to create request: %v", err)
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return false, fmt.Sprintf("api request failed: %v", err)
- }
- defer resp.Body.Close()
-
- if resp.StatusCode >= 500 {
- return false, fmt.Sprintf("api error: status %d", resp.StatusCode)
- }
-
- return true, ""
-}
-
-// updateVMHealthStatus updates the health status and handles state transitions
-func (hc *VMHealthChecker) updateVMHealthStatus(vmID string, isHealthy bool, errorMsg string, duration time.Duration) {
- hc.mu.Lock()
- defer hc.mu.Unlock()
-
- status, exists := hc.vmStatus[vmID]
- if !exists {
- return
- }
-
- now := time.Now()
- wasHealthy := status.IsHealthy
-
- // Update basic status
- status.LastCheck = now
- status.CheckCount++
-
- if isHealthy {
- hc.handleHealthyStatus(status, wasHealthy, vmID, now)
- } else {
- hc.handleUnhealthyStatus(status, wasHealthy, vmID, errorMsg, duration)
- }
-}
-
-// isProcessRunning checks if a process is still running
-func (hc *VMHealthChecker) isProcessRunning(pid int) bool {
- if pid <= 0 {
- return false
- }
-
- // Check if /proc/pid exists
- if _, err := os.Stat(fmt.Sprintf("/proc/%d", pid)); err != nil {
- return false
- }
-
- return true
-}
-
-// Shutdown stops all health checking
-func (hc *VMHealthChecker) Shutdown() {
- hc.mu.Lock()
- defer hc.mu.Unlock()
-
- hc.logger.Info("shutting down vm health checker")
-
- // Cancel all active checks
- for vmID, cancel := range hc.activeChecks {
- cancel()
- hc.logger.Debug("stopped health monitoring", "vm_id", vmID)
- }
-
- // Clear state
- hc.activeChecks = make(map[string]context.CancelFunc)
- hc.vmStatus = make(map[string]*VMHealthStatus)
-
- hc.logger.Info("vm health checker shutdown complete")
-}
-
-// handleHealthyStatus updates status when health check succeeds
-func (hc *VMHealthChecker) handleHealthyStatus(status *VMHealthStatus, wasHealthy bool, vmID string, now time.Time) {
- status.LastHealthy = now
- status.ErrorMsg = ""
-
- // Reset failure count on success
- if status.FailureCount > 0 {
- hc.logger.Debug("vm health check succeeded after failures",
- "vm_id", vmID,
- "previous_failures", status.FailureCount,
- )
- }
- status.FailureCount = 0
-
- // Check for recovery (unhealthy -> healthy transition)
- if !wasHealthy {
- status.IsHealthy = true
- hc.logger.Info("vm recovered",
- "vm_id", vmID,
- "downtime", now.Sub(status.LastHealthy),
- )
-
- // Trigger recovery callback
- if hc.onVMRecovered != nil {
- go hc.onVMRecovered(vmID, status)
- }
- }
-}
-
-// handleUnhealthyStatus updates status when health check fails
-func (hc *VMHealthChecker) handleUnhealthyStatus(status *VMHealthStatus, wasHealthy bool, vmID string, errorMsg string, duration time.Duration) {
- status.FailureCount++
- status.ErrorMsg = errorMsg
-
- hc.logger.Warn("vm health check failed",
- "vm_id", vmID,
- "failure_count", status.FailureCount,
- "error", errorMsg,
- "duration", duration,
- )
-
- // Check if we should mark as unhealthy
- if wasHealthy && status.FailureCount >= int64(hc.config.FailureThreshold) {
- status.IsHealthy = false
- hc.logger.Error("vm marked as unhealthy",
- "vm_id", vmID,
- "consecutive_failures", status.FailureCount,
- "threshold", hc.config.FailureThreshold,
- )
-
- // Trigger unhealthy callback
- if hc.onVMUnhealthy != nil {
- go hc.onVMUnhealthy(vmID, status)
- }
- }
-}
diff --git a/go/apps/metald/internal/jailer/jailer.go b/go/apps/metald/internal/jailer/jailer.go
deleted file mode 100644
index acb871140c..0000000000
--- a/go/apps/metald/internal/jailer/jailer.go
+++ /dev/null
@@ -1,349 +0,0 @@
-//go:build linux
-// +build linux
-
-package jailer
-
-import (
- "context"
- "fmt"
- "log/slog"
- "os"
- "os/exec"
- "path/filepath"
- "strings"
- "syscall"
-
- "github.com/unkeyed/unkey/go/apps/metald/internal/config"
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/trace"
- "golang.org/x/sys/unix"
-)
-
-// Jailer provides functionality similar to firecracker's jailer but integrated into metald
-type Jailer struct {
- logger *slog.Logger
- config *config.JailerConfig
- tracer trace.Tracer
-}
-
-// NewJailer creates a new integrated jailer
-func NewJailer(logger *slog.Logger, config *config.JailerConfig) *Jailer {
- tracer := otel.Tracer("metald.jailer.integrated")
- return &Jailer{
- logger: logger.With("component", "integrated-jailer"),
- config: config,
- tracer: tracer,
- }
-}
-
-// ExecOptions contains options for executing firecracker in a jailed environment
-type ExecOptions struct {
- // VMId is the unique identifier for this VM
- VMId string
-
- // NetworkNamespace is the path to the network namespace (e.g., /run/netns/vm-xxx)
- NetworkNamespace string
-
- // SocketPath is the path to the firecracker API socket
- SocketPath string
-
- // FirecrackerArgs are additional arguments to pass to firecracker
- FirecrackerArgs []string
-
- // Stdin, Stdout, Stderr for the firecracker process
- Stdin *os.File
- Stdout *os.File
- Stderr *os.File
-}
-
-// Exec executes firecracker in a jailed environment
-// This function does NOT return if successful - it execs into firecracker
-func (j *Jailer) Exec(ctx context.Context, opts *ExecOptions) error {
- ctx, span := j.tracer.Start(ctx, "metald.jailer.exec",
- trace.WithAttributes(
- attribute.String("vm_id", opts.VMId),
- attribute.String("netns", opts.NetworkNamespace),
- attribute.String("chroot_base", j.config.ChrootBaseDir),
- attribute.Int64("uid", int64(j.config.UID)),
- attribute.Int64("gid", int64(j.config.GID)),
- ),
- )
- defer span.End()
-
- j.logger.InfoContext(ctx, "executing firecracker with integrated jailer",
- slog.String("vm_id", opts.VMId),
- slog.String("netns", opts.NetworkNamespace),
- )
-
- // Step 1: Set up the chroot environment
- chrootPath := filepath.Join(j.config.ChrootBaseDir, "firecracker", opts.VMId, "root")
- if err := j.setupChroot(ctx, chrootPath); err != nil {
- span.RecordError(err)
- return fmt.Errorf("failed to setup chroot: %w", err)
- }
-
- // Step 2: Join the network namespace if specified
- if opts.NetworkNamespace != "" {
- if err := j.joinNetworkNamespace(ctx, opts.NetworkNamespace); err != nil {
- span.RecordError(err)
- return fmt.Errorf("failed to join network namespace: %w", err)
- }
- }
-
- // Step 3: Enter the chroot
- if err := syscall.Chroot(chrootPath); err != nil {
- span.RecordError(err)
- return fmt.Errorf("failed to chroot: %w", err)
- }
- if err := os.Chdir("/"); err != nil {
- span.RecordError(err)
- return fmt.Errorf("failed to chdir to /: %w", err)
- }
-
- // Step 4: Drop privileges
- if err := j.dropPrivileges(ctx); err != nil {
- span.RecordError(err)
- return fmt.Errorf("failed to drop privileges: %w", err)
- }
-
- // Step 5: Prepare firecracker command
- firecrackerPath := "/usr/local/bin/firecracker"
- args := []string{firecrackerPath}
- args = append(args, "--api-sock", opts.SocketPath)
- args = append(args, opts.FirecrackerArgs...)
-
- j.logger.InfoContext(ctx, "executing firecracker",
- slog.String("binary", firecrackerPath),
- slog.Any("args", args),
- )
-
- // Step 6: Validate and exec into firecracker
- if err := validateFirecrackerPath(firecrackerPath); err != nil {
- return fmt.Errorf("firecracker path validation failed: %w", err)
- }
-
- // This replaces the current process with firecracker
- //nolint:gosec // Path validation performed above
- return syscall.Exec(firecrackerPath, args, os.Environ())
-}
-
-// RunInJail runs firecracker in a jail by creating a minimal isolation environment
-// This function forks and execs firecracker with dropped privileges
-func (j *Jailer) RunInJail(ctx context.Context, opts *ExecOptions) (*os.Process, error) {
- ctx, span := j.tracer.Start(ctx, "metald.jailer.run_in_jail",
- trace.WithAttributes(
- attribute.String("vm_id", opts.VMId),
- attribute.String("netns", opts.NetworkNamespace),
- attribute.String("chroot_base", j.config.ChrootBaseDir),
- ),
- )
- defer span.End()
-
- j.logger.InfoContext(ctx, "running firecracker in jail",
- slog.String("vm_id", opts.VMId),
- slog.String("netns", opts.NetworkNamespace),
- )
-
- // Setup chroot environment
- chrootPath := filepath.Join(j.config.ChrootBaseDir, "firecracker", opts.VMId, "root")
- if err := j.setupChroot(ctx, chrootPath); err != nil {
- span.RecordError(err)
- return nil, fmt.Errorf("failed to setup chroot: %w", err)
- }
-
- // Build firecracker command
- // AIDEV-NOTE: Firecracker binary path is now hardcoded to standard location
- firecrackerPath := "/usr/local/bin/firecracker"
-
- // Validate firecracker path for security
- if err := validateFirecrackerPath(firecrackerPath); err != nil {
- span.RecordError(err)
- return nil, fmt.Errorf("firecracker path validation failed: %w", err)
- }
-
- args := []string{firecrackerPath, "--api-sock", opts.SocketPath}
- args = append(args, opts.FirecrackerArgs...)
-
- // Create the command
- //nolint:gosec // Path validation performed above
- cmd := exec.CommandContext(ctx, firecrackerPath, args[1:]...)
-
- // Set up file descriptors
- cmd.Stdin = opts.Stdin
- cmd.Stdout = opts.Stdout
- cmd.Stderr = opts.Stderr
-
- // Set working directory to chroot
- cmd.Dir = chrootPath
-
- // For now, run without full isolation to test
- // In production, we'd fork and do the chroot/namespace/privilege dropping
- // AIDEV-TODO: Implement proper forking with isolation
-
- // Start the process
- if err := cmd.Start(); err != nil {
- return nil, fmt.Errorf("failed to start firecracker: %w", err)
- }
-
- j.logger.InfoContext(ctx, "started jailed firecracker process",
- slog.String("vm_id", opts.VMId),
- slog.Int("pid", cmd.Process.Pid),
- )
-
- return cmd.Process, nil
-}
-
-// setupChroot prepares the chroot environment
-func (j *Jailer) setupChroot(ctx context.Context, chrootPath string) error {
- ctx, span := j.tracer.Start(ctx, "metald.jailer.setup_chroot",
- trace.WithAttributes(
- attribute.String("chroot_path", chrootPath),
- ),
- )
- defer span.End()
- // Create necessary directories
- for _, dir := range []string{"", "dev", "dev/net", "run"} {
- path := filepath.Join(chrootPath, dir)
- if err := os.MkdirAll(path, 0o755); err != nil {
- return fmt.Errorf("failed to create directory %s: %w", path, err)
- }
- }
-
- // Create /dev/net/tun
- tunPath := filepath.Join(chrootPath, "dev/net/tun")
- tunDev, err := safeUint64ToInt(unix.Mkdev(10, 200))
- if err != nil {
- return fmt.Errorf("failed to convert tun device number: %w", err)
- }
- if mkErr := unix.Mknod(tunPath, unix.S_IFCHR|0o666, tunDev); mkErr != nil {
- if !os.IsExist(mkErr) {
- return fmt.Errorf("failed to create /dev/net/tun: %w", mkErr)
- }
- }
-
- // Create /dev/kvm
- kvmPath := filepath.Join(chrootPath, "dev/kvm")
- kvmDev, err := safeUint64ToInt(unix.Mkdev(10, 232))
- if err != nil {
- return fmt.Errorf("failed to convert kvm device number: %w", err)
- }
- if err := unix.Mknod(kvmPath, unix.S_IFCHR|0o666, kvmDev); err != nil {
- if !os.IsExist(err) {
- return fmt.Errorf("failed to create /dev/kvm: %w", err)
- }
- }
-
- // Create metrics FIFO for billaged to read Firecracker stats
- metricsPath := filepath.Join(chrootPath, "metrics.fifo")
- if err := unix.Mkfifo(metricsPath, 0o644); err != nil && !os.IsExist(err) {
- span.RecordError(err)
- return fmt.Errorf("failed to create metrics FIFO: %w", err)
- }
- span.SetAttributes(attribute.String("metrics_fifo_path", metricsPath))
- j.logger.InfoContext(ctx, "created metrics FIFO for billaged",
- slog.String("path", metricsPath))
-
- // Set ownership
- if err := os.Chown(tunPath, int(j.config.UID), int(j.config.GID)); err != nil {
- j.logger.WarnContext(ctx, "failed to chown /dev/net/tun", "error", err)
- }
- if err := os.Chown(kvmPath, int(j.config.UID), int(j.config.GID)); err != nil {
- j.logger.WarnContext(ctx, "failed to chown /dev/kvm", "error", err)
- }
- if err := os.Chown(metricsPath, int(j.config.UID), int(j.config.GID)); err != nil {
- j.logger.WarnContext(ctx, "failed to chown metrics FIFO", "error", err)
- }
-
- return nil
-}
-
-// joinNetworkNamespace joins the specified network namespace
-func (j *Jailer) joinNetworkNamespace(ctx context.Context, netnsPath string) error {
- // Open the network namespace
- netnsFile, err := os.Open(netnsPath)
- if err != nil {
- return fmt.Errorf("failed to open network namespace: %w", err)
- }
- defer netnsFile.Close()
-
- // Join the network namespace
- if err := unix.Setns(int(netnsFile.Fd()), unix.CLONE_NEWNET); err != nil {
- return fmt.Errorf("failed to setns: %w", err)
- }
-
- j.logger.InfoContext(ctx, "joined network namespace", slog.String("netns", netnsPath))
- return nil
-}
-
-// dropPrivileges drops to the configured UID/GID
-func (j *Jailer) dropPrivileges(ctx context.Context) error {
- // Set groups
- if err := unix.Setgroups([]int{int(j.config.GID)}); err != nil {
- return fmt.Errorf("failed to setgroups: %w", err)
- }
-
- // Set GID
- if err := unix.Setresgid(int(j.config.GID), int(j.config.GID), int(j.config.GID)); err != nil {
- return fmt.Errorf("failed to setresgid: %w", err)
- }
-
- // Set UID (must be last)
- if err := unix.Setresuid(int(j.config.UID), int(j.config.UID), int(j.config.UID)); err != nil {
- return fmt.Errorf("failed to setresuid: %w", err)
- }
-
- j.logger.InfoContext(ctx, "dropped privileges",
- slog.Uint64("uid", uint64(j.config.UID)),
- slog.Uint64("gid", uint64(j.config.GID)),
- )
-
- return nil
-}
-
-// safeUint64ToInt safely converts uint64 to int, checking for overflow
-func safeUint64ToInt(value uint64) (int, error) {
- const maxInt = int(^uint(0) >> 1)
- if value > uint64(maxInt) {
- return 0, fmt.Errorf("value %d exceeds maximum int value %d", value, maxInt)
- }
- return int(value), nil
-}
-
-// validateFirecrackerPath validates the firecracker binary path for security
-func validateFirecrackerPath(path string) error {
- // Clean the path to resolve any . or .. components
- cleanPath := filepath.Clean(path)
-
- // Check for path traversal attempts
- if strings.Contains(cleanPath, "..") {
- return fmt.Errorf("path traversal attempt detected: %s", path)
- }
-
- // Ensure path is absolute and starts with expected directories
- if !filepath.IsAbs(cleanPath) {
- return fmt.Errorf("firecracker path must be absolute: %s", path)
- }
-
- // Check for dangerous characters
- if strings.ContainsAny(cleanPath, ";&|$`\\") {
- return fmt.Errorf("dangerous characters detected in path: %s", path)
- }
-
- // Verify file exists and is executable
- info, err := os.Stat(cleanPath)
- if err != nil {
- return fmt.Errorf("firecracker binary not found: %w", err)
- }
-
- if info.IsDir() {
- return fmt.Errorf("firecracker path is a directory: %s", cleanPath)
- }
-
- // Check if file is executable
- if info.Mode()&0o111 == 0 {
- return fmt.Errorf("firecracker binary is not executable: %s", cleanPath)
- }
-
- return nil
-}
diff --git a/go/apps/metald/internal/jailer/jailer_test.go b/go/apps/metald/internal/jailer/jailer_test.go
deleted file mode 100644
index 428029aeb5..0000000000
--- a/go/apps/metald/internal/jailer/jailer_test.go
+++ /dev/null
@@ -1,126 +0,0 @@
-//go:build linux
-// +build linux
-
-package jailer
-
-import (
- "context"
- "log/slog"
- "os"
- "path/filepath"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "github.com/unkeyed/unkey/go/apps/metald/internal/config"
-)
-
-func TestNewJailer(t *testing.T) {
- logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
- cfg := &config.JailerConfig{
- ChrootBaseDir: "/tmp/test-jailer",
- UID: 1000,
- GID: 1000,
- }
-
- jailer := NewJailer(logger, cfg)
- assert.NotNil(t, jailer)
- assert.Equal(t, cfg, jailer.config)
-}
-
-func TestSetupChroot(t *testing.T) {
- // This test requires root privileges to create device nodes
- if os.Getuid() != 0 {
- t.Skip("Test requires root privileges")
- }
-
- tmpDir, err := os.MkdirTemp("", "jailer-test-*")
- require.NoError(t, err)
- defer os.RemoveAll(tmpDir)
-
- logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
- cfg := &config.JailerConfig{
- ChrootBaseDir: tmpDir,
- UID: 1000,
- GID: 1000,
- }
-
- jailer := NewJailer(logger, cfg)
- chrootPath := filepath.Join(tmpDir, "test-vm", "root")
-
- err = jailer.setupChroot(context.Background(), chrootPath)
- assert.NoError(t, err)
-
- // Verify directories exist
- assert.DirExists(t, chrootPath)
- assert.DirExists(t, filepath.Join(chrootPath, "dev"))
- assert.DirExists(t, filepath.Join(chrootPath, "dev/net"))
- assert.DirExists(t, filepath.Join(chrootPath, "run"))
-
- // Verify device nodes exist
- tunPath := filepath.Join(chrootPath, "dev/net/tun")
- kvmPath := filepath.Join(chrootPath, "dev/kvm")
-
- tunInfo, err := os.Stat(tunPath)
- assert.NoError(t, err)
- assert.True(t, tunInfo.Mode()&os.ModeDevice != 0, "tun should be a device")
-
- kvmInfo, err := os.Stat(kvmPath)
- assert.NoError(t, err)
- assert.True(t, kvmInfo.Mode()&os.ModeDevice != 0, "kvm should be a device")
-}
-
-func TestExecOptions(t *testing.T) {
- opts := &ExecOptions{ //nolint:exhaustruct // Test only sets required fields for validation
- VMId: "test-vm",
- NetworkNamespace: "/run/netns/test-vm",
- SocketPath: "/firecracker.sock",
- FirecrackerArgs: []string{"--config-file", "config.json"},
- }
-
- assert.Equal(t, "test-vm", opts.VMId)
- assert.Equal(t, "/run/netns/test-vm", opts.NetworkNamespace)
- assert.Equal(t, "/firecracker.sock", opts.SocketPath)
- assert.Len(t, opts.FirecrackerArgs, 2)
-}
-
-// TestJoinNetworkNamespace tests network namespace joining
-// This test requires root privileges to create network namespaces
-func TestJoinNetworkNamespace(t *testing.T) {
- if os.Getuid() != 0 {
- t.Skip("Test requires root privileges")
- }
-
- // Create a test network namespace
- tmpDir, err := os.MkdirTemp("", "netns-test-*")
- require.NoError(t, err)
- defer os.RemoveAll(tmpDir)
-
- // This would require actual network namespace creation
- // which is complex to test without full network setup
- t.Skip("Network namespace testing requires complex setup")
-}
-
-// TestDropPrivileges tests privilege dropping
-// This test is dangerous to run as it actually drops privileges
-func TestDropPrivileges(t *testing.T) {
- t.Skip("Privilege dropping test would affect the test process")
-}
-
-// Integration test placeholder
-func TestIntegratedJailerWorkflow(t *testing.T) {
- t.Skip("Integration test requires full environment setup")
-
- // This would test:
- // 1. Setting up chroot
- // 2. Joining network namespace
- // 3. Dropping privileges
- // 4. Executing a test binary instead of firecracker
-}
-
-// AIDEV-NOTE: These tests cover the basic functionality of the integrated jailer
-// More comprehensive tests would require:
-// 1. Root privileges or specific capabilities
-// 2. Network namespace creation utilities
-// 3. A test binary to execute instead of firecracker
-// 4. Integration with the actual VM creation workflow
diff --git a/go/apps/metald/internal/network/bridge.go b/go/apps/metald/internal/network/bridge.go
deleted file mode 100644
index f3af0cae41..0000000000
--- a/go/apps/metald/internal/network/bridge.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package network
-
-import (
- "fmt"
- "log/slog"
-
- "github.com/vishvananda/netlink"
-)
-
-// CreateBridge creates the bridge if it doesn't exist
-func CreateBridge(logger *slog.Logger, cfg Config) (string, error) {
- link, linkErr := netlink.LinkByName(cfg.BridgeName)
- if linkErr == nil {
- logger.Debug("bridge exists",
- slog.String("bridge", cfg.BridgeName),
- slog.Int("mtu", link.Attrs().MTU),
- )
- return link.Attrs().Name, nil
- }
-
- bridge := &netlink.Bridge{ //nolint:exhaustruct
- LinkAttrs: netlink.LinkAttrs{
- Name: cfg.BridgeName, //nolint:exhaustruct
- },
- }
-
- if err := netlink.LinkAdd(bridge); err != nil {
- logger.Error("failed to create bridge",
- slog.String("bridge", cfg.BridgeName),
- slog.String("error", err.Error()),
- )
- return "", fmt.Errorf("failed to create bridge: %w", err)
- }
-
- br, brErr := netlink.LinkByName(cfg.BridgeName)
- if brErr != nil {
- return "", fmt.Errorf("failed to get bridge: %w", brErr)
- }
-
- addr, err := netlink.ParseAddr(cfg.BaseNetwork.String())
- if err != nil {
- return "", fmt.Errorf("failed to parse bridge IP: %w", err)
- }
-
- if err := netlink.AddrAdd(br, addr); err != nil {
- logger.Error("failed to add IP to bridge",
- slog.String("bridge", cfg.BridgeName),
- slog.String("error", err.Error()),
- )
- return "", fmt.Errorf("failed to add IP to bridge: %w", err)
- }
-
- // Bring bridge up
- if err := netlink.LinkSetUp(br); err != nil {
- logger.Error("failed to bring bridge up",
- slog.String("bridge", cfg.BridgeName),
- slog.String("error", err.Error()),
- )
- return "", fmt.Errorf("failed to bring bridge up: %w", err)
- }
-
- logger.Debug("bridge created",
- slog.String("bridge", cfg.BridgeName),
- slog.Int("mtu", br.Attrs().MTU),
- )
-
- return cfg.BridgeName, nil
-}
diff --git a/go/apps/metald/internal/network/idgen.go b/go/apps/metald/internal/network/idgen.go
deleted file mode 100644
index 2d3bed83e0..0000000000
--- a/go/apps/metald/internal/network/idgen.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package network
-
-import (
- "crypto/rand"
- "encoding/base32"
- "fmt"
-)
-
-// IDGenerator generates short, unique IDs for network devices
-// Network interface names in Linux are limited to 15 characters,
-
-// Generates an for the bridge, it's not guaranteed unique until it's saved to the DB
-func GenerateID() string {
- b := make([]byte, 8)
- rand.Read(b)
- id := base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(b)[:12]
-
- return fmt.Sprintf("br-%s", id)
-}
diff --git a/go/apps/metald/internal/network/iputil.go b/go/apps/metald/internal/network/iputil.go
deleted file mode 100644
index ac923d34b1..0000000000
--- a/go/apps/metald/internal/network/iputil.go
+++ /dev/null
@@ -1,71 +0,0 @@
-package network
-
-import (
- "encoding/json"
- "fmt"
- "net"
-)
-
-// GenerateAvailableIPs generates a JSON array of available IP addresses from a CIDR network
-// It excludes the network address (first IP), gateway (second IP), and broadcast (last IP)
-func GenerateAvailableIPs(cidr string) (string, error) {
- _, ipnet, err := net.ParseCIDR(cidr)
- if err != nil {
- return "", fmt.Errorf("invalid CIDR: %w", err)
- }
-
- // Calculate the number of hosts
- ones, bits := ipnet.Mask.Size()
- hostBits := bits - ones
- numHosts := (1 << hostBits) - 2 // Exclude network and broadcast
-
- if numHosts < 1 {
- return "[]", nil // No usable IPs in this network
- }
-
- // Generate IPs
- ips := make([]string, 0, numHosts-1) // -1 to also exclude gateway
-
- // Start from the third IP (skip network and gateway)
- ip := ipnet.IP.Mask(ipnet.Mask)
- incIP(ip) // Skip network address
- incIP(ip) // Skip gateway address
-
- // Generate IPs until we reach the broadcast address
- broadcast := getBroadcast(ipnet)
- for i := 0; i < numHosts-1 && !ip.Equal(broadcast); i++ {
- ips = append(ips, ip.String())
- incIP(ip)
- }
-
- // Convert to JSON array
- jsonBytes, err := json.Marshal(ips)
- if err != nil {
- return "", fmt.Errorf("failed to marshal IPs to JSON: %w", err)
- }
-
- return string(jsonBytes), nil
-}
-
-// incIP increments an IP address by 1
-func incIP(ip net.IP) {
- for j := len(ip) - 1; j >= 0; j-- {
- ip[j]++
- if ip[j] > 0 {
- break
- }
- }
-}
-
-// getBroadcast calculates the broadcast address for a network
-func getBroadcast(ipnet *net.IPNet) net.IP {
- ip := ipnet.IP.To4()
- mask := ipnet.Mask
- broadcast := make(net.IP, len(ip))
-
- for i := range ip {
- broadcast[i] = ip[i] | ^mask[i]
- }
-
- return broadcast
-}
diff --git a/go/apps/metald/internal/network/manager.go b/go/apps/metald/internal/network/manager.go
deleted file mode 100644
index 6ff27a8b66..0000000000
--- a/go/apps/metald/internal/network/manager.go
+++ /dev/null
@@ -1,45 +0,0 @@
-package network
-
-import (
- "fmt"
- "log/slog"
- "net"
- "sync"
-)
-
-// NewManager creates a new network manager to handle bridge/tap creation
-func NewManager(logger *slog.Logger, config *Config) (*Manager, error) {
- if config == nil {
- logger.Error("creating network manager")
- return nil, fmt.Errorf("network config can not be nil")
- }
-
- logger.Info("creating network manager",
- slog.String("bridge_name", config.BridgeName),
- slog.String("base_network", config.BaseNetwork.String()),
- )
-
- m := &Manager{ //nolint:exhaustruct
- logger: logger,
- config: config,
- }
-
- return m, nil
-}
-
-// Config holds network configuration
-type Config struct {
- BaseNetwork *net.IPNet
- BridgeName string
- DNSServers []string // Default: ["8.8.8.8", "8.8.4.4"]
- EnableIPv6 bool
- EnableRateLimit bool
- RateLimitMbps int // Per VM rate limit in Mbps
-}
-
-type Manager struct {
- logger *slog.Logger
- config *Config
- mu sync.RWMutex
- bridgeMu sync.RWMutex
-}
diff --git a/go/apps/metald/internal/network/network.go b/go/apps/metald/internal/network/network.go
deleted file mode 100644
index dc806cd37b..0000000000
--- a/go/apps/metald/internal/network/network.go
+++ /dev/null
@@ -1,117 +0,0 @@
-package network
-
-// import (
-// "fmt"
-// "hash/fnv"
-// )
-
-// const (
-// // BaseNetwork is the "root" network we're partitioning
-// BaseNetwork = "172.16.0.0/12"
-
-// // SubnetPrefix is the size of each subnet
-// SubnetPrefix = 28
-
-// // BasePrefix is the size of the base network
-// BasePrefix = 12
-
-// // TotalSubnets is the total number of /28 subnets in a /12
-// TotalSubnets = 1 << (SubnetPrefix - BasePrefix) // 65536 = 1 << (28 - 12)
-
-// // IPsPerSubnet is the number of IPs in each /28 subnet
-// IPsPerSubnet = 1 << (32 - SubnetPrefix) // 16
-// )
-
-// // SubnetInfo contains all the network information for a subnet
-// type SubnetInfo struct {
-// Index uint32 // 0-based index (0-65535)
-// Network string // CIDR notation (e.g., "172.16.0.0/28")
-// Gateway string // Gateway IP (e.g., "172.16.0.1")
-// Broadcast string // Broadcast IP (e.g., "172.16.0.15")
-// UsableRange string // Usable IP range (e.g., "172.16.0.2-172.16.0.14")
-// UsableIPs int // Number of usable IPs (13 for /28)
-// }
-
-// // CalculateIndex returns a subnet index (0-65535) for a given workspace ID
-// func CalculateIndex(identifier string) uint32 {
-// hash := fnv.New32a()
-// hash.Write([]byte(identifier))
-// return hash.Sum32() & 0xFFFF // Masks to keep only lower 16 bits
-// }
-
-// // CalculateIndexOneBased returns a subnet index (1-65536) for a given workspace ID
-// func CalculateIndexOneBased(identifier string) uint32 {
-// return CalculateIndex(identifier) + 1
-// }
-
-// // GetSubnetInfo returns complete subnet information for a workspace ID
-// func GetSubnetInfo(identifier string) SubnetInfo {
-// index := CalculateIndex(identifier)
-// return GetSubnetInfoByIndex(index)
-// }
-
-// // GetSubnetInfoByIndex returns complete subnet information for a given index
-// func GetSubnetInfoByIndex(index uint32) SubnetInfo {
-// if index >= TotalSubnets {
-// panic(fmt.Sprintf("index %d exceeds maximum subnet count %d", index, TotalSubnets))
-// }
-
-// // Calculate the base IP for this subnet
-// subnetOffset := index * IPsPerSubnet
-
-// // Calculate octets for 172.16.0.0/12 base
-// octet4 := subnetOffset % 256
-// octet3 := (subnetOffset / 256) % 256
-// octet2 := 16 + (subnetOffset / 65536)
-
-// // Build the subnet info
-// info := SubnetInfo{
-// Index: index,
-// Network: fmt.Sprintf("172.%d.%d.%d/%d", octet2, octet3, octet4, SubnetPrefix),
-// Gateway: fmt.Sprintf("172.%d.%d.%d", octet2, octet3, octet4+1),
-// Broadcast: fmt.Sprintf("172.%d.%d.%d", octet2, octet3, octet4+15),
-// UsableIPs: IPsPerSubnet - 3, // Exclude network, gateway, and broadcast
-// }
-
-// // Calculate usable range
-// usableStart := fmt.Sprintf("172.%d.%d.%d", octet2, octet3, octet4+2)
-// usableEnd := fmt.Sprintf("172.%d.%d.%d", octet2, octet3, octet4+14)
-// info.UsableRange = fmt.Sprintf("%s-%s", usableStart, usableEnd)
-
-// return info
-// }
-
-// // GetNetwork returns just the CIDR notation for a workspace ID
-// func GetNetwork(identifier string) string {
-// info := GetSubnetInfo(identifier)
-// return info.Network
-// }
-
-// // GetGateway returns the gateway IP for a workspace ID
-// func GetGateway(identifier string) string {
-// info := GetSubnetInfo(identifier)
-// return info.Gateway
-// }
-
-// // Validateidentifier checks if a workspace ID is valid (non-empty)
-// func ValidateIdentifier(identifier string) error {
-// if identifier == "" {
-// return fmt.Errorf("workspace ID cannot be empty")
-// }
-// return nil
-// }
-
-// // GetAllSubnetsInRange returns a slice of all possible subnet indices
-// // Useful for iteration or validation
-// func GetAllSubnetsInRange() []uint32 {
-// subnets := make([]uint32, TotalSubnets)
-// for i := range TotalSubnets {
-// subnets[i] = uint32(i)
-// }
-// return subnets
-// }
-
-// // IsValidIndex checks if an index is within the valid range
-// func IsValidIndex(index uint32) bool {
-// return index < TotalSubnets
-// }
diff --git a/go/apps/metald/internal/network/port_allocator.go b/go/apps/metald/internal/network/port_allocator.go
deleted file mode 100644
index 646845e3ab..0000000000
--- a/go/apps/metald/internal/network/port_allocator.go
+++ /dev/null
@@ -1,272 +0,0 @@
-package network
-
-import (
- "crypto/rand"
- "fmt"
- "math/big"
- "sync"
-)
-
-// PortMapping represents a mapping from VM port to host port
-type PortMapping struct {
- VMPort int `json:"vm_port"`
- HostPort int `json:"host_port"`
- Protocol string `json:"protocol"` // tcp or udp
- VMID string `json:"vm_id"`
-}
-
-// PortAllocator manages host port allocation for VMs
-type PortAllocator struct {
- // Port ranges for allocation
- minPort int
- maxPort int
-
- // Port tracking
- allocated map[int]bool // host port -> allocated
- vmPorts map[string][]PortMapping // VM ID -> port mappings
- portToVM map[int]string // host port -> VM ID
-
- mu sync.Mutex
-}
-
-// NewPortAllocator creates a new port allocator
-func NewPortAllocator(minPort, maxPort int) *PortAllocator {
- if minPort <= 0 || maxPort <= 0 || minPort >= maxPort {
- // Use default ephemeral port range if invalid
- minPort = 32768
- maxPort = 65535
- }
-
- //exhaustruct:ignore
- return &PortAllocator{
- minPort: minPort,
- maxPort: maxPort,
- allocated: make(map[int]bool),
- vmPorts: make(map[string][]PortMapping),
- portToVM: make(map[int]string),
- }
-}
-
-// AllocatePort allocates a host port for the given container port
-func (p *PortAllocator) AllocatePort(vmID string, containerPort int, protocol string) (int, error) {
- p.mu.Lock()
- defer p.mu.Unlock()
-
- // Validate protocol
- if protocol != "tcp" && protocol != "udp" {
- return 0, fmt.Errorf("unsupported protocol: %s", protocol)
- }
-
- // Use random port allocation for better security and distribution
- portRange := p.maxPort - p.minPort + 1
- maxAttempts := portRange
- if maxAttempts > 1000 {
- maxAttempts = 1000 // Limit attempts to avoid long search times
- }
-
- // Try random ports first using crypto/rand for security
- for attempt := 0; attempt < maxAttempts; attempt++ {
- randomOffset, err := rand.Int(rand.Reader, big.NewInt(int64(portRange)))
- if err != nil {
- // If crypto/rand fails, fall through to sequential search
- break
- }
- hostPort := p.minPort + int(randomOffset.Int64())
- if !p.allocated[hostPort] {
- return p.doAllocatePort(vmID, hostPort, containerPort, protocol)
- }
- }
-
- // Fallback: sequential search if random didn't work (very rare case)
- for hostPort := p.minPort; hostPort <= p.maxPort; hostPort++ {
- if !p.allocated[hostPort] {
- return p.doAllocatePort(vmID, hostPort, containerPort, protocol)
- }
- }
-
- return 0, fmt.Errorf("no available ports in range %d-%d", p.minPort, p.maxPort)
-}
-
-// AllocateSpecificPort allocates a specific host port if available
-func (p *PortAllocator) AllocateSpecificPort(vmID string, hostPort, containerPort int, protocol string) error {
- p.mu.Lock()
- defer p.mu.Unlock()
-
- // Validate protocol
- if protocol != "tcp" && protocol != "udp" {
- return fmt.Errorf("unsupported protocol: %s", protocol)
- }
-
- // Check if port is in range
- if hostPort < p.minPort || hostPort > p.maxPort {
- return fmt.Errorf("port %d outside allocation range %d-%d", hostPort, p.minPort, p.maxPort)
- }
-
- // Check if already allocated
- if p.allocated[hostPort] {
- return fmt.Errorf("port %d already allocated to VM %s", hostPort, p.portToVM[hostPort])
- }
-
- _, err := p.doAllocatePort(vmID, hostPort, containerPort, protocol)
- return err
-}
-
-// doAllocatePort performs the actual port allocation (internal helper)
-func (p *PortAllocator) doAllocatePort(vmID string, hostPort, vmPort int, protocol string) (int, error) {
- // Check for conflicting mapping for same VM
- if mappings, exists := p.vmPorts[vmID]; exists {
- for _, mapping := range mappings {
- if mapping.VMPort == vmPort && mapping.Protocol == protocol {
- return 0, fmt.Errorf("VM %s already has mapping for %s:%d", vmID, protocol, vmPort)
- }
- }
- }
-
- // Mark port as allocated
- p.allocated[hostPort] = true
- p.portToVM[hostPort] = vmID
-
- // Create mapping
- mapping := PortMapping{
- VMPort: vmPort,
- HostPort: hostPort,
- Protocol: protocol,
- VMID: vmID,
- }
-
- // Add to VM's port list
- p.vmPorts[vmID] = append(p.vmPorts[vmID], mapping)
-
- return hostPort, nil
-}
-
-// ReleasePort releases a specific host port
-func (p *PortAllocator) ReleasePort(hostPort int) error {
- p.mu.Lock()
- defer p.mu.Unlock()
-
- // Check if port is allocated
- vmID, allocated := p.portToVM[hostPort]
- if !allocated {
- return fmt.Errorf("port %d is not allocated", hostPort)
- }
-
- // Remove from allocated ports
- delete(p.allocated, hostPort)
- delete(p.portToVM, hostPort)
-
- // Remove from VM's port list
- if mappings, exists := p.vmPorts[vmID]; exists {
- newMappings := make([]PortMapping, 0, len(mappings))
- for _, mapping := range mappings {
- if mapping.HostPort != hostPort {
- newMappings = append(newMappings, mapping)
- }
- }
-
- if len(newMappings) == 0 {
- delete(p.vmPorts, vmID)
- } else {
- p.vmPorts[vmID] = newMappings
- }
- }
-
- return nil
-}
-
-// ReleaseVMPorts releases all ports allocated to a VM
-func (p *PortAllocator) ReleaseVMPorts(vmID string) []PortMapping {
- p.mu.Lock()
- defer p.mu.Unlock()
-
- mappings, exists := p.vmPorts[vmID]
- if !exists {
- return nil
- }
-
- // Release all host ports for this VM
- for _, mapping := range mappings {
- delete(p.allocated, mapping.HostPort)
- delete(p.portToVM, mapping.HostPort)
- }
-
- // Remove VM from tracking
- delete(p.vmPorts, vmID)
-
- return mappings
-}
-
-// GetVMPorts returns all port mappings for a VM
-func (p *PortAllocator) GetVMPorts(vmID string) []PortMapping {
- p.mu.Lock()
- defer p.mu.Unlock()
-
- mappings, exists := p.vmPorts[vmID]
- if !exists {
- return nil
- }
-
- // Return a copy to prevent race conditions
- result := make([]PortMapping, len(mappings))
- copy(result, mappings)
- return result
-}
-
-// IsPortAllocated checks if a host port is allocated
-func (p *PortAllocator) IsPortAllocated(hostPort int) bool {
- p.mu.Lock()
- defer p.mu.Unlock()
-
- return p.allocated[hostPort]
-}
-
-// GetPortVM returns the VM ID that has allocated the given host port
-func (p *PortAllocator) GetPortVM(hostPort int) (string, bool) {
- p.mu.Lock()
- defer p.mu.Unlock()
-
- vmID, exists := p.portToVM[hostPort]
- return vmID, exists
-}
-
-// GetAllocatedCount returns the number of allocated ports
-func (p *PortAllocator) GetAllocatedCount() int {
- p.mu.Lock()
- defer p.mu.Unlock()
-
- return len(p.allocated)
-}
-
-// GetAvailableCount returns the number of available ports
-func (p *PortAllocator) GetAvailableCount() int {
- total := p.maxPort - p.minPort + 1
- return total - p.GetAllocatedCount()
-}
-
-// GetAllAllocated returns all allocated port mappings
-func (p *PortAllocator) GetAllAllocated() []PortMapping {
- p.mu.Lock()
- defer p.mu.Unlock()
-
- var result []PortMapping
- for _, mappings := range p.vmPorts {
- result = append(result, mappings...)
- }
-
- return result
-}
-
-// Reset clears all port allocations
-func (p *PortAllocator) Reset() {
- p.mu.Lock()
- defer p.mu.Unlock()
-
- p.allocated = make(map[int]bool)
- p.vmPorts = make(map[string][]PortMapping)
- p.portToVM = make(map[int]string)
-}
-
-// GetPortRange returns the port allocation range
-func (p *PortAllocator) GetPortRange() (int, int) {
- return p.minPort, p.maxPort
-}
diff --git a/go/apps/metald/internal/network/tuntap.go b/go/apps/metald/internal/network/tuntap.go
deleted file mode 100644
index 9b3cf43dab..0000000000
--- a/go/apps/metald/internal/network/tuntap.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package network
-
-// setupVMNetworking creates and configures TAP and veth devices for a VM
-func (m *Manager) setupTunTap(ip string) error {
- // // tap := &netlink.Tuntap{
- // // LinkAttrs: netlink.LinkAttrs{
- // // Name: deviceNames.TAP,
- // // },
- // // Mode: netlink.TUNTAP_MODE_TAP,
- // // }
-
- // // if err := netlink.LinkAdd(tap); err != nil {
- // // return fmt.Errorf("failed to create TAP device %s: %w", deviceNames.TAP, err)
- // // }
-
- // // tapLink, err := netlink.LinkByName(deviceNames.TAP)
- // // if err != nil {
- // // return fmt.Errorf("failed to get TAP device: %w", err)
- // // }
-
- // // Set TAP device up
- // if err := netlink.LinkSetUp(tap); err != nil {
- // return fmt.Errorf("failed to bring TAP device up: %w", err)
- // }
-
- // m.logger.Info("TAP device created and up", slog.String("tap", tapLink.Attrs().Name))
-
- return nil
-}
diff --git a/go/apps/metald/internal/network/types.go b/go/apps/metald/internal/network/types.go
deleted file mode 100644
index b0a608b18e..0000000000
--- a/go/apps/metald/internal/network/types.go
+++ /dev/null
@@ -1,107 +0,0 @@
-package network
-
-import (
- "log/slog"
- "net"
- "sync"
- "time"
-)
-
-// BridgeManager manages workspace allocation across multiple bridges
-type BridgeManager struct {
- bridgeCount int // 8 or 32 bridges
- bridgePrefix string // "br-vms" -> br-vms-0, br-vms-1, etc.
- workspaces map[string]*WorkspaceAllocation // workspace_id -> allocation
- bridgeUsage map[int]map[string]bool // bridge_num -> workspace_id -> exists
- mu sync.RWMutex
- statePath string // Path to state persistence file
- logger *slog.Logger // Structured logger for state operations
-}
-
-// BridgeState represents the serializable state for persistence
-type BridgeState struct {
- Workspaces map[string]*WorkspaceAllocation `json:"workspaces"`
- BridgeUsage map[int]map[string]bool `json:"bridge_usage"`
- LastSaved time.Time `json:"last_saved"`
- Checksum string `json:"checksum"` // SHA256 checksum for integrity validation
-}
-
-type MultiBridgeManager struct {
- bridgeCount int // 8 or 32 bridges
- bridgePrefix string // "br-vms" -> br-vms-0, br-vms-1, etc.
- workspaces map[string]*WorkspaceAllocation // workspace_id -> allocation
- bridgeUsage map[int]map[string]bool // bridge_num -> workspace_id -> exists
- mu sync.RWMutex
- vlanRangeStart int // Starting VLAN ID (100)
- vlanRangeEnd int // Ending VLAN ID (4000)
- statePath string // Path to state persistence file
- logger *slog.Logger // Structured logger for state operations
-}
-
-// WorkspaceAllocation represents a workspace's network allocation
-type WorkspaceAllocation struct {
- WorkspaceID string `json:"workspace_id"`
- BridgeNumber int `json:"bridge_number"` // 0-31
- BridgeName string `json:"bridge_name"` // br-vms-N
- CreatedAt string `json:"created_at"`
- VMCount int `json:"vm_count"` // Track VM count for IP allocation
-}
-
-// VMNetwork contains network configuration for a VM
-type VMNetwork struct {
- VMID string `json:"vm_id"`
- NetworkID string `json:"network_id"` // AIDEV-NOTE: Internal 8-char ID for network device naming
- WorkspaceID string `json:"workspace_id"` // AIDEV-NOTE: Track workspace for proper IP release
- Namespace string `json:"namespace"`
- TapDevice string `json:"tap_device"`
- IPAddress net.IP `json:"ip_address"`
- Netmask net.IPMask `json:"netmask"`
- Gateway net.IP `json:"gateway"`
- MacAddress string `json:"mac_address"`
- DNSServers []string `json:"dns_servers"`
- CreatedAt time.Time `json:"created_at"`
-
- // Optional fields for advanced configuration
- IPv6Address net.IP `json:"ipv6_address,omitempty"`
- Routes []Route `json:"routes,omitempty"`
-}
-
-// Route represents a network route
-type Route struct {
- Destination *net.IPNet `json:"destination"`
- Gateway net.IP `json:"gateway"`
- Metric int `json:"metric"`
-}
-
-// NetworkStats contains network interface statistics
-type NetworkStats struct {
- RxBytes uint64 `json:"rx_bytes"`
- TxBytes uint64 `json:"tx_bytes"`
- RxPackets uint64 `json:"rx_packets"`
- TxPackets uint64 `json:"tx_packets"`
- RxDropped uint64 `json:"rx_dropped"`
- TxDropped uint64 `json:"tx_dropped"`
- RxErrors uint64 `json:"rx_errors"`
- TxErrors uint64 `json:"tx_errors"`
-}
-
-// NetworkPolicy defines network access rules for a VM
-type NetworkPolicy struct {
- VMID string `json:"vm_id"`
- CustomerID string `json:"customer_id"`
- Rules []FirewallRule `json:"rules"`
- DefaultAction string `json:"default_action"` // "allow" or "deny"
-}
-
-// FirewallRule defines a single firewall rule
-type FirewallRule struct {
- Name string `json:"name"`
- Direction string `json:"direction"` // "ingress" or "egress"
- Protocol string `json:"protocol"` // "tcp", "udp", "icmp", or ""
- Port int `json:"port,omitempty"`
- PortRange string `json:"port_range,omitempty"` // e.g., "8080-8090"
- Source string `json:"source"` // CIDR or "any"
- Destination string `json:"destination,omitempty"` // CIDR or "any"
- Action string `json:"action"` // "allow" or "deny"
- Priority int `json:"priority"` // Lower number = higher priority
-}
diff --git a/go/apps/metald/internal/observability/billing_metrics.go b/go/apps/metald/internal/observability/billing_metrics.go
deleted file mode 100644
index 7f2c44ab07..0000000000
--- a/go/apps/metald/internal/observability/billing_metrics.go
+++ /dev/null
@@ -1,135 +0,0 @@
-package observability
-
-import (
- "context"
- "log/slog"
- "time"
-
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/metric"
-)
-
-// BillingMetrics tracks billing-related operations
-type BillingMetrics struct {
- logger *slog.Logger
- meter metric.Meter
- highCardinalityEnabled bool
-
- // Billing batch metrics
- billingBatchesSent metric.Int64Counter
- billingBatchSendDuration metric.Float64Histogram
- heartbeatsSent metric.Int64Counter
-
- // Metrics collection
- metricsCollected metric.Int64Counter
- metricsCollectionDuration metric.Float64Histogram
- vmMetricsRequests metric.Int64Counter
-}
-
-// NewBillingMetrics creates new billing metrics
-func NewBillingMetrics(logger *slog.Logger, highCardinalityEnabled bool) (*BillingMetrics, error) {
- meter := otel.Meter("unkey.metald.billing")
-
- bm := &BillingMetrics{ //nolint:exhaustruct // Metric fields are initialized below with error handling
- logger: logger.With("component", "billing_metrics"),
- meter: meter,
- highCardinalityEnabled: highCardinalityEnabled,
- }
-
- var err error
-
- // Billing batch metrics
- if bm.billingBatchesSent, err = meter.Int64Counter(
- "metald_billing_batches_sent_total",
- metric.WithDescription("Total number of billing batches sent"),
- ); err != nil {
- return nil, err
- }
-
- if bm.billingBatchSendDuration, err = meter.Float64Histogram(
- "metald_billing_batch_send_duration_seconds",
- metric.WithDescription("Duration of billing batch send operations"),
- metric.WithUnit("s"),
- ); err != nil {
- return nil, err
- }
-
- if bm.heartbeatsSent, err = meter.Int64Counter(
- "metald_heartbeat_sent_total",
- metric.WithDescription("Total number of heartbeats sent to billing service"),
- ); err != nil {
- return nil, err
- }
-
- // Metrics collection
- if bm.metricsCollected, err = meter.Int64Counter(
- "metald_metrics_collected_total",
- metric.WithDescription("Total number of VM metrics collected"),
- ); err != nil {
- return nil, err
- }
-
- if bm.metricsCollectionDuration, err = meter.Float64Histogram(
- "metald_metrics_collection_duration_seconds",
- metric.WithDescription("Duration of metrics collection operations"),
- metric.WithUnit("s"),
- ); err != nil {
- return nil, err
- }
-
- if bm.vmMetricsRequests, err = meter.Int64Counter(
- "metald_vm_metrics_requests_total",
- metric.WithDescription("Total number of VM metrics requests"),
- ); err != nil {
- return nil, err
- }
-
- logger.Info("billing metrics initialized")
- return bm, nil
-}
-
-// RecordBillingBatchSent records a billing batch being sent
-func (bm *BillingMetrics) RecordBillingBatchSent(ctx context.Context, vmID, customerID string, batchSize int, duration time.Duration) {
- var attrs []attribute.KeyValue
- if bm.highCardinalityEnabled {
- attrs = []attribute.KeyValue{
- attribute.String("vm_id", vmID),
- attribute.String("customer_id", customerID),
- }
- }
-
- bm.billingBatchesSent.Add(ctx, 1, metric.WithAttributes(attrs...))
- bm.billingBatchSendDuration.Record(ctx, duration.Seconds(), metric.WithAttributes(attrs...))
-}
-
-// RecordHeartbeatSent records a heartbeat being sent
-func (bm *BillingMetrics) RecordHeartbeatSent(ctx context.Context, instanceID string) {
- bm.heartbeatsSent.Add(ctx, 1, metric.WithAttributes(
- attribute.String("instance_id", instanceID),
- ))
-}
-
-// RecordMetricsCollected records VM metrics being collected
-func (bm *BillingMetrics) RecordMetricsCollected(ctx context.Context, vmID string, metricsCount int, duration time.Duration) {
- var attrs []attribute.KeyValue
- if bm.highCardinalityEnabled {
- attrs = []attribute.KeyValue{
- attribute.String("vm_id", vmID),
- }
- }
-
- bm.metricsCollected.Add(ctx, int64(metricsCount), metric.WithAttributes(attrs...))
- bm.metricsCollectionDuration.Record(ctx, duration.Seconds(), metric.WithAttributes(attrs...))
-}
-
-// RecordVMMetricsRequest records a VM metrics request
-func (bm *BillingMetrics) RecordVMMetricsRequest(ctx context.Context, vmID string) {
- var attrs []attribute.KeyValue
- if bm.highCardinalityEnabled {
- attrs = []attribute.KeyValue{
- attribute.String("vm_id", vmID),
- }
- }
- bm.vmMetricsRequests.Add(ctx, 1, metric.WithAttributes(attrs...))
-}
diff --git a/go/apps/metald/internal/observability/debug_interceptor.go b/go/apps/metald/internal/observability/debug_interceptor.go
deleted file mode 100644
index 37ad19fafd..0000000000
--- a/go/apps/metald/internal/observability/debug_interceptor.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package observability
-
-import (
- "context"
- "errors"
- "fmt"
- "log/slog"
- "strings"
- "time"
-
- "connectrpc.com/connect"
-)
-
-// DebugInterceptor provides detailed debug logging for ConnectRPC calls
-// AIDEV-NOTE: This interceptor logs detailed connection error information
-// to help diagnose inter-service communication issues
-func DebugInterceptor(logger *slog.Logger, serviceName string) connect.UnaryInterceptorFunc {
- return func(next connect.UnaryFunc) connect.UnaryFunc {
- return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
- start := time.Now()
- procedure := req.Spec().Procedure
-
- // Log request initiation at debug level
- logger.LogAttrs(ctx, slog.LevelDebug, fmt.Sprintf("%s rpc request initiated", serviceName),
- slog.String("procedure", procedure),
- slog.String("protocol", req.Spec().StreamType.String()),
- )
-
- // Execute the request
- resp, err := next(ctx, req)
- duration := time.Since(start)
-
- if err != nil { //nolint:nestif // Complex error logging logic requires nested conditions for different error types and connection troubleshooting
- // AIDEV-BUSINESS_RULE: Enhanced error logging for connection issues
- var connectErr *connect.Error
- if errors.As(err, &connectErr) {
- // Connection error - log with full details
- attrs := []slog.Attr{
- slog.String("service", serviceName),
- slog.String("procedure", procedure),
- slog.Duration("duration", duration),
- slog.String("error", err.Error()),
- slog.String("code", connectErr.Code().String()),
- slog.String("message", connectErr.Message()),
- }
-
- // Add additional context for specific error codes
- switch connectErr.Code() {
- case connect.CodeUnavailable:
- attrs = append(attrs, slog.String("likely_cause", "service unreachable or down"))
- case connect.CodeDeadlineExceeded:
- attrs = append(attrs, slog.String("likely_cause", "request timeout - service may be overloaded"))
- case connect.CodePermissionDenied:
- attrs = append(attrs, slog.String("likely_cause", "authentication/authorization failure"))
- case connect.CodeUnauthenticated:
- attrs = append(attrs, slog.String("likely_cause", "missing or invalid credentials"))
- case connect.CodeCanceled:
- attrs = append(attrs, slog.String("likely_cause", "request was cancelled"))
- case connect.CodeUnknown:
- attrs = append(attrs, slog.String("likely_cause", "unknown server error"))
- case connect.CodeInvalidArgument:
- attrs = append(attrs, slog.String("likely_cause", "invalid request parameters"))
- case connect.CodeNotFound:
- attrs = append(attrs, slog.String("likely_cause", "requested resource not found"))
- case connect.CodeAlreadyExists:
- attrs = append(attrs, slog.String("likely_cause", "resource already exists"))
- case connect.CodeResourceExhausted:
- attrs = append(attrs, slog.String("likely_cause", "service resource limits exceeded"))
- case connect.CodeFailedPrecondition:
- attrs = append(attrs, slog.String("likely_cause", "operation precondition not met"))
- case connect.CodeAborted:
- attrs = append(attrs, slog.String("likely_cause", "operation was aborted"))
- case connect.CodeOutOfRange:
- attrs = append(attrs, slog.String("likely_cause", "operation out of valid range"))
- case connect.CodeUnimplemented:
- attrs = append(attrs, slog.String("likely_cause", "operation not implemented"))
- case connect.CodeInternal:
- attrs = append(attrs, slog.String("likely_cause", "internal server error"))
- case connect.CodeDataLoss:
- attrs = append(attrs, slog.String("likely_cause", "unrecoverable data loss"))
- }
-
- // Check if this is a connection refused error
- if strings.Contains(err.Error(), "connection refused") {
- attrs = append(attrs, slog.String("connection_status", "refused"))
- attrs = append(attrs, slog.String("troubleshooting", "check if target service is running and listening on the correct port"))
- }
-
- // Check for DNS resolution errors
- if strings.Contains(err.Error(), "no such host") {
- attrs = append(attrs, slog.String("connection_status", "dns_failure"))
- attrs = append(attrs, slog.String("troubleshooting", "check service endpoint configuration and DNS resolution"))
- }
-
- // Check for TLS errors
- if strings.Contains(err.Error(), "tls:") || strings.Contains(err.Error(), "x509:") {
- attrs = append(attrs, slog.String("connection_status", "tls_failure"))
- attrs = append(attrs, slog.String("troubleshooting", "check TLS certificates and configuration"))
- }
-
- logger.LogAttrs(ctx, slog.LevelError, fmt.Sprintf("%s connection error", serviceName), attrs...)
- } else {
- // Non-connection error
- logger.LogAttrs(ctx, slog.LevelError, fmt.Sprintf("%s rpc error", serviceName),
- slog.String("procedure", procedure),
- slog.Duration("duration", duration),
- slog.String("error", err.Error()),
- slog.String("error_type", fmt.Sprintf("%T", err)),
- )
- }
- } else {
- // Success - log at debug level
- logger.LogAttrs(ctx, slog.LevelDebug, fmt.Sprintf("%s rpc success", serviceName),
- slog.String("procedure", procedure),
- slog.Duration("duration", duration),
- )
- }
-
- return resp, err
- }
- }
-}
diff --git a/go/apps/metald/internal/observability/metrics.go b/go/apps/metald/internal/observability/metrics.go
deleted file mode 100644
index 4361c4b8e7..0000000000
--- a/go/apps/metald/internal/observability/metrics.go
+++ /dev/null
@@ -1,627 +0,0 @@
-package observability
-
-import (
- "context"
- "log/slog"
- "time"
-
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/metric"
-)
-
-// VMMetrics tracks VM-related operations using OpenTelemetry counters
-type VMMetrics struct {
- logger *slog.Logger
- meter metric.Meter
- highCardinalityEnabled bool
-
- // VM lifecycle counters
- vmCreateRequests metric.Int64Counter
- vmCreateSuccess metric.Int64Counter
- vmCreateFailures metric.Int64Counter
- vmBootRequests metric.Int64Counter
- vmBootSuccess metric.Int64Counter
- vmBootFailures metric.Int64Counter
- vmShutdownRequests metric.Int64Counter
- vmShutdownSuccess metric.Int64Counter
- vmShutdownFailures metric.Int64Counter
- vmDeleteRequests metric.Int64Counter
- vmDeleteSuccess metric.Int64Counter
- vmDeleteFailures metric.Int64Counter
-
- // VM state operation counters
- vmPauseRequests metric.Int64Counter
- vmPauseSuccess metric.Int64Counter
- vmPauseFailures metric.Int64Counter
- vmResumeRequests metric.Int64Counter
- vmResumeSuccess metric.Int64Counter
- vmResumeFailures metric.Int64Counter
- vmRebootRequests metric.Int64Counter
- vmRebootSuccess metric.Int64Counter
- vmRebootFailures metric.Int64Counter
-
- // VM information counters
- vmInfoRequests metric.Int64Counter
- vmListRequests metric.Int64Counter
- vmMetricsRequests metric.Int64Counter
-
- // Process management counters
- processCreateRequests metric.Int64Counter
- processCreateSuccess metric.Int64Counter
- processCreateFailures metric.Int64Counter
- processTerminations metric.Int64Counter
- processCleanups metric.Int64Counter
-
- // Jailer-specific counters
- jailerStartRequests metric.Int64Counter
- jailerStartSuccess metric.Int64Counter
- jailerStartFailures metric.Int64Counter
-
- // Duration histograms for operation timing
- vmCreateDuration metric.Float64Histogram
- vmBootDuration metric.Float64Histogram
- vmShutdownDuration metric.Float64Histogram
- vmDeleteDuration metric.Float64Histogram
-}
-
-// NewVMMetrics creates a new VM metrics instance
-func NewVMMetrics(logger *slog.Logger, highCardinalityEnabled bool) (*VMMetrics, error) {
- meter := otel.Meter("unkey.metald.vm.operations")
-
- vm := &VMMetrics{ //nolint:exhaustruct // Metric fields are initialized below with error handling
- logger: logger.With("component", "vm_metrics"),
- meter: meter,
- highCardinalityEnabled: highCardinalityEnabled,
- }
-
- var err error
-
- // VM lifecycle counters
- if vm.vmCreateRequests, err = meter.Int64Counter(
- "unkey_metald_vm_create_requests_total",
- metric.WithDescription("Total number of VM create requests"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmCreateSuccess, err = meter.Int64Counter(
- "unkey_metald_vm_create_success_total",
- metric.WithDescription("Total number of successful VM creates"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmCreateFailures, err = meter.Int64Counter(
- "unkey_metald_vm_create_failures_total",
- metric.WithDescription("Total number of failed VM creates"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmBootRequests, err = meter.Int64Counter(
- "unkey_metald_vm_boot_requests_total",
- metric.WithDescription("Total number of VM boot requests"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmBootSuccess, err = meter.Int64Counter(
- "unkey_metald_vm_boot_success_total",
- metric.WithDescription("Total number of successful VM boots"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmBootFailures, err = meter.Int64Counter(
- "unkey_metald_vm_boot_failures_total",
- metric.WithDescription("Total number of failed VM boots"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmShutdownRequests, err = meter.Int64Counter(
- "unkey_metald_vm_shutdown_requests_total",
- metric.WithDescription("Total number of VM shutdown requests"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmShutdownSuccess, err = meter.Int64Counter(
- "unkey_metald_vm_shutdown_success_total",
- metric.WithDescription("Total number of successful VM shutdowns"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmShutdownFailures, err = meter.Int64Counter(
- "unkey_metald_vm_shutdown_failures_total",
- metric.WithDescription("Total number of failed VM shutdowns"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmDeleteRequests, err = meter.Int64Counter(
- "unkey_metald_vm_delete_requests_total",
- metric.WithDescription("Total number of VM delete requests"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmDeleteSuccess, err = meter.Int64Counter(
- "unkey_metald_vm_delete_success_total",
- metric.WithDescription("Total number of successful VM deletes"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmDeleteFailures, err = meter.Int64Counter(
- "unkey_metald_vm_delete_failures_total",
- metric.WithDescription("Total number of failed VM deletes"),
- ); err != nil {
- return nil, err
- }
-
- // VM state operation counters
- if vm.vmPauseRequests, err = meter.Int64Counter(
- "unkey_metald_vm_pause_requests_total",
- metric.WithDescription("Total number of VM pause requests"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmPauseSuccess, err = meter.Int64Counter(
- "unkey_metald_vm_pause_success_total",
- metric.WithDescription("Total number of successful VM pauses"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmPauseFailures, err = meter.Int64Counter(
- "unkey_metald_vm_pause_failures_total",
- metric.WithDescription("Total number of failed VM pauses"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmResumeRequests, err = meter.Int64Counter(
- "unkey_metald_vm_resume_requests_total",
- metric.WithDescription("Total number of VM resume requests"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmResumeSuccess, err = meter.Int64Counter(
- "unkey_metald_vm_resume_success_total",
- metric.WithDescription("Total number of successful VM resumes"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmResumeFailures, err = meter.Int64Counter(
- "unkey_metald_vm_resume_failures_total",
- metric.WithDescription("Total number of failed VM resumes"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmRebootRequests, err = meter.Int64Counter(
- "unkey_metald_vm_reboot_requests_total",
- metric.WithDescription("Total number of VM reboot requests"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmRebootSuccess, err = meter.Int64Counter(
- "unkey_metald_vm_reboot_success_total",
- metric.WithDescription("Total number of successful VM reboots"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmRebootFailures, err = meter.Int64Counter(
- "unkey_metald_vm_reboot_failures_total",
- metric.WithDescription("Total number of failed VM reboots"),
- ); err != nil {
- return nil, err
- }
-
- // VM information counters
- if vm.vmInfoRequests, err = meter.Int64Counter(
- "unkey_metald_vm_info_requests_total",
- metric.WithDescription("Total number of VM info requests"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmListRequests, err = meter.Int64Counter(
- "unkey_metald_vm_list_requests_total",
- metric.WithDescription("Total number of VM list requests"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmMetricsRequests, err = meter.Int64Counter(
- "unkey_metald_vm_metrics_requests_total",
- metric.WithDescription("Total number of VM metrics requests"),
- ); err != nil {
- return nil, err
- }
-
- // Process management counters
- if vm.processCreateRequests, err = meter.Int64Counter(
- "unkey_metald_process_create_requests_total",
- metric.WithDescription("Total number of process create requests"),
- ); err != nil {
- return nil, err
- }
-
- if vm.processCreateSuccess, err = meter.Int64Counter(
- "unkey_metald_process_create_success_total",
- metric.WithDescription("Total number of successful process creates"),
- ); err != nil {
- return nil, err
- }
-
- if vm.processCreateFailures, err = meter.Int64Counter(
- "unkey_metald_process_create_failures_total",
- metric.WithDescription("Total number of failed process creates"),
- ); err != nil {
- return nil, err
- }
-
- if vm.processTerminations, err = meter.Int64Counter(
- "unkey_metald_process_terminations_total",
- metric.WithDescription("Total number of process terminations"),
- ); err != nil {
- return nil, err
- }
-
- if vm.processCleanups, err = meter.Int64Counter(
- "unkey_metald_process_cleanups_total",
- metric.WithDescription("Total number of process cleanups"),
- ); err != nil {
- return nil, err
- }
-
- // Jailer-specific counters
- if vm.jailerStartRequests, err = meter.Int64Counter(
- "unkey_metald_jailer_start_requests_total",
- metric.WithDescription("Total number of jailer start requests"),
- ); err != nil {
- return nil, err
- }
-
- if vm.jailerStartSuccess, err = meter.Int64Counter(
- "unkey_metald_jailer_start_success_total",
- metric.WithDescription("Total number of successful jailer starts"),
- ); err != nil {
- return nil, err
- }
-
- if vm.jailerStartFailures, err = meter.Int64Counter(
- "unkey_metald_jailer_start_failures_total",
- metric.WithDescription("Total number of failed jailer starts"),
- ); err != nil {
- return nil, err
- }
-
- // Duration histograms
- if vm.vmCreateDuration, err = meter.Float64Histogram(
- "unkey_metald_vm_create_duration_seconds",
- metric.WithDescription("VM create operation duration"),
- metric.WithUnit("s"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmBootDuration, err = meter.Float64Histogram(
- "unkey_metald_vm_boot_duration_seconds",
- metric.WithDescription("VM boot operation duration"),
- metric.WithUnit("s"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmShutdownDuration, err = meter.Float64Histogram(
- "unkey_metald_vm_shutdown_duration_seconds",
- metric.WithDescription("VM shutdown operation duration"),
- metric.WithUnit("s"),
- ); err != nil {
- return nil, err
- }
-
- if vm.vmDeleteDuration, err = meter.Float64Histogram(
- "unkey_metald_vm_delete_duration_seconds",
- metric.WithDescription("VM delete operation duration"),
- metric.WithUnit("s"),
- ); err != nil {
- return nil, err
- }
-
- vm.logger.Info("VM metrics initialized")
- return vm, nil
-}
-
-// VM lifecycle metric methods
-func (vm *VMMetrics) RecordVMCreateRequest(ctx context.Context, backend string) {
- vm.vmCreateRequests.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- ))
-}
-
-func (vm *VMMetrics) RecordVMCreateSuccess(ctx context.Context, vmID string, backend string, duration time.Duration) {
- // Success counters don't include VM ID to avoid high cardinality
- vm.vmCreateSuccess.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- ))
- vm.vmCreateDuration.Record(ctx, duration.Seconds(), metric.WithAttributes(
- attribute.String("backend", backend),
- ))
-}
-
-func (vm *VMMetrics) RecordVMCreateFailure(ctx context.Context, backend string, errorType string) {
- vm.vmCreateFailures.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- attribute.String("error_type", errorType),
- ))
-}
-
-func (vm *VMMetrics) RecordVMBootRequest(ctx context.Context, vmID string, backend string) {
- // Request counters don't include VM ID to avoid high cardinality
- vm.vmBootRequests.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- ))
-}
-
-func (vm *VMMetrics) RecordVMBootSuccess(ctx context.Context, vmID string, backend string, duration time.Duration) {
- // Success counters don't include VM ID to avoid high cardinality
- vm.vmBootSuccess.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- ))
- vm.vmBootDuration.Record(ctx, duration.Seconds(), metric.WithAttributes(
- attribute.String("backend", backend),
- ))
-}
-
-func (vm *VMMetrics) RecordVMBootFailure(ctx context.Context, vmID string, backend string, errorType string) {
- // Failure metrics only include backend and error type, not VM ID
- vm.vmBootFailures.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- attribute.String("error_type", errorType),
- ))
-}
-
-func (vm *VMMetrics) RecordVMShutdownRequest(ctx context.Context, vmID string, backend string, force bool) {
- // Request counters don't include VM ID to avoid high cardinality
- vm.vmShutdownRequests.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- attribute.Bool("force", force),
- ))
-}
-
-func (vm *VMMetrics) RecordVMShutdownSuccess(ctx context.Context, vmID string, backend string, force bool, duration time.Duration) {
- // Success counters don't include VM ID to avoid high cardinality
- vm.vmShutdownSuccess.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- attribute.Bool("force", force),
- ))
- vm.vmShutdownDuration.Record(ctx, duration.Seconds(), metric.WithAttributes(
- attribute.String("backend", backend),
- attribute.Bool("force", force),
- ))
-}
-
-func (vm *VMMetrics) RecordVMShutdownFailure(ctx context.Context, vmID string, backend string, force bool, errorType string) {
- // Failure metrics only include backend, force flag, and error type
- vm.vmShutdownFailures.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- attribute.Bool("force", force),
- attribute.String("error_type", errorType),
- ))
-}
-
-func (vm *VMMetrics) RecordVMDeleteRequest(ctx context.Context, vmID string, backend string) {
- // Request counters don't include VM ID to avoid high cardinality
- vm.vmDeleteRequests.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- ))
-}
-
-func (vm *VMMetrics) RecordVMDeleteSuccess(ctx context.Context, vmID string, backend string, duration time.Duration) {
- // Success counters don't include VM ID to avoid high cardinality
- vm.vmDeleteSuccess.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- ))
- vm.vmDeleteDuration.Record(ctx, duration.Seconds(), metric.WithAttributes(
- attribute.String("backend", backend),
- ))
-}
-
-func (vm *VMMetrics) RecordVMDeleteFailure(ctx context.Context, vmID string, backend string, errorType string) {
- // Failure metrics only include backend and error type
- vm.vmDeleteFailures.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- attribute.String("error_type", errorType),
- ))
-}
-
-// VM state operation metric methods
-func (vm *VMMetrics) RecordVMPauseRequest(ctx context.Context, vmID string, backend string) {
- // Request counters don't include VM ID to avoid high cardinality
- vm.vmPauseRequests.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- ))
-}
-
-func (vm *VMMetrics) RecordVMPauseSuccess(ctx context.Context, vmID string, backend string) {
- // Success counters don't include VM ID to avoid high cardinality
- vm.vmPauseSuccess.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- ))
-}
-
-func (vm *VMMetrics) RecordVMPauseFailure(ctx context.Context, vmID string, backend string, errorType string) {
- // Failure metrics only include backend and error type
- vm.vmPauseFailures.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- attribute.String("error_type", errorType),
- ))
-}
-
-func (vm *VMMetrics) RecordVMResumeRequest(ctx context.Context, vmID string, backend string) {
- // Request counters don't include VM ID to avoid high cardinality
- vm.vmResumeRequests.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- ))
-}
-
-func (vm *VMMetrics) RecordVMResumeSuccess(ctx context.Context, vmID string, backend string) {
- // Success counters don't include VM ID to avoid high cardinality
- vm.vmResumeSuccess.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- ))
-}
-
-func (vm *VMMetrics) RecordVMResumeFailure(ctx context.Context, vmID string, backend string, errorType string) {
- // Failure metrics only include backend and error type
- vm.vmResumeFailures.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- attribute.String("error_type", errorType),
- ))
-}
-
-func (vm *VMMetrics) RecordVMRebootRequest(ctx context.Context, vmID string, backend string) {
- // Request counters don't include VM ID to avoid high cardinality
- vm.vmRebootRequests.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- ))
-}
-
-func (vm *VMMetrics) RecordVMRebootSuccess(ctx context.Context, vmID string, backend string) {
- // Success counters don't include VM ID to avoid high cardinality
- vm.vmRebootSuccess.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- ))
-}
-
-func (vm *VMMetrics) RecordVMRebootFailure(ctx context.Context, vmID string, backend string, errorType string) {
- // Failure metrics only include backend and error type
- vm.vmRebootFailures.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- attribute.String("error_type", errorType),
- ))
-}
-
-// VM information metric methods
-func (vm *VMMetrics) RecordVMInfoRequest(ctx context.Context, vmID string, backend string) {
- // Info request counters don't include VM ID to avoid high cardinality
- vm.vmInfoRequests.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- ))
-}
-
-func (vm *VMMetrics) RecordVMListRequest(ctx context.Context, backend string) {
- vm.vmListRequests.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- ))
-}
-
-func (vm *VMMetrics) RecordVMMetricsRequest(ctx context.Context, vmID string, backend string) {
- // Metrics request counters don't include VM ID to avoid high cardinality
- vm.vmMetricsRequests.Add(ctx, 1, metric.WithAttributes(
- attribute.String("backend", backend),
- ))
-}
-
-// Process management metric methods
-func (vm *VMMetrics) RecordProcessCreateRequest(ctx context.Context, vmID string, useJailer bool) {
- var attrs []attribute.KeyValue
- if vm.highCardinalityEnabled {
- attrs = append(attrs, attribute.String("vm_id", vmID))
- }
- attrs = append(attrs, attribute.Bool("use_jailer", useJailer))
- vm.processCreateRequests.Add(ctx, 1, metric.WithAttributes(attrs...))
-}
-
-func (vm *VMMetrics) RecordProcessCreateSuccess(ctx context.Context, vmID string, processID string, useJailer bool) {
- var attrs []attribute.KeyValue
- if vm.highCardinalityEnabled {
- attrs = append(attrs,
- attribute.String("vm_id", vmID),
- attribute.String("process_id", processID),
- )
- }
- attrs = append(attrs, attribute.Bool("use_jailer", useJailer))
- vm.processCreateSuccess.Add(ctx, 1, metric.WithAttributes(attrs...))
-}
-
-func (vm *VMMetrics) RecordProcessCreateFailure(ctx context.Context, vmID string, useJailer bool, errorType string) {
- var attrs []attribute.KeyValue
- if vm.highCardinalityEnabled {
- attrs = append(attrs, attribute.String("vm_id", vmID))
- }
- attrs = append(attrs,
- attribute.Bool("use_jailer", useJailer),
- attribute.String("error_type", errorType),
- )
- vm.processCreateFailures.Add(ctx, 1, metric.WithAttributes(attrs...))
-}
-
-func (vm *VMMetrics) RecordProcessTermination(ctx context.Context, vmID string, processID string, exitCode int) {
- var attrs []attribute.KeyValue
- if vm.highCardinalityEnabled {
- attrs = append(attrs,
- attribute.String("vm_id", vmID),
- attribute.String("process_id", processID),
- )
- }
- attrs = append(attrs, attribute.Int("exit_code", exitCode))
- vm.processTerminations.Add(ctx, 1, metric.WithAttributes(attrs...))
-}
-
-func (vm *VMMetrics) RecordProcessCleanup(ctx context.Context, vmID string, processID string) {
- var attrs []attribute.KeyValue
- if vm.highCardinalityEnabled {
- attrs = append(attrs,
- attribute.String("vm_id", vmID),
- attribute.String("process_id", processID),
- )
- }
- vm.processCleanups.Add(ctx, 1, metric.WithAttributes(attrs...))
-}
-
-// Jailer-specific metric methods
-func (vm *VMMetrics) RecordJailerStartRequest(ctx context.Context, vmID string, jailerID string) {
- var attrs []attribute.KeyValue
- if vm.highCardinalityEnabled {
- attrs = append(attrs,
- attribute.String("vm_id", vmID),
- attribute.String("jailer_id", jailerID),
- )
- }
- vm.jailerStartRequests.Add(ctx, 1, metric.WithAttributes(attrs...))
-}
-
-func (vm *VMMetrics) RecordJailerStartSuccess(ctx context.Context, vmID string, jailerID string) {
- var attrs []attribute.KeyValue
- if vm.highCardinalityEnabled {
- attrs = append(attrs,
- attribute.String("vm_id", vmID),
- attribute.String("jailer_id", jailerID),
- )
- }
- vm.jailerStartSuccess.Add(ctx, 1, metric.WithAttributes(attrs...))
-}
-
-func (vm *VMMetrics) RecordJailerStartFailure(ctx context.Context, vmID string, jailerID string, errorType string) {
- var attrs []attribute.KeyValue
- if vm.highCardinalityEnabled {
- attrs = append(attrs,
- attribute.String("vm_id", vmID),
- attribute.String("jailer_id", jailerID),
- )
- }
- attrs = append(attrs, attribute.String("error_type", errorType))
- vm.jailerStartFailures.Add(ctx, 1, metric.WithAttributes(attrs...))
-}
diff --git a/go/apps/metald/internal/observability/otel.go b/go/apps/metald/internal/observability/otel.go
deleted file mode 100644
index 6ee015ba5a..0000000000
--- a/go/apps/metald/internal/observability/otel.go
+++ /dev/null
@@ -1,225 +0,0 @@
-package observability
-
-import (
- "context"
- "errors"
- "fmt"
- "log/slog"
- "net/http"
- "time"
-
- "github.com/unkeyed/unkey/go/apps/metald/internal/config"
-
- "github.com/prometheus/client_golang/prometheus/promhttp"
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/codes"
- "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
- "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
- "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
- "go.opentelemetry.io/otel/exporters/prometheus"
- "go.opentelemetry.io/otel/metric"
- "go.opentelemetry.io/otel/propagation"
- sdkmetric "go.opentelemetry.io/otel/sdk/metric"
- "go.opentelemetry.io/otel/sdk/resource"
- sdktrace "go.opentelemetry.io/otel/sdk/trace"
- semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
- "go.opentelemetry.io/otel/trace"
- "go.opentelemetry.io/otel/trace/noop"
-)
-
-// Providers holds the OpenTelemetry providers
-type Providers struct {
- TracerProvider trace.TracerProvider
- MeterProvider metric.MeterProvider
- PrometheusHTTP http.Handler
- Shutdown func(context.Context) error
-}
-
-// InitProviders initializes OpenTelemetry providers
-func InitProviders(ctx context.Context, cfg *config.Config, version string, logger *slog.Logger) (*Providers, error) {
- if !cfg.OpenTelemetry.Enabled {
- // Return no-op providers
- return &Providers{
- TracerProvider: noop.NewTracerProvider(),
- MeterProvider: nil,
- PrometheusHTTP: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusNotFound)
- _, _ = w.Write([]byte("OpenTelemetry is disabled"))
- }),
- Shutdown: func(context.Context) error { return nil },
- }, nil
- }
-
- // AIDEV-NOTE: Schema conflict fix - Using semconv v1.24.0 with OTEL v1.36.0
- // and resource.New() without auto-detection resolves conflicting Schema URLs
- res, err := resource.New(ctx,
- resource.WithAttributes(
- semconv.ServiceNamespace("unkey"),
- semconv.ServiceName(cfg.OpenTelemetry.ServiceName),
- semconv.ServiceVersion(version),
- ),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create OTEL resource: %w", err)
- }
-
- // Initialize trace provider
- tracerProvider, tracerShutdown, err := initTracerProvider(ctx, cfg, res)
- if err != nil {
- return nil, fmt.Errorf("failed to initialize tracer provider: %w", err)
- }
-
- // Initialize meter provider
- meterProvider, promHandler, meterShutdown, err := initMeterProvider(ctx, cfg, res)
- if err != nil {
- if shutdownErr := tracerShutdown(ctx); shutdownErr != nil {
- logger.ErrorContext(ctx, "Failed to shutdown tracer", "error", shutdownErr)
- }
- return nil, fmt.Errorf("failed to initialize meter provider: %w", err)
- }
-
- // Set global providers
- otel.SetTracerProvider(tracerProvider)
- otel.SetMeterProvider(meterProvider)
- otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
- propagation.TraceContext{},
- propagation.Baggage{},
- ))
-
- // Combined shutdown function
- shutdown := func(ctx context.Context) error {
- var errs []error
-
- if err := tracerShutdown(ctx); err != nil {
- errs = append(errs, fmt.Errorf("tracer shutdown error: %w", err))
- }
-
- if err := meterShutdown(ctx); err != nil {
- errs = append(errs, fmt.Errorf("meter shutdown error: %w", err))
- }
-
- if len(errs) > 0 {
- return errors.Join(errs...)
- }
-
- return nil
- }
-
- return &Providers{
- TracerProvider: tracerProvider,
- MeterProvider: meterProvider,
- PrometheusHTTP: promHandler,
- Shutdown: shutdown,
- }, nil
-}
-
-// initTracerProvider initializes the tracer provider
-func initTracerProvider(ctx context.Context, cfg *config.Config, res *resource.Resource) (trace.TracerProvider, func(context.Context) error, error) {
- // Create OTLP trace exporter
- traceExporter, err := otlptrace.New(ctx,
- otlptracehttp.NewClient(
- otlptracehttp.WithEndpoint(cfg.OpenTelemetry.OTLPEndpoint),
- otlptracehttp.WithInsecure(), // For local development
- otlptracehttp.WithTimeout(30*time.Second),
- ),
- )
- if err != nil {
- return nil, nil, fmt.Errorf("failed to create trace exporter: %w", err)
- }
-
- // Create sampler with parent-based + ratio
- // Note: Error sampling is handled at the span level when RecordError is called
- ratioSampler := sdktrace.TraceIDRatioBased(cfg.OpenTelemetry.TracingSamplingRate)
- parentBasedSampler := sdktrace.ParentBased(ratioSampler)
-
- // Create tracer provider
- tp := sdktrace.NewTracerProvider(
- sdktrace.WithBatcher(traceExporter),
- sdktrace.WithResource(res),
- sdktrace.WithSampler(parentBasedSampler),
- )
-
- return tp, tp.Shutdown, nil
-}
-
-// initMeterProvider initializes the meter provider
-func initMeterProvider(ctx context.Context, cfg *config.Config, res *resource.Resource) (metric.MeterProvider, http.Handler, func(context.Context) error, error) {
- var readers []sdkmetric.Reader
-
- // OTLP metric exporter
- metricExporter, err := otlpmetrichttp.New(ctx,
- otlpmetrichttp.WithEndpoint(cfg.OpenTelemetry.OTLPEndpoint),
- otlpmetrichttp.WithInsecure(), // For local development
- otlpmetrichttp.WithTimeout(30*time.Second),
- )
- if err != nil {
- return nil, nil, nil, fmt.Errorf("failed to create metric exporter: %w", err)
- }
-
- readers = append(readers, sdkmetric.NewPeriodicReader(
- metricExporter,
- sdkmetric.WithInterval(15*time.Second),
- ))
-
- // Prometheus exporter
- var promHandler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusNotFound)
- _, _ = w.Write([]byte("Prometheus metrics disabled"))
- })
-
- if cfg.OpenTelemetry.PrometheusEnabled {
- promExporter, err := prometheus.New()
- if err != nil {
- return nil, nil, nil, fmt.Errorf("failed to create prometheus exporter: %w", err)
- }
- readers = append(readers, promExporter)
- promHandler = promhttp.Handler()
- }
-
- // Create meter provider with readers
- mpOpts := []sdkmetric.Option{
- sdkmetric.WithResource(res),
- }
- for _, reader := range readers {
- mpOpts = append(mpOpts, sdkmetric.WithReader(reader))
- }
- mp := sdkmetric.NewMeterProvider(mpOpts...)
-
- return mp, promHandler, mp.Shutdown, nil
-}
-
-// RecordError records an error in the current span and sets the status
-func RecordError(span trace.Span, err error) {
- if err != nil {
- span.RecordError(err)
- span.SetStatus(codes.Error, err.Error())
- }
-}
-
-// HTTPStatusCode returns the appropriate trace status code for an HTTP status
-func HTTPStatusCode(httpStatus int) codes.Code {
- if httpStatus >= 200 && httpStatus < 400 {
- return codes.Ok
- }
- return codes.Error
-}
-
-// SpanKindFromMethod returns the appropriate span kind for a method
-func SpanKindFromMethod(method string) trace.SpanKind {
- switch method {
- case "GET", "HEAD", "OPTIONS":
- return trace.SpanKindClient
- default:
- return trace.SpanKindInternal
- }
-}
-
-// ServiceAttributes returns common service attributes
-func ServiceAttributes(cfg *config.Config, version string) []attribute.KeyValue {
- return []attribute.KeyValue{
- semconv.ServiceName(cfg.OpenTelemetry.ServiceName),
- semconv.ServiceVersion(version),
- semconv.ServiceNamespace("unkey"),
- }
-}
diff --git a/go/apps/metald/internal/observability/sampler.go b/go/apps/metald/internal/observability/sampler.go
deleted file mode 100644
index 20d0e9da36..0000000000
--- a/go/apps/metald/internal/observability/sampler.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package observability
-
-import (
- sdktrace "go.opentelemetry.io/otel/sdk/trace"
-)
-
-// AlwaysOnErrorSampler samples all spans with errors, delegates other decisions to the base sampler
-type AlwaysOnErrorSampler struct {
- baseSampler sdktrace.Sampler
-}
-
-// NewAlwaysOnErrorSampler creates a new AlwaysOnErrorSampler
-func NewAlwaysOnErrorSampler(baseSampler sdktrace.Sampler) sdktrace.Sampler {
- return &AlwaysOnErrorSampler{
- baseSampler: baseSampler,
- }
-}
-
-// ShouldSample implements the Sampler interface
-func (s *AlwaysOnErrorSampler) ShouldSample(p sdktrace.SamplingParameters) sdktrace.SamplingResult {
- // Always use base sampler for initial decision
- result := s.baseSampler.ShouldSample(p)
-
- // The span will be set to error status later, but we can't know that at sampling time
- // So we need to use a SpanProcessor to handle error sampling
- return result
-}
-
-// Description returns the description of the sampler
-func (s *AlwaysOnErrorSampler) Description() string {
- return "AlwaysOnError{" + s.baseSampler.Description() + "}"
-}
-
-// ErrorSpanProcessor ensures spans with errors are always exported
-type ErrorSpanProcessor struct {
- sdktrace.SpanProcessor
-}
-
-// NewErrorSpanProcessor creates a new ErrorSpanProcessor
-func NewErrorSpanProcessor(wrapped sdktrace.SpanProcessor) sdktrace.SpanProcessor {
- return &ErrorSpanProcessor{
- SpanProcessor: wrapped,
- }
-}
-
-// OnEnd is called when a span ends
-func (p *ErrorSpanProcessor) OnEnd(s sdktrace.ReadOnlySpan) {
- // For error spans, we always call the wrapped processor's OnEnd
- // This ensures error spans are exported even with low sampling rates
- // No additional processing needed here - the sampling decision was already made
-
- // Always call the wrapped processor
- p.SpanProcessor.OnEnd(s)
-}
diff --git a/go/apps/metald/internal/service/auth.go b/go/apps/metald/internal/service/auth.go
deleted file mode 100644
index 6d43c3366c..0000000000
--- a/go/apps/metald/internal/service/auth.go
+++ /dev/null
@@ -1 +0,0 @@
-package service
diff --git a/go/apps/metald/internal/service/deployment.go b/go/apps/metald/internal/service/deployment.go
deleted file mode 100644
index cc86f71d60..0000000000
--- a/go/apps/metald/internal/service/deployment.go
+++ /dev/null
@@ -1,315 +0,0 @@
-package service
-
-import (
- "context"
- "database/sql"
- "encoding/json"
- "fmt"
- "log/slog"
- "net"
- "sync"
- "time"
-
- "connectrpc.com/connect"
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/types"
- "github.com/unkeyed/unkey/go/apps/metald/internal/database"
- "github.com/unkeyed/unkey/go/apps/metald/internal/network"
- metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1"
- "github.com/unkeyed/unkey/go/pkg/db"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/trace"
-)
-
-// In-memory tracking of deployments to VM IDs and IPs
-var (
- deploymentVMs = make(map[string][]string) // deployment_id -> vm_ids
- vmIPs = make(map[string]string) // vm_id -> ip_address
- deploymentMu sync.RWMutex
-)
-
-// CreateDeployment allocates a network, generates IDs etc
-func (s *VMService) CreateDeployment(ctx context.Context, req *connect.Request[metaldv1.CreateDeploymentRequest]) (*connect.Response[metaldv1.CreateDeploymentResponse], error) {
- ctx, span := s.tracer.Start(ctx, "metald.vm.create",
- trace.WithAttributes(
- attribute.String("service.name", "metald"),
- attribute.String("operation.name", "deployment.create"),
- ),
- )
- defer span.End()
-
- logger := s.logger.With("deployment_id", req.Msg.GetDeployment().GetDeploymentId())
- vmCount := req.Msg.GetDeployment().GetVmCount()
-
- // Cleanup old allocations for old pods (older than 2hrs)
- if s.backend.Type() == string(types.BackendTypeKubernetes) {
- staleTime := time.Now().Add(-(2*time.Hour + 5*time.Minute))
-
- if err := s.queries.CleanupStaleIPAllocations(ctx, sql.NullTime{Time: staleTime, Valid: true}); err != nil && !db.IsNotFound(err) {
- logger.Warn("failed to cleanup stale IP allocations",
- slog.String("error", err.Error()),
- )
- }
-
- if err := s.queries.ReleaseStaleNetworks(ctx, sql.NullTime{Time: staleTime, Valid: true}); err != nil && !db.IsNotFound(err) {
- logger.Warn("failed to release stale networks",
- slog.String("error", err.Error()),
- )
- }
-
- if err := s.queries.DeleteStaleNetworkAllocations(ctx, sql.NullTime{Time: staleTime, Valid: true}); err != nil && !db.IsNotFound(err) {
- logger.Warn("failed to delete stale network allocations",
- slog.String("error", err.Error()),
- )
- }
- }
-
- // We need to acquire a network allocation for the deployment and create one if it doesn't exist yet.
- nwAlloc, err := s.queries.GetNetworkAllocation(ctx, req.Msg.GetDeployment().DeploymentId)
- if err != nil && !db.IsNotFound(err) {
- logger.Info("failed to get network allocation",
- slog.String("error", err.Error()),
- )
-
- return nil, connect.NewError(connect.CodeInternal, err)
- }
-
- if db.IsNotFound(err) {
- n, netErr := s.queries.AllocateNetwork(ctx)
- if netErr != nil {
- logger.Info("failed to allocate network",
- slog.String("error", netErr.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, netErr)
- }
-
- _, bn, bnErr := net.ParseCIDR(n.BaseNetwork)
- if bnErr != nil {
- logger.Info("failed to parse network",
- slog.String("error", bnErr.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, netErr)
- }
-
- netConfig := network.Config{
- BaseNetwork: bn,
- BridgeName: network.GenerateID(),
- DNSServers: []string{"4.2.2.2", "4.2.2.1"},
- EnableIPv6: false,
- EnableRateLimit: false,
- RateLimitMbps: 0,
- }
-
- var br string
-
- if s.backend.Type() == string(types.BackendTypeFirecracker) {
- fBr, brErr := network.CreateBridge(logger, netConfig)
- if brErr != nil {
- logger.Info("failed to create bridge",
- slog.String("error", brErr.Error()),
- )
-
- if err := s.queries.ReleaseNetwork(ctx, n.ID); err != nil { // Delete entry from DB
- return nil, connect.NewError(connect.CodeInternal, err)
- }
-
- logger.Debug("cleaned up bridge")
-
- return nil, connect.NewError(connect.CodeInternal, brErr)
- }
-
- br = fBr
- } else {
- br = netConfig.BridgeName
- }
-
- // Generate available IPs for this network
- availableIPs, ipErr := network.GenerateAvailableIPs(n.BaseNetwork)
- if ipErr != nil {
- logger.ErrorContext(ctx, "failed to generate available IPs",
- slog.String("error", ipErr.Error()),
- )
-
- return nil, connect.NewError(connect.CodeInternal, ipErr)
- }
-
- na, naErr := s.queries.CreateNetworkAllocation(ctx, database.CreateNetworkAllocationParams{
- DeploymentID: req.Msg.Deployment.GetDeploymentId(),
- BridgeName: br,
- NetworkID: n.ID,
- AvailableIps: availableIPs,
- })
- if naErr != nil {
- logger.ErrorContext(ctx, "failed to save network allocation",
- slog.String("error", naErr.Error()),
- )
-
- return nil, connect.NewError(connect.CodeInternal, naErr)
- }
-
- logger.Debug("network allocated",
- slog.Any("network_cidr", netConfig.BaseNetwork.String()),
- slog.Any("network_id", na.ID),
- )
-
- logger.Debug("bridge allocated",
- slog.Any("network_cidr", netConfig.BaseNetwork.String()),
- slog.String("bridge_id", br),
- )
-
- nwAlloc = database.GetNetworkAllocationRow{
- ID: na.ID,
- DeploymentID: na.DeploymentID,
- NetworkID: na.NetworkID,
- BridgeName: na.BridgeName,
- AvailableIps: na.AvailableIps,
- AllocatedAt: na.AllocatedAt,
- BaseNetwork: n.BaseNetwork,
- }
- }
-
- logger.DebugContext(ctx, "creating vms", slog.Int64("count", int64(vmCount)))
- vmIds := []string{}
- for vm := range vmCount {
- vmID := fmt.Sprintf("ud-%s", s.generateVmID(ctx))
-
- // Pop an available IP from the pool
- ipRow, popErr := s.queries.PopAvailableIPJSON(ctx, req.Msg.Deployment.GetDeploymentId())
- if popErr != nil {
- logger.ErrorContext(ctx, "failed to pop available IP",
- slog.String("error", popErr.Error()),
- )
-
- return nil, connect.NewError(connect.CodeInternal, popErr)
- }
-
- // Now allocate the IP with all required fields
- ipAlloc, allocErr := s.queries.AllocateIP(ctx, database.AllocateIPParams{
- VmID: vmID,
- IpAddr: ipRow.Column1,
- NetworkAllocationID: ipRow.ID,
- })
- if allocErr != nil {
- logger.ErrorContext(ctx, "failed to pop available IP",
- slog.String("error", allocErr.Error()),
- )
-
- return nil, connect.NewError(connect.CodeInternal, allocErr)
- }
-
- // Pass network info to backend via NetworkConfig
- networkConfigJSON, _ := json.Marshal(map[string]string{
- "deployment_id": req.Msg.Deployment.GetDeploymentId(),
- "subnet": nwAlloc.BaseNetwork,
- "allocated_ip": ipRow.Column1,
- "bridge_name": nwAlloc.BridgeName,
- })
-
- // This returns vmID and err
- _, err := s.backend.CreateVM(ctx, &metaldv1.VmConfig{
- VcpuCount: req.Msg.Deployment.GetCpu(),
- MemorySizeMib: req.Msg.Deployment.GetMemorySizeMib(),
- Boot: req.Msg.Deployment.GetImage(),
- NetworkConfig: string(networkConfigJSON),
- Console: nil,
- Storage: nil,
- Id: vmID,
- Metadata: nil,
- })
- if err != nil {
- logger.ErrorContext(ctx, "failed to create VM",
- slog.String("error", err.Error()),
- )
-
- return nil, connect.NewError(connect.CodeInternal, err)
- }
-
- logger.Debug("created vm",
- slog.String("id", ipAlloc.VmID),
- slog.String("ip", ipAlloc.IpAddr),
- slog.Any("requested", vmCount),
- slog.Any("fulfilled", vm),
- )
-
- // Store VM IP for tracking
- deploymentMu.Lock()
- vmIPs[vmID] = ipAlloc.IpAddr
- deploymentMu.Unlock()
-
- // For now boot the VM directly once it's been created
- err = s.backend.BootVM(ctx, vmID)
- if err != nil {
- logger.ErrorContext(ctx, "failed to boot VM",
- slog.String("error", err.Error()),
- )
-
- return nil, connect.NewError(connect.CodeInternal, err)
- }
-
- vmIds = append(vmIds, vmID)
- }
-
- // Track all VM IDs for this deployment
- deploymentMu.Lock()
- deploymentVMs[req.Msg.GetDeployment().DeploymentId] = vmIds
- deploymentMu.Unlock()
-
- return connect.NewResponse(&metaldv1.CreateDeploymentResponse{
- VmIds: vmIds,
- }), nil
-}
-
-// GetDeployment returns all of the VMs and their state for the passed deployment_id
-func (s *VMService) GetDeployment(ctx context.Context, req *connect.Request[metaldv1.GetDeploymentRequest]) (*connect.Response[metaldv1.GetDeploymentResponse], error) {
- ctx, span := s.tracer.Start(ctx, "metald.deployment.get",
- trace.WithAttributes(
- attribute.String("service.name", "metald"),
- attribute.String("operation.name", "deployment.get"),
- ),
- )
- defer span.End()
-
- logger := s.logger.With("deployment_id", req.Msg.GetDeploymentId())
-
- // Get VM IDs from in-memory tracking, this should later be replaced via a DB read.
- deploymentMu.RLock()
- vmIDs := deploymentVMs[req.Msg.GetDeploymentId()]
- deploymentMu.RUnlock()
-
- if len(vmIDs) == 0 {
- return connect.NewResponse(&metaldv1.GetDeploymentResponse{
- DeploymentId: req.Msg.GetDeploymentId(),
- Vms: []*metaldv1.GetDeploymentResponse_Vm{},
- }), nil
- }
-
- // Get VM info from backend for each VM ID
- var vmResponse []*metaldv1.GetDeploymentResponse_Vm
- for _, vmID := range vmIDs {
- vmInfo, err := s.backend.GetVMInfo(ctx, vmID)
- if err != nil {
- logger.Warn("failed to get VM info", slog.String("vm_id", vmID), slog.String("error", err.Error()))
- continue // Skip missing VMs
- }
-
- // Get VM IP from in-memory tracking
- deploymentMu.RLock()
- vmIP := vmIPs[vmID]
- deploymentMu.RUnlock()
-
- if vmIP == "" {
- vmIP = "unknown" // Fallback if IP not found
- }
-
- vmResponse = append(vmResponse, &metaldv1.GetDeploymentResponse_Vm{
- Id: vmID,
- Host: vmIP,
- Port: 8080, // For now just force the port to 8080 as this is what k8s/docker uses
- State: vmInfo.State,
- })
- }
-
- return connect.NewResponse(&metaldv1.GetDeploymentResponse{
- DeploymentId: req.Msg.GetDeploymentId(),
- Vms: vmResponse,
- }), nil
-}
diff --git a/go/apps/metald/internal/service/vm.go b/go/apps/metald/internal/service/vm.go
deleted file mode 100644
index 551c1577e1..0000000000
--- a/go/apps/metald/internal/service/vm.go
+++ /dev/null
@@ -1,541 +0,0 @@
-package service
-
-import (
- "context"
- "crypto/rand"
- "encoding/base32"
- "encoding/json"
- "fmt"
- "log/slog"
- "time"
-
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/types"
- "github.com/unkeyed/unkey/go/apps/metald/internal/billing"
- "github.com/unkeyed/unkey/go/apps/metald/internal/database"
- "github.com/unkeyed/unkey/go/apps/metald/internal/observability"
- metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1"
- "github.com/unkeyed/unkey/go/gen/proto/metald/v1/metaldv1connect"
-
- "connectrpc.com/connect"
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/trace"
-)
-
-// VMService implements the VmServiceHandler interface
-type VMService struct {
- backend types.Backend
- logger *slog.Logger
- metricsCollector *billing.MetricsCollector
- vmMetrics *observability.VMMetrics
- tracer trace.Tracer
- queries database.Querier
- metaldv1connect.UnimplementedVmServiceHandler
-}
-
-// NewVMService creates a new VM service instance
-func NewVMService(backend types.Backend, logger *slog.Logger, metricsCollector *billing.MetricsCollector, vmMetrics *observability.VMMetrics, queries database.Querier) *VMService {
- tracer := otel.Tracer("metald.service.vm")
-
- return &VMService{ //nolint:exhaustruct
- backend: backend,
- logger: logger.With("service", "metald"),
- metricsCollector: metricsCollector,
- vmMetrics: vmMetrics,
- queries: queries,
- tracer: tracer,
- }
-}
-
-func (s *VMService) generateVmID(ctx context.Context) string {
- b := make([]byte, 15)
- rand.Read(b)
- return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(b)[:24]
-}
-
-// CreateVm creates a new VM instance
-func (s *VMService) CreateVm(ctx context.Context, req *connect.Request[metaldv1.CreateVmRequest]) (*connect.Response[metaldv1.CreateVmResponse], error) {
- ctx, span := s.tracer.Start(ctx, "metald.vm.create",
- trace.WithAttributes(
- attribute.String("service.name", "metald"),
- attribute.String("operation.name", "create_vm"),
- ),
- )
- defer span.End()
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "creating vm",
- slog.String("method", "CreateVm"),
- )
-
- // Record VM create request metric
- if s.vmMetrics != nil {
- s.vmMetrics.RecordVMCreateRequest(ctx, s.backend.Type())
- }
-
- config := req.Msg.GetConfig()
-
- // DEBUG: Log full request config for debugging
- if config != nil {
- configJSON, _ := json.Marshal(config)
- s.logger.LogAttrs(ctx, slog.LevelDebug, "full VM config received",
- slog.String("config_json", string(configJSON)),
- )
- }
- if config == nil {
- err := fmt.Errorf("vm config is required")
- span.RecordError(err)
- s.logger.LogAttrs(ctx, slog.LevelError, "missing vm config")
- if s.vmMetrics != nil {
- s.vmMetrics.RecordVMCreateFailure(ctx, s.backend.Type(), "missing_config")
- }
- return nil, connect.NewError(connect.CodeInvalidArgument, err)
- }
-
- network, netErr := s.queries.AllocateNetwork(ctx)
- if netErr != nil {
- s.logger.Info("failed to allocate network",
- slog.String("error", netErr.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, netErr)
- }
-
- s.logger.Info("network allocated",
- slog.Any("network_cidr", network.BaseNetwork),
- )
-
- ip, ipErr := s.queries.AllocateIP(ctx, database.AllocateIPParams{
- VmID: req.Msg.GetVmId(),
- })
- if ipErr != nil {
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to allocate IP for vm: %w", ipErr))
- }
-
- // Create VM using backend (config is already in unified format)
- start := time.Now()
- vmID, err := s.backend.CreateVM(ctx, config)
- duration := time.Since(start)
- if err != nil {
- span.RecordError(err)
- span.SetAttributes(
- attribute.String("error.type", "backend_error"),
- attribute.String("error.message", err.Error()),
- )
- if s.vmMetrics != nil {
- s.vmMetrics.RecordVMCreateFailure(ctx, s.backend.Type(), "backend_error")
- }
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to create vm: %w", err))
- }
-
- // Record success attributes
- span.SetAttributes(
- attribute.String("vm_id", vmID),
- attribute.Int64("duration_ms", duration.Milliseconds()),
- attribute.Bool("success", true),
- )
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "vm created successfully",
- slog.String("vm_id", vmID),
- slog.Duration("duration", duration),
- )
-
- // Record successful VM creation
- if s.vmMetrics != nil {
- s.vmMetrics.RecordVMCreateSuccess(ctx, vmID, s.backend.Type(), duration)
- }
-
- return connect.NewResponse(&metaldv1.CreateVmResponse{
- Endpoint: &metaldv1.Endpoint{
- Host: ip.IpAddr,
- Port: 35428,
- },
- State: metaldv1.VmState_VM_STATE_CREATED,
- }), nil
-}
-
-// DeleteVm deletes a VM instance
-func (s *VMService) DeleteVm(ctx context.Context, req *connect.Request[metaldv1.DeleteVmRequest]) (*connect.Response[metaldv1.DeleteVmResponse], error) {
- vmID := req.Msg.GetVmId()
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "deleting vm",
- slog.String("method", "DeleteVm"),
- slog.String("vm_id", vmID),
- )
-
- // Record VM delete request metric
- if s.vmMetrics != nil {
- s.vmMetrics.RecordVMDeleteRequest(ctx, vmID, s.backend.Type())
- }
-
- if vmID == "" {
- s.logger.LogAttrs(ctx, slog.LevelError, "missing vm id")
- if s.vmMetrics != nil {
- s.vmMetrics.RecordVMDeleteFailure(ctx, "", s.backend.Type(), "missing_vm_id")
- }
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("vm_id is required"))
- }
-
- // AIDEV-NOTE: Metrics collection re-enabled - metald now reads from Firecracker stats sockets
- // Stop metrics collection before deletion
- if s.metricsCollector != nil {
- s.metricsCollector.StopCollection(vmID)
- s.logger.LogAttrs(ctx, slog.LevelInfo, "stopped metrics collection",
- slog.String("vm_id", vmID),
- )
- }
-
- start := time.Now()
- err := s.backend.DeleteVM(ctx, vmID)
- duration := time.Since(start)
- if err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to delete vm",
- slog.String("vm_id", vmID),
- slog.String("error", err.Error()),
- )
- if s.vmMetrics != nil {
- s.vmMetrics.RecordVMDeleteFailure(ctx, vmID, s.backend.Type(), "backend_error")
- }
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to delete vm: %w", err))
- }
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "vm deleted successfully",
- slog.String("vm_id", vmID),
- slog.Duration("duration", duration),
- )
-
- // Record successful VM deletion
- if s.vmMetrics != nil {
- s.vmMetrics.RecordVMDeleteSuccess(ctx, vmID, s.backend.Type(), duration)
- }
-
- return connect.NewResponse(&metaldv1.DeleteVmResponse{
- Success: true,
- }), nil
-}
-
-// BootVm boots a VM instance
-func (s *VMService) BootVm(ctx context.Context, req *connect.Request[metaldv1.BootVmRequest]) (*connect.Response[metaldv1.BootVmResponse], error) {
- vmID := req.Msg.GetVmId()
-
- ctx, span := s.tracer.Start(ctx, "metald.vm.boot",
- trace.WithAttributes(
- attribute.String("service.name", "metald"),
- attribute.String("operation.name", "boot_vm"),
- attribute.String("vm_id", vmID),
- ),
- )
- defer span.End()
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "booting vm",
- slog.String("method", "BootVm"),
- slog.String("vm_id", vmID),
- )
-
- // Record VM boot request metric
- if s.vmMetrics != nil {
- s.vmMetrics.RecordVMBootRequest(ctx, vmID, s.backend.Type())
- }
-
- if vmID == "" {
- s.logger.LogAttrs(ctx, slog.LevelError, "missing vm id")
- if s.vmMetrics != nil {
- s.vmMetrics.RecordVMBootFailure(ctx, "", s.backend.Type(), "missing_vm_id")
- }
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("vm_id is required"))
- }
-
- start := time.Now()
- err := s.backend.BootVM(ctx, vmID)
- duration := time.Since(start)
- if err != nil {
- span.RecordError(err)
- span.SetAttributes(
- attribute.String("error.type", "backend_error"),
- attribute.String("error.message", err.Error()),
- )
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to boot vm",
- slog.String("vm_id", vmID),
- slog.String("error", err.Error()),
- )
- if s.vmMetrics != nil {
- s.vmMetrics.RecordVMBootFailure(ctx, vmID, s.backend.Type(), "backend_error")
- }
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to boot vm: %w", err))
- }
-
- // Record success attributes
- span.SetAttributes(
- attribute.String("vm_id", vmID),
- attribute.Int64("duration_ms", duration.Milliseconds()),
- attribute.Bool("success", true),
- )
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "vm booted successfully",
- slog.String("vm_id", vmID),
- slog.Duration("duration", duration),
- )
-
- // Record successful VM boot
- if s.vmMetrics != nil {
- s.vmMetrics.RecordVMBootSuccess(ctx, vmID, s.backend.Type(), duration)
- }
-
- return connect.NewResponse(&metaldv1.BootVmResponse{
- State: metaldv1.VmState_VM_STATE_RUNNING,
- }), nil
-}
-
-// ShutdownVm shuts down a VM instance
-func (s *VMService) ShutdownVm(ctx context.Context, req *connect.Request[metaldv1.ShutdownVmRequest]) (*connect.Response[metaldv1.ShutdownVmResponse], error) {
- vmID := req.Msg.GetVmId()
-
- force := req.Msg.GetForce()
- timeout := req.Msg.GetTimeoutSeconds()
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "shutting down vm",
- slog.String("method", "ShutdownVm"),
- slog.String("vm_id", vmID),
- slog.Bool("force", force),
- slog.Int("timeout_seconds", int(timeout)),
- )
-
- // Record VM shutdown request metric
- if s.vmMetrics != nil {
- s.vmMetrics.RecordVMShutdownRequest(ctx, vmID, s.backend.Type(), force)
- }
-
- if vmID == "" {
- s.logger.LogAttrs(ctx, slog.LevelError, "missing vm id")
- if s.vmMetrics != nil {
- s.vmMetrics.RecordVMShutdownFailure(ctx, "", s.backend.Type(), force, "missing_vm_id")
- }
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("vm_id is required"))
- }
-
- // AIDEV-NOTE: Metrics collection re-enabled - metald now reads from Firecracker stats sockets
- // Stop metrics collection before shutdown
- if s.metricsCollector != nil {
- s.metricsCollector.StopCollection(vmID)
- s.logger.LogAttrs(ctx, slog.LevelInfo, "stopped metrics collection",
- slog.String("vm_id", vmID),
- )
- }
-
- start := time.Now()
- err := s.backend.ShutdownVMWithOptions(ctx, vmID, force, timeout)
- duration := time.Since(start)
- if err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to shutdown vm",
- slog.String("vm_id", vmID),
- slog.String("error", err.Error()),
- )
- if s.vmMetrics != nil {
- s.vmMetrics.RecordVMShutdownFailure(ctx, vmID, s.backend.Type(), force, "backend_error")
- }
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to shutdown vm: %w", err))
- }
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "vm shutdown successfully",
- slog.String("vm_id", vmID),
- slog.Duration("duration", duration),
- )
-
- // Record successful VM shutdown
- if s.vmMetrics != nil {
- s.vmMetrics.RecordVMShutdownSuccess(ctx, vmID, s.backend.Type(), force, duration)
- }
-
- return connect.NewResponse(&metaldv1.ShutdownVmResponse{
- State: metaldv1.VmState_VM_STATE_SHUTDOWN,
- }), nil
-}
-
-// PauseVm pauses a VM instance
-func (s *VMService) PauseVm(ctx context.Context, req *connect.Request[metaldv1.PauseVmRequest]) (*connect.Response[metaldv1.PauseVmResponse], error) {
- vmID := req.Msg.GetVmId()
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "pausing vm",
- slog.String("method", "PauseVm"),
- slog.String("vm_id", vmID),
- )
-
- if vmID == "" {
- s.logger.LogAttrs(ctx, slog.LevelError, "missing vm id")
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("vm_id is required"))
- }
-
- if err := s.backend.PauseVM(ctx, vmID); err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to pause vm",
- slog.String("vm_id", vmID),
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to pause vm: %w", err))
- }
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "vm paused successfully",
- slog.String("vm_id", vmID),
- )
-
- return connect.NewResponse(&metaldv1.PauseVmResponse{
- State: metaldv1.VmState_VM_STATE_PAUSED,
- }), nil
-}
-
-// ResumeVm resumes a paused VM instance
-func (s *VMService) ResumeVm(ctx context.Context, req *connect.Request[metaldv1.ResumeVmRequest]) (*connect.Response[metaldv1.ResumeVmResponse], error) {
- vmID := req.Msg.GetVmId()
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "resuming vm",
- slog.String("method", "ResumeVm"),
- slog.String("vm_id", vmID),
- )
-
- if vmID == "" {
- s.logger.LogAttrs(ctx, slog.LevelError, "missing vm id")
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("vm_id is required"))
- }
-
- if err := s.backend.ResumeVM(ctx, vmID); err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to resume vm",
- slog.String("vm_id", vmID),
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to resume vm: %w", err))
- }
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "vm resumed successfully",
- slog.String("vm_id", vmID),
- )
-
- return connect.NewResponse(&metaldv1.ResumeVmResponse{
- State: metaldv1.VmState_VM_STATE_RUNNING,
- }), nil
-}
-
-// RebootVm reboots a VM instance
-func (s *VMService) RebootVm(ctx context.Context, req *connect.Request[metaldv1.RebootVmRequest]) (*connect.Response[metaldv1.RebootVmResponse], error) {
- vmID := req.Msg.GetVmId()
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "rebooting vm",
- slog.String("method", "RebootVm"),
- slog.String("vm_id", vmID),
- )
-
- if vmID == "" {
- s.logger.LogAttrs(ctx, slog.LevelError, "missing vm id")
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("vm_id is required"))
- }
-
- if err := s.backend.RebootVM(ctx, vmID); err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to reboot vm",
- slog.String("vm_id", vmID),
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to reboot vm: %w", err))
- }
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "vm rebooted successfully",
- slog.String("vm_id", vmID),
- )
-
- return connect.NewResponse(&metaldv1.RebootVmResponse{
- State: metaldv1.VmState_VM_STATE_RUNNING,
- }), nil
-}
-
-// GetVmInfo gets VM information
-func (s *VMService) GetVmInfo(ctx context.Context, req *connect.Request[metaldv1.GetVmInfoRequest]) (*connect.Response[metaldv1.GetVmInfoResponse], error) {
- vmID := req.Msg.GetVmId()
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "getting vm info",
- slog.String("method", "GetVmInfo"),
- slog.String("vm_id", vmID),
- )
-
- // Record VM info request metric
- if s.vmMetrics != nil {
- s.vmMetrics.RecordVMInfoRequest(ctx, vmID, s.backend.Type())
- }
-
- if vmID == "" {
- s.logger.LogAttrs(ctx, slog.LevelError, "missing vm id")
- return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("vm_id is required"))
- }
-
- info, err := s.backend.GetVMInfo(ctx, vmID)
- if err != nil {
- s.logger.LogAttrs(ctx, slog.LevelError, "failed to get vm info",
- slog.String("vm_id", vmID),
- slog.String("error", err.Error()),
- )
- return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to get vm info: %w", err))
- }
-
- s.logger.LogAttrs(ctx, slog.LevelInfo, "retrieved vm info successfully",
- slog.String("vm_id", vmID),
- slog.String("state", info.State.String()),
- )
-
- return connect.NewResponse(&metaldv1.GetVmInfoResponse{ //nolint:exhaustruct // Metrics and BackendInfo fields are optional and not populated in this response
- VmId: vmID,
- Config: info.Config,
- State: info.State,
- }), nil
-}
-
-// validateVMConfig validates the VM configuration
-// func (s *VMService) validateVMConfig(config *metaldv1.VmConfig) error {
-// if config.GetCpu() == nil {
-// return fmt.Errorf("cpu configuration is required")
-// }
-
-// if config.GetMemory() == nil {
-// return fmt.Errorf("memory configuration is required")
-// }
-
-// if config.GetBoot() == nil {
-// return fmt.Errorf("boot configuration is required")
-// }
-
-// // Validate CPU configuration
-// cpu := config.GetCpu()
-// if cpu.GetVcpuCount() <= 0 {
-// return fmt.Errorf("vcpu_count must be greater than 0")
-// }
-
-// if cpu.GetMaxVcpuCount() > 0 && cpu.GetMaxVcpuCount() < cpu.GetVcpuCount() {
-// return fmt.Errorf("max_vcpu_count must be greater than or equal to vcpu_count")
-// }
-
-// // Validate memory configuration
-// memory := config.GetMemory()
-// if memory.GetSizeBytes() <= 0 {
-// return fmt.Errorf("memory size_bytes must be greater than 0")
-// }
-
-// // Validate boot configuration
-// boot := config.GetBoot()
-// if boot.GetKernelPath() == "" {
-// return fmt.Errorf("kernel_path is required")
-// }
-
-// // Validate storage configuration - ensure at least one storage device exists
-// if len(config.GetStorage()) == 0 {
-// return fmt.Errorf("at least one storage device is required")
-// }
-
-// // Validate that we have a root device
-// hasRootDevice := false
-// for i, storage := range config.GetStorage() {
-// if storage.GetPath() == "" {
-// return fmt.Errorf("storage device %d path is required", i)
-// }
-// if storage.GetIsRootDevice() || i == 0 {
-// hasRootDevice = true
-// }
-// }
-// if !hasRootDevice {
-// return fmt.Errorf("at least one storage device must be marked as root device")
-// }
-
-// return nil
-// }
diff --git a/go/apps/metald/migrations/20250917000000_initial_schema.sql b/go/apps/metald/migrations/20250917000000_initial_schema.sql
deleted file mode 100644
index 3ecd3a972b..0000000000
--- a/go/apps/metald/migrations/20250917000000_initial_schema.sql
+++ /dev/null
@@ -1,44 +0,0 @@
--- +goose Up
--- Initial schema for metald network management
-CREATE TABLE networks (
- id INTEGER PRIMARY KEY,
- base_network TEXT UNIQUE NOT NULL, -- CIDR notation: "10.0.0.16/28"
- is_allocated INTEGER NOT NULL DEFAULT 0
-);
-
-CREATE TABLE network_allocations (
- id INTEGER PRIMARY KEY,
- deployment_id TEXT NOT NULL UNIQUE,
- network_id INTEGER NOT NULL,
- bridge_name TEXT NOT NULL UNIQUE,
- available_ips TEXT NOT NULL, -- JSON array: ["10.0.0.18","10.0.0.19",...]
- allocated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (network_id) REFERENCES networks(id)
-);
-
-CREATE TABLE ip_allocations (
- id INTEGER PRIMARY KEY,
- vm_id TEXT NOT NULL UNIQUE,
- ip_addr TEXT NOT NULL,
- network_allocation_id INTEGER NOT NULL,
- allocated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (network_allocation_id) REFERENCES network_allocations(id),
- UNIQUE(network_allocation_id, ip_addr)
-);
-
--- Indexes for performance
-CREATE INDEX idx_networks_unallocated
- ON networks(is_allocated, id)
- WHERE is_allocated = 0;
-
-CREATE INDEX idx_ip_allocations_network
- ON ip_allocations(network_allocation_id);
-
-CREATE INDEX idx_network_allocations_deployment
- ON network_allocations(deployment_id);
-
--- +goose Down
--- Drop all tables
-DROP TABLE IF EXISTS ip_allocations;
-DROP TABLE IF EXISTS network_allocations;
-DROP TABLE IF EXISTS networks;
diff --git a/go/apps/metald/migrations/20250917000100_seed_networks.go b/go/apps/metald/migrations/20250917000100_seed_networks.go
deleted file mode 100644
index 68c078586f..0000000000
--- a/go/apps/metald/migrations/20250917000100_seed_networks.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package migrations
-
-import (
- "database/sql"
- "encoding/binary"
- "fmt"
- "net"
- "os"
-
- "github.com/pressly/goose/v3"
-)
-
-func init() {
- goose.AddMigration(upSeedNetworks, downSeedNetworks)
-}
-
-func upSeedNetworks(tx *sql.Tx) error {
- // Get network CIDR from environment variable, default to 10.0.0.0/8
- rootCIDR := os.Getenv("UNKEY_METALD_NETWORK_CIDR")
- if rootCIDR == "" {
- rootCIDR = "10.0.0.0/8"
- }
-
- // Get subnet prefix from environment variable, default to /26
- subnetPrefix := 26
- if envPrefix := os.Getenv("UNKEY_METALD_SUBNET_PREFIX"); envPrefix != "" {
- var err error
- if _, err = fmt.Sscanf(envPrefix, "%d", &subnetPrefix); err != nil {
- return fmt.Errorf("invalid UNKEY_METALD_SUBNET_PREFIX %s: %w", envPrefix, err)
- }
- }
-
- fmt.Printf("Seeding networks from %s with /%d subnets\n", rootCIDR, subnetPrefix)
-
- _, rootNet, err := net.ParseCIDR(rootCIDR)
- if err != nil {
- return fmt.Errorf("failed to parse root CIDR %s: %w", rootCIDR, err)
- }
-
- ones, bits := rootNet.Mask.Size()
- if subnetPrefix <= ones {
- return fmt.Errorf("subnet /%d must be smaller than root network /%d", subnetPrefix, ones)
- }
-
- // Prepare batch insert statement
- stmt, err := tx.Prepare("INSERT INTO networks (base_network) VALUES (?)")
- if err != nil {
- return fmt.Errorf("failed to prepare insert statement: %w", err)
- }
- defer stmt.Close()
-
- // Generate all /26 subnets
- count := 0
- for ip := rootNet.IP.Mask(rootNet.Mask); rootNet.Contains(ip); {
- subnet := &net.IPNet{
- IP: ip,
- Mask: net.CIDRMask(subnetPrefix, bits),
- }
-
- _, err := stmt.Exec(subnet.String())
- if err != nil {
- return fmt.Errorf("failed to insert subnet %s: %w", subnet.String(), err)
- }
-
- count++
- if count%10000 == 0 {
- // Log progress for large batches
- fmt.Printf("Inserted %d networks...\n", count)
- }
-
- // Move to next subnet
- inc := 1 << (bits - subnetPrefix)
- ipInt := ipToInt(ip)
- ipInt += uint32(inc)
- ip = intToIP(ipInt)
- }
-
- fmt.Printf("Successfully seeded %d /26 networks from %s\n", count, rootCIDR)
- return nil
-}
-
-func downSeedNetworks(tx *sql.Tx) error {
- // Remove all seeded networks
- _, err := tx.Exec("DELETE FROM networks")
- if err != nil {
- return fmt.Errorf("failed to delete networks: %w", err)
- }
- return nil
-}
-
-// ipToInt converts an IP address to a 32-bit integer
-func ipToInt(ip net.IP) uint32 {
- ip = ip.To4()
- if ip == nil {
- return 0
- }
- return binary.BigEndian.Uint32(ip)
-}
-
-// intToIP converts a 32-bit integer to an IP address
-func intToIP(n uint32) net.IP {
- ip := make(net.IP, 4)
- binary.BigEndian.PutUint32(ip, n)
- return ip
-}
diff --git a/go/apps/metald/run.go b/go/apps/metald/run.go
deleted file mode 100644
index f0e3350ce8..0000000000
--- a/go/apps/metald/run.go
+++ /dev/null
@@ -1,518 +0,0 @@
-package metald
-
-import (
- "context"
- "database/sql"
- "embed"
- "errors"
- "fmt"
- "log/slog"
- "net/http"
- "os"
- "os/signal"
- "runtime"
- "runtime/debug"
- "syscall"
- "time"
-
- "github.com/unkeyed/unkey/go/apps/metald/internal/backend/types"
- "github.com/unkeyed/unkey/go/apps/metald/internal/billing"
- "github.com/unkeyed/unkey/go/apps/metald/internal/database"
- "github.com/unkeyed/unkey/go/apps/metald/internal/observability"
- "github.com/unkeyed/unkey/go/apps/metald/internal/service"
-
- "github.com/pressly/goose/v3"
- _ "github.com/unkeyed/unkey/go/apps/metald/migrations" // Import Go migrations
- healthpkg "github.com/unkeyed/unkey/go/deploy/pkg/health"
- "github.com/unkeyed/unkey/go/deploy/pkg/observability/interceptors"
- tlspkg "github.com/unkeyed/unkey/go/deploy/pkg/tls"
- "github.com/unkeyed/unkey/go/gen/proto/metald/v1/metaldv1connect"
-
- "connectrpc.com/connect"
- "github.com/prometheus/client_golang/prometheus/promhttp"
- "go.opentelemetry.io/otel"
- "golang.org/x/net/http2"
- "golang.org/x/net/http2/h2c"
-)
-
-//go:embed migrations/*.sql
-var migrationFS embed.FS
-
-// version is set at build time via ldflags
-var version = ""
-
-// Config represents the complete configuration for metald
-type Config struct {
- Server ServerConfig
- Backend BackendConfig
- Database DatabaseConfig
- AssetManager AssetManagerConfig
- Billing BillingConfig
- TLS TLSConfig
- OpenTelemetry OpenTelemetryConfig
- InstanceID string
-}
-
-// ServerConfig contains server settings
-type ServerConfig struct {
- Address string
- Port string
-}
-
-// BackendConfig contains backend settings
-type BackendConfig struct {
- Type string // firecracker, docker, or k8s
- Jailer JailerConfig
-}
-
-// JailerConfig contains jailer settings (firecracker only)
-type JailerConfig struct {
- UID uint32
- GID uint32
- ChrootBaseDir string
-}
-
-// DatabaseConfig contains database settings
-type DatabaseConfig struct {
- DataDir string
-}
-
-// AssetManagerConfig contains asset manager settings
-type AssetManagerConfig struct {
- Enabled bool
- Endpoint string
-}
-
-// BillingConfig contains billing settings
-type BillingConfig struct {
- Enabled bool
- Endpoint string
- MockMode bool
-}
-
-// TLSConfig contains TLS settings
-type TLSConfig struct {
- Mode string // disabled, file, or spiffe
- CertFile string
- KeyFile string
- CAFile string
- SPIFFESocketPath string
- EnableCertCaching bool
- CertCacheTTL string
-}
-
-// OpenTelemetryConfig contains OpenTelemetry settings
-type OpenTelemetryConfig struct {
- Enabled bool
- ServiceName string
- ServiceVersion string
- TracingSamplingRate float64
- OTLPEndpoint string
- PrometheusEnabled bool
- PrometheusPort string
- PrometheusInterface string
- HighCardinalityLabelsEnabled bool
-}
-
-// Validate validates the configuration
-func (c *Config) Validate() error {
- // Validate backend type
- switch c.Backend.Type {
- case "firecracker", "docker", "k8s":
- // Valid backend types
- default:
- return fmt.Errorf("invalid backend type: %s (must be firecracker, docker, or k8s)", c.Backend.Type)
- }
-
- // Validate TLS mode
- switch c.TLS.Mode {
- case "disabled", "file", "spiffe":
- // Valid TLS modes
- default:
- return fmt.Errorf("invalid TLS mode: %s (must be disabled, file, or spiffe)", c.TLS.Mode)
- }
-
- // If TLS mode is file, ensure cert and key files are provided
- if c.TLS.Mode == "file" {
- if c.TLS.CertFile == "" || c.TLS.KeyFile == "" {
- return fmt.Errorf("TLS mode is 'file' but cert or key file not provided")
- }
- }
-
- // Validate sampling rate
- if c.OpenTelemetry.TracingSamplingRate < 0 || c.OpenTelemetry.TracingSamplingRate > 1 {
- return fmt.Errorf("invalid tracing sampling rate: %f (must be between 0 and 1)", c.OpenTelemetry.TracingSamplingRate)
- }
-
- return nil
-}
-
-// Run starts the metald service with the given configuration
-func Run(ctx context.Context, cfg Config) error {
- startTime := time.Now()
-
- // Initialize structured logger with JSON output
- //exhaustruct:ignore
- logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
- Level: slog.LevelDebug,
- }))
- slog.SetDefault(logger)
-
- // Log startup
- logger.Info("starting metald",
- slog.String("version", getVersion()),
- slog.String("go_version", runtime.Version()),
- slog.String("backend", cfg.Backend.Type),
- slog.String("instance_id", cfg.InstanceID),
- )
-
- // Initialize OpenTelemetry
- otelProviders, err := observability.InitProviders(ctx, convertToInternalConfig(&cfg), getVersion(), logger)
- if err != nil {
- logger.Error("failed to initialize OpenTelemetry",
- slog.String("error", err.Error()),
- )
-
- return err
- }
-
- defer func() {
- shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
- if shutdownErr := otelProviders.Shutdown(shutdownCtx); shutdownErr != nil {
- logger.Error("failed to shutdown OpenTelemetry",
- slog.String("error", shutdownErr.Error()),
- )
- }
- }()
-
- // Initialize TLS provider
- //exhaustruct:ignore
- tlsConfig := tlspkg.Config{
- Mode: tlspkg.Mode(cfg.TLS.Mode),
- CertFile: cfg.TLS.CertFile,
- KeyFile: cfg.TLS.KeyFile,
- CAFile: cfg.TLS.CAFile,
- SPIFFESocketPath: cfg.TLS.SPIFFESocketPath,
- EnableCertCaching: cfg.TLS.EnableCertCaching,
- }
-
- // Parse certificate cache TTL
- if cfg.TLS.CertCacheTTL != "" {
- if duration, parseErr := time.ParseDuration(cfg.TLS.CertCacheTTL); parseErr == nil {
- tlsConfig.CertCacheTTL = duration
- } else {
- logger.Warn("invalid TLS certificate cache TTL, using default 5s",
- "value", cfg.TLS.CertCacheTTL,
- "error", parseErr)
- }
- }
-
- tlsProvider, err := tlspkg.NewProvider(ctx, tlsConfig)
- if err != nil {
- logger.Error("TLS initialization failed",
- "error", err,
- "mode", cfg.TLS.Mode)
- return err
- }
- defer tlsProvider.Close()
-
- logger.Info("TLS provider initialized",
- "mode", cfg.TLS.Mode,
- "spiffe_enabled", cfg.TLS.Mode == "spiffe")
-
- // Initialize database
- db, dbErr := database.NewDatabaseWithLogger(cfg.Database.DataDir, slog.Default())
- if dbErr != nil {
- logger.Error("failed to get DB",
- slog.String("error", dbErr.Error()),
- )
- return dbErr
- }
- defer db.Close()
-
- // Run database migrations
- if err := runMigrations(cfg.Database.DataDir, logger, db.DB()); err != nil {
- logger.Error("failed to run migrations", slog.String("error", err.Error()))
- return err
- }
-
- // Initialize backend based on configuration
- backend, err := initializeBackend(ctx, &cfg, logger, tlsProvider)
- if err != nil {
- logger.Error("failed to initialize backend",
- slog.String("error", err.Error()),
- slog.String("backend", cfg.Backend.Type),
- )
- return err
- }
-
- // Create billing client
- var billingClient billing.BillingClient
- if cfg.Billing.Enabled {
- if cfg.Billing.MockMode {
- billingClient = billing.NewMockBillingClient(logger)
- logger.Info("initialized mock billing client")
- } else {
- httpClient := tlsProvider.HTTPClient()
- billingClient = billing.NewConnectRPCBillingClientWithHTTP(cfg.Billing.Endpoint, logger, httpClient)
- logger.Info("initialized ConnectRPC billing client",
- "endpoint", cfg.Billing.Endpoint,
- "tls_enabled", cfg.TLS.Mode != "disabled",
- )
- }
- } else {
- billingClient = billing.NewMockBillingClient(logger)
- logger.Info("billing disabled, using mock client")
- }
-
- // Create VM metrics (only if OpenTelemetry is enabled)
- var vmMetrics *observability.VMMetrics
- var billingMetrics *observability.BillingMetrics
- if cfg.OpenTelemetry.Enabled {
- vmMetrics, err = observability.NewVMMetrics(logger, cfg.OpenTelemetry.HighCardinalityLabelsEnabled)
- if err != nil {
- logger.Error("failed to initialize VM metrics",
- slog.String("error", err.Error()),
- )
- return err
- }
-
- billingMetrics, err = observability.NewBillingMetrics(logger, cfg.OpenTelemetry.HighCardinalityLabelsEnabled)
- if err != nil {
- logger.Error("failed to initialize billing metrics",
- slog.String("error", err.Error()),
- )
- return err
- }
- logger.Info("VM and billing metrics initialized",
- slog.Bool("high_cardinality_enabled", cfg.OpenTelemetry.HighCardinalityLabelsEnabled),
- )
- }
-
- // Create metrics collector
- metricsCollector := billing.NewMetricsCollector(backend, billingClient, logger, cfg.InstanceID, billingMetrics)
-
- // Start heartbeat service
- metricsCollector.StartHeartbeat()
-
- // Create VM service
- vmService := service.NewVMService(backend, logger, metricsCollector, vmMetrics, db.Queries)
-
- // Create unified health handler
- healthHandler := healthpkg.Handler("metald", getVersion(), startTime)
-
- // Create ConnectRPC handler with shared interceptors
- var interceptorList []connect.Interceptor
-
- // Configure shared interceptor options
- interceptorOpts := []interceptors.Option{
- interceptors.WithServiceName("metald"),
- interceptors.WithLogger(logger),
- interceptors.WithActiveRequestsMetric(true),
- interceptors.WithRequestDurationMetric(true),
- interceptors.WithErrorResampling(true),
- interceptors.WithPanicStackTrace(true),
- }
-
- // Add meter if OpenTelemetry is enabled
- if cfg.OpenTelemetry.Enabled {
- interceptorOpts = append(interceptorOpts, interceptors.WithMeter(otel.Meter("metald")))
- }
-
- // Get default interceptors
- sharedInterceptors := interceptors.NewDefaultInterceptors("metald", interceptorOpts...)
-
- // Add shared interceptors
- for _, interceptor := range sharedInterceptors {
- interceptorList = append(interceptorList, connect.Interceptor(interceptor))
- }
-
- mux := http.NewServeMux()
- path, handler := metaldv1connect.NewVmServiceHandler(vmService,
- connect.WithInterceptors(interceptorList...),
- )
- mux.Handle(path, handler)
-
- // Add health endpoint
- mux.HandleFunc("/health", healthHandler)
-
- // Add Prometheus metrics endpoint if enabled
- if cfg.OpenTelemetry.Enabled && cfg.OpenTelemetry.PrometheusEnabled {
- mux.Handle("/metrics", otelProviders.PrometheusHTTP)
- logger.Info("Prometheus metrics endpoint enabled",
- slog.String("path", "/metrics"),
- )
- }
-
- // Create HTTP server with H2C support for gRPC
- addr := fmt.Sprintf("%s:%s", cfg.Server.Address, cfg.Server.Port)
-
- var httpHandler http.Handler = mux
-
- // Configure server with optional TLS and security timeouts
- server := &http.Server{
- Addr: addr,
- Handler: h2c.NewHandler(httpHandler, &http2.Server{}), //nolint:exhaustruct
- ReadTimeout: 30 * time.Second,
- WriteTimeout: 30 * time.Second,
- IdleTimeout: 120 * time.Second,
- MaxHeaderBytes: 1 << 20, // 1MB max header size
- }
-
- // Apply TLS configuration if enabled
- serverTLSConfig, _ := tlsProvider.ServerTLSConfig()
- if serverTLSConfig != nil {
- server.TLSConfig = serverTLSConfig
- server.Handler = httpHandler // For TLS, use regular handler, not h2c
- }
-
- // Start main server in goroutine
- go func() {
- if serverTLSConfig != nil {
- logger.Info("starting HTTPS server with TLS",
- slog.String("address", addr),
- slog.String("tls_mode", cfg.TLS.Mode),
- )
- if err := server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
- logger.Error("server failed",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
- } else {
- logger.Info("starting HTTP server without TLS",
- slog.String("address", addr),
- )
- if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
- logger.Error("server failed",
- slog.String("error", err.Error()),
- )
- os.Exit(1)
- }
- }
- }()
-
- // Start Prometheus server on separate port if enabled
- var promServer *http.Server
- if cfg.OpenTelemetry.Enabled && cfg.OpenTelemetry.PrometheusEnabled {
- promAddr := fmt.Sprintf("%s:%s", cfg.OpenTelemetry.PrometheusInterface, cfg.OpenTelemetry.PrometheusPort)
- promMux := http.NewServeMux()
- promMux.Handle("/metrics", promhttp.Handler())
- promMux.HandleFunc("/health", healthHandler)
-
- promServer = &http.Server{
- Addr: promAddr,
- Handler: promMux,
- ReadTimeout: 30 * time.Second,
- WriteTimeout: 30 * time.Second,
- IdleTimeout: 120 * time.Second,
- }
-
- go func() {
- localhostOnly := cfg.OpenTelemetry.PrometheusInterface == "127.0.0.1" || cfg.OpenTelemetry.PrometheusInterface == "localhost"
- logger.Info("starting prometheus metrics server",
- slog.String("address", promAddr),
- slog.Bool("localhost_only", localhostOnly),
- )
- if err := promServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
- logger.Error("prometheus server failed",
- slog.String("error", err.Error()),
- )
- }
- }()
- }
-
- // Wait for interrupt signal
- sigChan := make(chan os.Signal, 1)
- signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
- <-sigChan
-
- logger.Info("shutting down server")
-
- // Graceful shutdown with timeout
- shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
-
- // Shutdown all servers
- var shutdownErrors []error
-
- if err := server.Shutdown(shutdownCtx); err != nil {
- shutdownErrors = append(shutdownErrors, fmt.Errorf("main server: %w", err))
- }
-
- if promServer != nil {
- if err := promServer.Shutdown(shutdownCtx); err != nil {
- shutdownErrors = append(shutdownErrors, fmt.Errorf("prometheus server: %w", err))
- }
- }
-
- if len(shutdownErrors) > 0 {
- logger.Error("failed to shutdown servers gracefully",
- slog.String("error", errors.Join(shutdownErrors...).Error()),
- )
- return errors.Join(shutdownErrors...)
- }
-
- logger.Info("server shutdown complete")
- return nil
-}
-
-// initializeBackend creates the appropriate backend based on configuration
-func initializeBackend(ctx context.Context, cfg *Config, logger *slog.Logger, tlsProvider tlspkg.Provider) (types.Backend, error) {
- switch cfg.Backend.Type {
- case "firecracker":
- return initializeFirecrackerBackend(ctx, cfg, logger, tlsProvider)
- case "docker":
- return initializeDockerBackend(ctx, cfg, logger)
- case "k8s":
- return initializeK8sBackend(ctx, cfg, logger)
- default:
- return nil, fmt.Errorf("unsupported backend type: %s", cfg.Backend.Type)
- }
-}
-
-// getVersion returns the version string, with fallback to debug.ReadBuildInfo
-func getVersion() string {
- // If version was set via ldflags (production builds), use it
- if version != "" {
- return version
- }
-
- // Fallback to debug.ReadBuildInfo for development/module builds
- if info, ok := debug.ReadBuildInfo(); ok {
- // Use the module version if available
- if info.Main.Version != "(devel)" && info.Main.Version != "" {
- return info.Main.Version
- }
-
- // Try to get version from VCS info
- for _, setting := range info.Settings {
- if setting.Key == "vcs.revision" && len(setting.Value) >= 7 {
- return "dev-" + setting.Value[:7] // First 8 chars of commit hash
- }
- }
-
- return "dev"
- }
-
- return version
-}
-
-// runMigrations runs database migrations using goose with embedded migrations
-func runMigrations(dataDir string, logger *slog.Logger, db *sql.DB) error {
- if err := goose.SetDialect("sqlite3"); err != nil {
- return fmt.Errorf("failed to set goose dialect: %w", err)
- }
-
- // Set embedded filesystem as the migration source
- goose.SetBaseFS(migrationFS)
-
- if err := goose.Up(db, "migrations"); err != nil {
- return fmt.Errorf("failed to run migrations: %w", err)
- }
-
- logger.Info("migrations completed successfully")
-
- return nil
-}
diff --git a/go/apps/metald/scripts/_schema.sql.preamble b/go/apps/metald/scripts/_schema.sql.preamble
deleted file mode 100644
index f8320801b8..0000000000
--- a/go/apps/metald/scripts/_schema.sql.preamble
+++ /dev/null
@@ -1,37 +0,0 @@
--- schema.sql
-
-CREATE TABLE networks (
- id INTEGER PRIMARY KEY,
- base_network TEXT UNIQUE NOT NULL, -- CIDR notation: "10.0.0.16/28"
- is_allocated INTEGER NOT NULL DEFAULT 0
-);
-
-CREATE TABLE network_allocations (
- id INTEGER PRIMARY KEY,
- deployment_id TEXT NOT NULL UNIQUE,
- network_id INTEGER NOT NULL,
- available_ips TEXT NOT NULL, -- JSON array: ["10.0.0.18","10.0.0.19",...]
- allocated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (network_id) REFERENCES networks(id)
-);
-
-CREATE TABLE ip_allocations (
- id INTEGER PRIMARY KEY,
- vm_id TEXT NOT NULL UNIQUE,
- ip_addr TEXT NOT NULL,
- network_allocation_id INTEGER NOT NULL,
- allocated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (network_allocation_id) REFERENCES network_allocations(id),
- UNIQUE(network_allocation_id, ip_addr)
-);
-
--- Indexes for performance
-CREATE INDEX idx_networks_unallocated
- ON networks(is_allocated, id)
- WHERE is_allocated = 0;
-
-CREATE INDEX idx_ip_allocations_network
- ON ip_allocations(network_allocation_id);
-
-CREATE INDEX idx_network_allocations_deployment
- ON network_allocations(deployment_id);
diff --git a/go/apps/metald/scripts/generate-schema-and-seed.sh b/go/apps/metald/scripts/generate-schema-and-seed.sh
deleted file mode 100644
index c3614f2cf4..0000000000
--- a/go/apps/metald/scripts/generate-schema-and-seed.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/usr/bin/env bash
-
-set -euo pipefail
-
-BASE=$(dirname $(realpath $0))
-
-cat "${BASE}/_schema.sql.preamble" | tee "${BASE}/../sqlc/schema.sql"
-
-go run "${BASE}/netcalc.go" 10.0.0.0/8 /26 | tee -a "${BASE}/../sqlc/networks-seed.sql"
diff --git a/go/apps/metald/scripts/import-dashboards.sh b/go/apps/metald/scripts/import-dashboards.sh
deleted file mode 100755
index 3bc91abed8..0000000000
--- a/go/apps/metald/scripts/import-dashboards.sh
+++ /dev/null
@@ -1,215 +0,0 @@
-#!/bin/bash
-set -euo pipefail
-
-# Import Grafana dashboards for metald monitoring
-# Requires the LGTM stack to be running (use: make o11y)
-
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-DASHBOARD_DIR="$(dirname "$SCRIPT_DIR")/grafana-dashboards"
-GRAFANA_URL="${GRAFANA_URL:-http://localhost:3000}"
-GRAFANA_USER="${GRAFANA_USER:-admin}"
-GRAFANA_PASS="${GRAFANA_PASS:-admin}"
-
-# Colors for output
-RED='\033[0;31m'
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-NC='\033[0m' # No Color
-
-log_info() {
- echo -e "${GREEN}[INFO]${NC} $1"
-}
-
-log_warn() {
- echo -e "${YELLOW}[WARN]${NC} $1"
-}
-
-log_error() {
- echo -e "${RED}[ERROR]${NC} $1"
-}
-
-# Check if Grafana is accessible
-check_grafana() {
- log_info "Checking Grafana accessibility at $GRAFANA_URL..."
-
- if ! curl -s -f -u "$GRAFANA_USER:$GRAFANA_PASS" "$GRAFANA_URL/api/health" >/dev/null; then
- log_error "Grafana is not accessible at $GRAFANA_URL"
- log_error "Make sure the LGTM stack is running: make o11y"
- exit 1
- fi
-
- log_info "Grafana is accessible"
-}
-
-# Remove metald dashboards by searching for them
-remove_existing_dashboards() {
- log_info "Removing existing metald dashboards..."
-
- # Search for dashboards containing "metald" in title
- local search_response=$(curl -s -u "$GRAFANA_USER:$GRAFANA_PASS" \
- "$GRAFANA_URL/api/search?type=dash-db&query=metald")
-
- if [ "$search_response" = "null" ] || [ "$search_response" = "[]" ]; then
- log_info "No existing metald dashboards found"
- return 0
- fi
-
- # Parse and delete each dashboard
- local deleted=0
- while IFS= read -r uid; do
- if [ -n "$uid" ] && [ "$uid" != "null" ]; then
- log_info "Removing dashboard with UID: $uid"
- local delete_response=$(curl -s -X DELETE \
- -u "$GRAFANA_USER:$GRAFANA_PASS" \
- "$GRAFANA_URL/api/dashboards/uid/$uid")
-
- if echo "$delete_response" | jq -e '.message == "Dashboard deleted"' >/dev/null 2>&1; then
- ((deleted++))
- log_info "✅ Successfully removed dashboard: $uid"
- else
- log_warn "⚠️ Could not remove dashboard $uid: $delete_response"
- fi
- fi
- done < <(echo "$search_response" | jq -r '.[].uid // empty')
-
- if [ $deleted -gt 0 ]; then
- log_info "Removed $deleted existing dashboards"
- fi
-}
-
-
-# Create datasource if it doesn't exist
-setup_prometheus_datasource() {
- log_info "Setting up Prometheus datasource..."
-
- # Check if datasource already exists
- local existing=$(curl -s -u "$GRAFANA_USER:$GRAFANA_PASS" \
- "$GRAFANA_URL/api/datasources/name/prometheus" 2>/dev/null || echo "null")
-
- if [ "$existing" != "null" ]; then
- log_info "Prometheus datasource already exists"
- return 0
- fi
-
- # Create the datasource
- local datasource_payload='{
- "name": "prometheus",
- "type": "prometheus",
- "url": "http://localhost:9090",
- "access": "proxy",
- "isDefault": true,
- "basicAuth": false
- }'
-
- local response=$(curl -s -X POST \
- -H "Content-Type: application/json" \
- -u "$GRAFANA_USER:$GRAFANA_PASS" \
- -d "$datasource_payload" \
- "$GRAFANA_URL/api/datasources")
-
- if echo "$response" | jq -e '.id' >/dev/null 2>&1; then
- log_info "✅ Successfully created Prometheus datasource"
- else
- log_warn "⚠️ Could not create Prometheus datasource (may already exist)"
- log_warn " Response: $response"
- fi
-}
-
-# Main function
-main() {
- echo "🚀 Metald Dashboard Import Script"
- echo "================================"
-
- # Check dependencies
- if ! command -v curl >/dev/null 2>&1; then
- log_error "curl is required but not installed"
- exit 1
- fi
-
- if ! command -v jq >/dev/null 2>&1; then
- log_error "jq is required but not installed"
- exit 1
- fi
-
- # Check if dashboard directory exists
- if [ ! -d "$DASHBOARD_DIR" ]; then
- log_error "Dashboard directory not found: $DASHBOARD_DIR"
- exit 1
- fi
-
- # Check Grafana accessibility
- check_grafana
-
- # Setup Prometheus datasource
- setup_prometheus_datasource
-
- # Remove existing metald dashboards
- remove_existing_dashboards
-
- # Import all dashboards
- log_info "Importing dashboards from: $DASHBOARD_DIR"
-
- local imported=0
- local failed=0
-
- for dashboard_file in "$DASHBOARD_DIR"/*.json; do
- if [ -f "$dashboard_file" ]; then
- local dashboard_name=$(basename "$dashboard_file" .json)
- log_info "Importing dashboard: $dashboard_name"
-
- # Validate JSON first
- if ! jq . "$dashboard_file" >/dev/null 2>&1; then
- log_error "❌ Invalid JSON in $dashboard_file"
- ((failed++))
- continue
- fi
-
- # Create payload and import
- local payload=$(cat "$dashboard_file" | jq -c '{dashboard: ., overwrite: true, message: "Imported via script"}')
- local response=$(curl -s -X POST \
- -H "Content-Type: application/json" \
- -u "$GRAFANA_USER:$GRAFANA_PASS" \
- -d "$payload" \
- "$GRAFANA_URL/api/dashboards/db")
-
- # Check if import was successful
- if echo "$response" | jq -e '.status == "success"' >/dev/null 2>&1; then
- local dashboard_uid=$(echo "$response" | jq -r '.uid')
- local dashboard_url="$GRAFANA_URL/d/$dashboard_uid"
- log_info "✅ Successfully imported $dashboard_name"
- log_info " Dashboard URL: $dashboard_url"
- ((imported++))
- else
- log_error "❌ Failed to import $dashboard_name"
- log_error " Response: $response"
- ((failed++))
- fi
- fi
- done
-
- echo ""
- echo "📊 Import Summary"
- echo "================="
- log_info "Successfully imported: $imported dashboards"
-
- if [ $failed -gt 0 ]; then
- log_error "Failed to import: $failed dashboards"
- exit 1
- else
- log_info "🎉 All dashboards imported successfully!"
- echo ""
- echo "🔗 Access your dashboards at: $GRAFANA_URL"
- echo " Username: $GRAFANA_USER"
- echo " Password: $GRAFANA_PASS"
- echo ""
- echo "📋 Available dashboards:"
- echo " • VM Operations: $GRAFANA_URL/d/metald-vm-ops"
- echo " • Security Operations: $GRAFANA_URL/d/metald-security-ops"
- echo " • Billing & Metrics: $GRAFANA_URL/d/metald-billing"
- echo " • Multi-Tenant Billing: $GRAFANA_URL/d/metald-multi-tenant-billing"
- echo " • System Health: $GRAFANA_URL/d/metald-system-health"
- fi
-}
-
-# Run main function
-main "$@"
\ No newline at end of file
diff --git a/go/apps/metald/scripts/netcalc.go b/go/apps/metald/scripts/netcalc.go
deleted file mode 100644
index 6acdaf7c2d..0000000000
--- a/go/apps/metald/scripts/netcalc.go
+++ /dev/null
@@ -1,115 +0,0 @@
-// generate_seed.go
-package main
-
-import (
- "encoding/binary"
- "flag"
- "fmt"
- "net"
- "os"
- "strconv"
- "strings"
-)
-
-func main() {
- flag.Usage = func() {
- fmt.Fprintf(os.Stderr, "Usage: %s \n\n", os.Args[0])
- fmt.Fprintf(os.Stderr, "Examples:\n")
- fmt.Fprintf(os.Stderr, " %s 10.0.0.0/24 /28 # Split a /24 into /28 subnets\n", os.Args[0])
- fmt.Fprintf(os.Stderr, " %s 192.168.0.0/16 /24 # Split a /16 into /24 subnets\n", os.Args[0])
- fmt.Fprintf(os.Stderr, " %s 10.0.0.0/22 /27 # Split a /22 into /27 subnets\n\n", os.Args[0])
- fmt.Fprintf(os.Stderr, "Output can be piped directly to sqlite3:\n")
- fmt.Fprintf(os.Stderr, " %s 10.0.0.0/24 /28 | sqlite3 network.db\n", os.Args[0])
- }
-
- flag.Parse()
-
- if flag.NArg() != 2 {
- flag.Usage()
- os.Exit(1)
- }
-
- rootCIDR := flag.Arg(0)
- subnetSize := flag.Arg(1)
-
- // Parse the root network
- _, rootNet, err := net.ParseCIDR(rootCIDR)
- if err != nil {
- fmt.Fprintf(os.Stderr, "Error: Invalid CIDR notation '%s': %v\n", rootCIDR, err)
- os.Exit(1)
- }
-
- // Parse desired subnet size (e.g., "/28" -> 28)
- subnetSize = strings.TrimPrefix(subnetSize, "/")
- newPrefix, err := strconv.Atoi(subnetSize)
- if err != nil {
- fmt.Fprintf(os.Stderr, "Error: Invalid subnet size '%s'. Use format like '/28' or '28'\n", flag.Arg(1))
- os.Exit(1)
- }
-
- // Validate subnet size
- ones, bits := rootNet.Mask.Size()
- if newPrefix <= ones {
- fmt.Fprintf(os.Stderr, "Error: Subnet /%d must be smaller than root network /%d\n", newPrefix, ones)
- os.Exit(1)
- }
- if newPrefix > 32 {
- fmt.Fprintf(os.Stderr, "Error: Invalid subnet size /%d (maximum is /32)\n", newPrefix)
- os.Exit(1)
- }
-
- // Calculate how many subnets we'll create
- subnetCount := 1 << (newPrefix - ones)
-
- // Print info as SQL comments
- fmt.Printf("-- Generated network seed\n")
- fmt.Printf("-- Splitting %s into %d x /%d subnets\n", rootCIDR, subnetCount, newPrefix)
- fmt.Printf("-- Each /%d subnet has %d total IPs (%d usable)\n",
- newPrefix,
- 1<<(32-newPrefix),
- (1<<(32-newPrefix))-2)
- fmt.Println()
-
- // Generate the INSERT statement
- fmt.Println("INSERT INTO networks (base_network) VALUES")
-
- subnets := []string{}
- for ip := rootNet.IP.Mask(rootNet.Mask); rootNet.Contains(ip); {
- subnet := &net.IPNet{
- IP: ip,
- Mask: net.CIDRMask(newPrefix, bits),
- }
- subnets = append(subnets, fmt.Sprintf(" ('%s')", subnet.String()))
-
- // Move to next subnet
- inc := 1 << (bits - newPrefix)
- ipInt := ipToInt(ip)
- ipInt += uint32(inc)
- ip = intToIP(ipInt)
- }
-
- // Output with proper SQL formatting
- for i, subnet := range subnets {
- if i < len(subnets)-1 {
- fmt.Println(subnet + ",")
- } else {
- fmt.Println(subnet + ";")
- }
- }
-}
-
-// ipToInt converts an IP address to a 32-bit integer
-func ipToInt(ip net.IP) uint32 {
- ip = ip.To4()
- if ip == nil {
- return 0
- }
- return binary.BigEndian.Uint32(ip)
-}
-
-// intToIP converts a 32-bit integer to an IP address
-func intToIP(n uint32) net.IP {
- ip := make(net.IP, 4)
- binary.BigEndian.PutUint32(ip, n)
- return ip
-}
diff --git a/go/apps/metald/scripts/vm-stress-test.sh b/go/apps/metald/scripts/vm-stress-test.sh
deleted file mode 100755
index e9046b38f6..0000000000
--- a/go/apps/metald/scripts/vm-stress-test.sh
+++ /dev/null
@@ -1,582 +0,0 @@
-#!/bin/bash
-
-# AIDEV-NOTE: VM lifecycle stress test script for metald
-# This script performs comprehensive stress testing of VM creation, boot, shutdown, resume, and deletion
-# operations while respecting concurrency limits and tracking VM lifecycle states.
-
-set -euo pipefail
-
-# Configuration
-readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-readonly DOCKER_IMAGE="${DOCKER_IMAGE:-ghcr.io/unkeyed/best-api:v1.1.0}"
-readonly MAX_CONCURRENT_VMS=15
-readonly MAX_TOTAL_VMS=500
-readonly CREATION_DELAY_SECONDS=1
-readonly METALD_CLI="${METALD_CLI:-metald-cli}"
-
-# State tracking
-declare -A vm_states=()
-declare -a active_vms=()
-declare -a all_vms=()
-total_created=0
-total_operations=0
-errors=0
-
-# Colors for output
-readonly RED='\033[0;31m'
-readonly GREEN='\033[0;32m'
-readonly YELLOW='\033[1;33m'
-readonly BLUE='\033[0;34m'
-readonly NC='\033[0m' # No Color
-
-# Logging functions
-log_info() {
- echo -e "${BLUE}[INFO]${NC} $*" >&2
-}
-
-log_success() {
- echo -e "${GREEN}[SUCCESS]${NC} $*" >&2
-}
-
-log_warning() {
- echo -e "${YELLOW}[WARNING]${NC} $*" >&2
-}
-
-log_error() {
- echo -e "${RED}[ERROR]${NC} $*" >&2
- ((errors+=1))
-}
-
-# VM ID parsing function
-extract_vm_id() {
- local output="$1"
- # Extract VM IDs that start with 'ud-' from metald-cli output
- echo "$output" | grep -oE 'ud-[a-f0-9]+' | head -1 || true
-}
-
-# Execute metald-cli command with error handling
-execute_metald_command() {
- local cmd="$*"
- log_info "Executing: $cmd"
-
- local output
- local exit_code=0
-
- if output=$(timeout 60 $cmd 2>&1); then
- log_success "Command succeeded: $cmd"
- echo "$output"
- return 0
- else
- exit_code=$?
- log_error "Command failed (exit code: $exit_code): $cmd"
- log_error "Output: $output"
- return $exit_code
- fi
-}
-
-# Get current VM list and parse VM IDs
-list_vms() {
- local output
- if output=$(execute_metald_command $METALD_CLI -docker-image="$DOCKER_IMAGE" list); then
- echo "$output" | grep -oE 'ud-[a-f0-9]+' || true
- fi
-}
-
-# Create and boot a new VM
-create_and_boot_vm() {
- if [ ${#active_vms[@]} -ge $MAX_CONCURRENT_VMS ]; then
- log_warning "Maximum concurrent VMs ($MAX_CONCURRENT_VMS) reached. Skipping creation."
- return 1
- fi
-
- if [ $total_created -ge $MAX_TOTAL_VMS ]; then
- log_warning "Maximum total VMs ($MAX_TOTAL_VMS) reached. Skipping creation."
- return 1
- fi
-
- log_info "Creating and booting VM (active: ${#active_vms[@]}, total created: $total_created)"
-
- local output
- if output=$(execute_metald_command $METALD_CLI -docker-image="$DOCKER_IMAGE" create-and-boot); then
- local vm_id
- vm_id=$(extract_vm_id "$output")
-
- if [ -n "$vm_id" ]; then
- active_vms+=("$vm_id")
- all_vms+=("$vm_id")
- vm_states["$vm_id"]="running"
- ((total_created+=1))
- ((total_operations+=1))
- log_success "Created and booted VM: $vm_id"
- return 0
- else
- log_error "Failed to extract VM ID from create-and-boot output"
- return 1
- fi
- else
- log_error "Failed to create and boot VM"
- return 1
- fi
-}
-
-# Get VM info
-get_vm_info() {
- local vm_id="$1"
- log_info "Getting info for VM: $vm_id"
-
- if execute_metald_command $METALD_CLI -docker-image="$DOCKER_IMAGE" info "$vm_id" >/dev/null; then
- ((total_operations+=1))
- log_success "Retrieved info for VM: $vm_id"
- return 0
- else
- log_error "Failed to get info for VM: $vm_id"
- return 1
- fi
-}
-
-# Shutdown a VM
-shutdown_vm() {
- local vm_id="$1"
- log_info "Shutting down VM: $vm_id"
-
- if execute_metald_command $METALD_CLI -docker-image="$DOCKER_IMAGE" shutdown "$vm_id" >/dev/null; then
- vm_states["$vm_id"]="shutdown"
- ((total_operations+=1))
- log_success "Shutdown VM: $vm_id"
- return 0
- else
- log_error "Failed to shutdown VM: $vm_id"
- return 1
- fi
-}
-
-# Resume a VM
-resume_vm() {
- local vm_id="$1"
- log_info "Resuming VM: $vm_id"
-
- if execute_metald_command $METALD_CLI -docker-image="$DOCKER_IMAGE" resume "$vm_id" >/dev/null; then
- vm_states["$vm_id"]="running"
- ((total_operations+=1))
- log_success "Resumed VM: $vm_id"
- return 0
- else
- log_error "Failed to resume VM: $vm_id"
- return 1
- fi
-}
-
-# Delete a VM
-delete_vm() {
- local vm_id="$1"
- log_info "Deleting VM: $vm_id"
-
- if execute_metald_command $METALD_CLI -docker-image="$DOCKER_IMAGE" delete "$vm_id" >/dev/null; then
- # Remove from active VMs array
- for i in "${!active_vms[@]}"; do
- if [[ "${active_vms[i]}" == "$vm_id" ]]; then
- unset "active_vms[i]"
- break
- fi
- done
- # Rebuild array to remove gaps
- active_vms=("${active_vms[@]}")
-
- unset vm_states["$vm_id"]
- ((total_operations+=1))
- log_success "Deleted VM: $vm_id"
- return 0
- else
- log_error "Failed to delete VM: $vm_id"
- return 1
- fi
-}
-
-# Perform a random VM lifecycle operation
-perform_random_operation() {
- if [ ${#active_vms[@]} -eq 0 ]; then
- create_and_boot_vm
- return
- fi
-
- # Choose a random operation
- local operations=("info" "shutdown" "resume" "delete")
- local operation="${operations[$((RANDOM % ${#operations[@]}))]}"
-
- # Choose a random VM
- local vm_id="${active_vms[$((RANDOM % ${#active_vms[@]}))]}"
- local current_state="${vm_states[$vm_id]:-unknown}"
-
- case "$operation" in
- "info")
- get_vm_info "$vm_id"
- ;;
- "shutdown")
- if [ "$current_state" = "running" ]; then
- shutdown_vm "$vm_id"
- else
- log_info "VM $vm_id is not running (state: $current_state), skipping shutdown"
- fi
- ;;
- "resume")
- if [ "$current_state" = "shutdown" ]; then
- resume_vm "$vm_id"
- else
- log_info "VM $vm_id is not shutdown (state: $current_state), skipping resume"
- fi
- ;;
- "delete")
- delete_vm "$vm_id"
- ;;
- esac
-}
-
-# Clean up all VMs
-cleanup_all_vms() {
- log_info "Cleaning up all VMs..."
-
- local cleanup_vms
- readarray -t cleanup_vms < <(list_vms)
-
- for vm_id in "${cleanup_vms[@]}"; do
- if [ -n "$vm_id" ]; then
- log_info "Cleaning up VM: $vm_id"
- # Try to shutdown first, then delete
- execute_metald_command $METALD_CLI -docker-image="$DOCKER_IMAGE" shutdown "$vm_id" >/dev/null 2>&1 || true
- sleep 1
- execute_metald_command $METALD_CLI -docker-image="$DOCKER_IMAGE" delete "$vm_id" >/dev/null 2>&1 || true
- fi
- done
-
- log_success "Cleanup completed"
-}
-
-# Print current status
-print_status() {
- log_info "=== VM Stress Test Status ==="
- log_info "Active VMs: ${#active_vms[@]}"
- log_info "Total VMs created: $total_created"
- log_info "Total operations: $total_operations"
- log_info "Errors: $errors"
-
- if [ ${#active_vms[@]} -gt 0 ]; then
- log_info "Active VM states:"
- for vm_id in "${active_vms[@]}"; do
- log_info " $vm_id: ${vm_states[$vm_id]:-unknown}"
- done
- fi
- log_info "=========================="
-}
-
-# Signal handlers
-cleanup_and_exit() {
- log_warning "Received signal, cleaning up..."
- cleanup_all_vms
- print_status
- exit 0
-}
-
-# Set up signal handlers
-trap cleanup_and_exit SIGINT SIGTERM
-
-# Test VM persistence across metald restarts
-# AIDEV-BUSINESS_RULE: VMs should survive metald service restarts like VMware/VirtualBox
-run_persistence_test() {
- local test_duration="${1:-120}" # Default 2 minutes per phase
- log_info "Starting VM persistence test"
- log_info "This test will create VMs, restart metald, and verify VMs persist"
-
- # Phase 1: Create and manage some VMs
- log_info "Phase 1: Creating test VMs"
- local test_vms=()
- local shutdown_vms=()
- local running_vms=()
-
- # Create 5 VMs for testing
- for i in {1..5}; do
- log_info "Creating test VM $i/5"
- local output
- if output=$(execute_metald_command $METALD_CLI -docker-image="$DOCKER_IMAGE" create-and-boot); then
- local vm_id
- vm_id=$(extract_vm_id "$output")
- if [ -n "$vm_id" ]; then
- test_vms+=("$vm_id")
- running_vms+=("$vm_id")
- log_success "Created test VM: $vm_id"
- sleep 2
- fi
- else
- log_error "Failed to create test VM $i"
- fi
- done
-
- if [ ${#test_vms[@]} -eq 0 ]; then
- log_error "No test VMs created, aborting persistence test"
- return 1
- fi
-
- # Shutdown half of the VMs to test SHUTDOWN persistence
- local shutdown_count=$((${#test_vms[@]} / 2))
- log_info "Phase 2: Shutting down $shutdown_count VMs for persistence testing"
-
- for ((i=0; i/dev/null 2>&1; then
- log_error "metald-cli not found at: $METALD_CLI"
- log_error "Please install metald-cli or set METALD_CLI environment variable"
- exit 1
- fi
-
- if [ "$cleanup_only" = true ]; then
- cleanup_all_vms
- exit 0
- fi
-
- if [ "$status_only" = true ]; then
- local existing_vms
- readarray -t existing_vms < <(list_vms)
- log_info "Current VMs:"
- for vm_id in "${existing_vms[@]}"; do
- if [ -n "$vm_id" ]; then
- get_vm_info "$vm_id" || true
- fi
- done
- exit 0
- fi
-
- if [ "$persistence_test" = true ]; then
- run_persistence_test
- exit $?
- fi
-
- run_stress_test "$duration"
-}
-
-# Run main function with all arguments
-main "$@"
diff --git a/go/apps/metald/sqlc/generate.go b/go/apps/metald/sqlc/generate.go
deleted file mode 100644
index 79526fe004..0000000000
--- a/go/apps/metald/sqlc/generate.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package db
-
-//go:generate sqlc generate
diff --git a/go/apps/metald/sqlc/queries.sql b/go/apps/metald/sqlc/queries.sql
deleted file mode 100644
index 3f6bfddae9..0000000000
--- a/go/apps/metald/sqlc/queries.sql
+++ /dev/null
@@ -1,90 +0,0 @@
--- queries.sql
-
--- name: AllocateNetwork :one
-UPDATE networks
-SET is_allocated = 1
-WHERE id = (
- SELECT id FROM networks
- WHERE is_allocated = 0
- ORDER BY id
- LIMIT 1
-)
-RETURNING *;
-
--- name: CreateNetworkAllocation :one
-INSERT INTO network_allocations (deployment_id, network_id, available_ips, bridge_name)
-VALUES (?, ?, ?, ?)
-RETURNING *;
-
--- name: GetNetworkAllocation :one
-SELECT na.*, n.base_network
-FROM network_allocations na
-JOIN networks n ON na.network_id = n.id
-WHERE na.deployment_id = ?;
-
--- name: PopAvailableIPJSON :one
-UPDATE network_allocations
-SET available_ips = json_remove(available_ips, '$[0]')
-WHERE deployment_id = ?
-AND json_array_length(available_ips) > 0
-RETURNING CAST(json_extract(available_ips, '$[0]') AS TEXT) AS ip, id;
-
--- name: CleanupStaleIPAllocations :exec
-DELETE FROM ip_allocations
-WHERE network_allocation_id IN (
- SELECT id FROM network_allocations na
- WHERE na.allocated_at < ?
-);
-
--- name: ReleaseStaleNetworks :exec
-UPDATE networks
-SET is_allocated = 0
-WHERE id IN (
- SELECT network_id FROM network_allocations na
- WHERE na.allocated_at < ?
-);
-
--- name: DeleteStaleNetworkAllocations :exec
-DELETE FROM network_allocations
-WHERE allocated_at < ?;
-
--- name: AllocateIP :one
-INSERT INTO ip_allocations (vm_id, ip_addr, network_allocation_id)
-VALUES (?, ?, ?)
-RETURNING *;
-
--- name: GetIPAllocation :one
-SELECT * FROM ip_allocations WHERE vm_id = ?;
-
--- name: ReleaseIP :exec
-DELETE FROM ip_allocations WHERE vm_id = ?;
-
--- name: ReturnIPJSON :exec
-UPDATE network_allocations
-SET available_ips = json_insert(available_ips, '$[#]', ?)
-WHERE deployment_id = ?;
-
--- name: GetAvailableIPCount :one
-SELECT json_array_length(available_ips) as count
-FROM network_allocations
-WHERE deployment_id = ?;
-
--- name: ReleaseNetwork :exec
-UPDATE networks
-SET is_allocated = 0
-WHERE id = ?;
-
--- name: DeleteIPAllocationsForNetwork :exec
-DELETE FROM ip_allocations
-WHERE network_allocation_id = ?;
-
--- name: DeleteNetworkAllocation :exec
-DELETE FROM network_allocations
-WHERE deployment_id = ?;
-
--- name: GetNetworkStats :one
-SELECT
- (SELECT COUNT(*) FROM networks) as total_networks,
- (SELECT COUNT(*) FROM networks WHERE is_allocated = 0) as available_networks,
- (SELECT COUNT(*) FROM network_allocations) as active_deployments,
- (SELECT COUNT(*) FROM ip_allocations) as allocated_ips;
diff --git a/go/apps/metald/sqlc/sqlc.yaml b/go/apps/metald/sqlc/sqlc.yaml
deleted file mode 100644
index f79a512fa9..0000000000
--- a/go/apps/metald/sqlc/sqlc.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-version: "2"
-sql:
- - engine: "sqlite"
- queries: "queries.sql"
- schema: "../migrations/*.sql"
- gen:
- go:
- package: "database"
- out: "../internal/database"
- emit_db_tags: true
- emit_json_tags: true
- emit_interface: true
diff --git a/go/cmd/ctrl/main.go b/go/cmd/ctrl/main.go
index 60f2a04381..80d832fc96 100644
--- a/go/cmd/ctrl/main.go
+++ b/go/cmd/ctrl/main.go
@@ -54,10 +54,10 @@ var Cmd = &cli.Command{
// Control Plane Specific
cli.String("auth-token", "Authentication token for control plane API access. Required for secure deployments.",
cli.EnvVar("UNKEY_AUTH_TOKEN")),
+ cli.String("krane-address", "Full URL of the krane service for VM operations. Required for deployments. Example: https://krane.example.com:8080",
+ cli.Required(), cli.EnvVar("UNKEY_KRANE_ADDRESS")),
cli.String("api-key", "API key for simple authentication (demo purposes only). Will be replaced with JWT authentication.",
cli.Required(), cli.EnvVar("UNKEY_API_KEY")),
- cli.String("metald-address", "Full URL of the metald service for VM operations. Required for deployments. Example: https://metald.example.com:8080",
- cli.Required(), cli.EnvVar("UNKEY_METALD_ADDRESS")),
cli.String("spiffe-socket-path", "Path to SPIFFE agent socket for mTLS authentication. Default: /var/lib/spire/agent/agent.sock",
cli.Default("/var/lib/spire/agent/agent.sock"), cli.EnvVar("UNKEY_SPIFFE_SOCKET_PATH")),
@@ -122,8 +122,8 @@ func action(ctx context.Context, cmd *cli.Command) error {
// Control Plane Specific
AuthToken: cmd.String("auth-token"),
+ KraneAddress: cmd.String("krane-address"),
APIKey: cmd.String("api-key"),
- MetaldAddress: cmd.String("metald-address"),
SPIFFESocketPath: cmd.String("spiffe-socket-path"),
// Vault configuration
diff --git a/go/cmd/krane/main.go b/go/cmd/krane/main.go
new file mode 100644
index 0000000000..2dfc8aaa17
--- /dev/null
+++ b/go/cmd/krane/main.go
@@ -0,0 +1,83 @@
+package krane
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/unkeyed/unkey/go/apps/krane"
+ "github.com/unkeyed/unkey/go/pkg/cli"
+ "github.com/unkeyed/unkey/go/pkg/uid"
+)
+
+var Cmd = &cli.Command{
+ Name: "krane",
+ Usage: "Run the k8s management service",
+ Description: `krane (/kreɪn/) is the kubernetes deployment service for Unkey infrastructure.
+
+It manages the lifecycle of deployments in a kubernetes cluster:
+
+EXAMPLES:
+unkey run krane # Run with default configuration`,
+
+ Flags: []cli.Flag{
+ // Server Configuration
+ cli.Int("http-port", "Port for the server to listen on. Default: 8080",
+ cli.Default(8080), cli.EnvVar("UNKEY_HTTP_PORT")),
+
+ // Instance Identification
+ cli.String("instance-id", "Unique identifier for this instance. Auto-generated if not provided.",
+ cli.Default(uid.New(uid.InstancePrefix, 4)), cli.EnvVar("UNKEY_INSTANCE_ID")),
+
+ cli.String("backend", "Backend type for the service. Either kubernetes or docker. Default: kubernetes",
+ cli.Default("kubernetes"), cli.EnvVar("UNKEY_KRANE_BACKEND")),
+
+ cli.String("docker-socket", "Path to the docker socket. Only used if backend is docker. Default: /var/run/docker.sock",
+ cli.Default("/var/run/docker.sock"), cli.EnvVar("UNKEY_DOCKER_SOCKET")),
+
+ // This has no use outside of our demo cluster and will be removed soon
+ cli.Duration("deployment-eviction-ttl", "Automatically delete deployments after some time. Use go duration formats such as 2h30m",
+ cli.Default(0), cli.EnvVar("UNKEY_DEPLOYMENT_EVICTION_TTL")),
+ },
+
+ Action: action,
+}
+
+func action(ctx context.Context, cmd *cli.Command) error {
+ backend, err := parseBackend(cmd.String("backend"))
+ if err != nil {
+ return cli.Exit(err.Error(), 1)
+ }
+ config := krane.Config{
+ HttpPort: cmd.Int("http-port"),
+ Backend: backend,
+ Platform: cmd.String("platform"),
+ Image: cmd.String("image"),
+ Region: cmd.String("region"),
+ OtelEnabled: false,
+ OtelTraceSamplingRate: 1.0,
+ InstanceID: cmd.String("instance-id"),
+ DockerSocketPath: cmd.String("docker-socket"),
+ DeploymentEvictionTTL: cmd.Duration("deployment-eviction-ttl"),
+ }
+
+ // Validate configuration
+ err = config.Validate()
+ if err != nil {
+ return cli.Exit("Invalid configuration: "+err.Error(), 1)
+ }
+
+ // Run krane
+ return krane.Run(ctx, config)
+}
+
+func parseBackend(s string) (krane.Backend, error) {
+ switch s {
+ case "docker":
+ return krane.Docker, nil
+ case "kubernetes":
+
+ return krane.Kubernetes, nil
+ default:
+ return "", fmt.Errorf("unknown backend type: %s", s)
+ }
+}
diff --git a/go/cmd/metald/main.go b/go/cmd/metald/main.go
deleted file mode 100644
index e94073573e..0000000000
--- a/go/cmd/metald/main.go
+++ /dev/null
@@ -1,161 +0,0 @@
-package metald
-
-import (
- "context"
-
- "github.com/unkeyed/unkey/go/apps/metald"
- "github.com/unkeyed/unkey/go/pkg/cli"
- "github.com/unkeyed/unkey/go/pkg/uid"
-)
-
-var Cmd = &cli.Command{
- Name: "metald",
- Usage: "Run the Metald VM management service",
- Description: `Metald is the VM management service for Unkey infrastructure.
-
-It manages the lifecycle of virtual machines using different backends:
-- firecracker: High-performance microVMs (Linux only)
-- docker: Container-based VMs for development
-- k8s: Kubernetes pod-based VMs for cloud environments
-
-EXAMPLES:
-unkey run metald # Run with default configuration
-unkey run metald --backend docker # Use Docker backend for development
-unkey run metald --backend k8s # Use Kubernetes backend
-unkey run metald --backend firecracker # Use Firecracker (Linux only)`,
-
- Flags: []cli.Flag{
- // Server Configuration
- cli.String("address", "Bind address for the server. Default: 0.0.0.0",
- cli.Default("0.0.0.0"), cli.EnvVar("UNKEY_METALD_ADDRESS")),
- cli.String("port", "Port for the server to listen on. Default: 8080",
- cli.Default("8080"), cli.EnvVar("UNKEY_METALD_PORT")),
-
- // Backend Configuration
- cli.String("backend", "Backend type: firecracker, docker, or k8s. Default: docker",
- cli.Default("docker"), cli.EnvVar("UNKEY_METALD_BACKEND")),
-
- // Database Configuration
- cli.String("database-dir", "Directory for local database storage. Default: /tmp/metald",
- cli.Default("/tmp/metald"), cli.EnvVar("UNKEY_METALD_DATABASE_DIR")),
-
- // Jailer Configuration (Firecracker only)
- cli.Int("jailer-uid", "User ID for jailer process (firecracker only). Default: 1000",
- cli.Default(1000), cli.EnvVar("UNKEY_METALD_JAILER_UID")),
- cli.Int("jailer-gid", "Group ID for jailer process (firecracker only). Default: 1000",
- cli.Default(1000), cli.EnvVar("UNKEY_METALD_JAILER_GID")),
- cli.String("jailer-chroot-dir", "Chroot base directory (firecracker only). Default: /srv/jailer",
- cli.Default("/srv/jailer"), cli.EnvVar("UNKEY_METALD_JAILER_CHROOT_DIR")),
-
- // AssetManager Configuration
- cli.Bool("assetmanager-enabled", "Enable AssetManager for kernel/rootfs management. Default: false",
- cli.Default(false), cli.EnvVar("UNKEY_METALD_ASSETMANAGER_ENABLED")),
- cli.String("assetmanager-endpoint", "AssetManager service endpoint",
- cli.EnvVar("UNKEY_METALD_ASSETMANAGER_ENDPOINT")),
-
- // Billing Configuration
- cli.Bool("billing-enabled", "Enable billing integration. Default: false",
- cli.Default(false), cli.EnvVar("UNKEY_METALD_BILLING_ENABLED")),
- cli.String("billing-endpoint", "Billing service endpoint",
- cli.EnvVar("UNKEY_METALD_BILLING_ENDPOINT")),
- cli.Bool("billing-mock", "Use mock billing client for testing. Default: false",
- cli.Default(false), cli.EnvVar("UNKEY_METALD_BILLING_MOCK")),
-
- // TLS Configuration
- cli.String("tls-mode", "TLS mode: disabled, file, or spiffe. Default: disabled",
- cli.Default("disabled"), cli.EnvVar("UNKEY_METALD_TLS_MODE")),
- cli.String("tls-cert-file", "Path to TLS certificate file (file mode only)",
- cli.EnvVar("UNKEY_METALD_TLS_CERT_FILE")),
- cli.String("tls-key-file", "Path to TLS key file (file mode only)",
- cli.EnvVar("UNKEY_METALD_TLS_KEY_FILE")),
- cli.String("tls-ca-file", "Path to CA certificate file",
- cli.EnvVar("UNKEY_METALD_TLS_CA_FILE")),
- cli.String("tls-spiffe-socket", "SPIFFE Workload API socket path. Default: /tmp/spire-agent/public/api.sock",
- cli.Default("/tmp/spire-agent/public/api.sock"), cli.EnvVar("UNKEY_METALD_TLS_SPIFFE_SOCKET")),
-
- // OpenTelemetry Configuration
- cli.Bool("otel-enabled", "Enable OpenTelemetry tracing and metrics. Default: false",
- cli.Default(false), cli.EnvVar("UNKEY_METALD_OTEL_ENABLED")),
- cli.String("otel-service-name", "Service name for OpenTelemetry. Default: metald",
- cli.Default("metald"), cli.EnvVar("UNKEY_METALD_OTEL_SERVICE_NAME")),
- cli.String("otel-service-version", "Service version for OpenTelemetry. Default: 0.1.0",
- cli.Default("0.1.0"), cli.EnvVar("UNKEY_METALD_OTEL_SERVICE_VERSION")),
- cli.Float("otel-sampling-rate", "Trace sampling rate (0.0-1.0). Default: 1.0",
- cli.Default(1.0), cli.EnvVar("UNKEY_METALD_OTEL_SAMPLING_RATE")),
- cli.String("otel-endpoint", "OTLP endpoint. Default: localhost:4318",
- cli.Default("localhost:4318"), cli.EnvVar("UNKEY_METALD_OTEL_ENDPOINT")),
- cli.Bool("otel-prometheus-enabled", "Enable Prometheus metrics. Default: true",
- cli.Default(true), cli.EnvVar("UNKEY_METALD_OTEL_PROMETHEUS_ENABLED")),
- cli.String("otel-prometheus-port", "Prometheus metrics port. Default: 9464",
- cli.Default("9464"), cli.EnvVar("UNKEY_METALD_OTEL_PROMETHEUS_PORT")),
- cli.String("otel-prometheus-interface", "Prometheus metrics interface. Default: 127.0.0.1",
- cli.Default("127.0.0.1"), cli.EnvVar("UNKEY_METALD_OTEL_PROMETHEUS_INTERFACE")),
- cli.Bool("otel-high-cardinality", "Enable high-cardinality labels. Default: false",
- cli.Default(false), cli.EnvVar("UNKEY_METALD_OTEL_HIGH_CARDINALITY_ENABLED")),
-
- // Instance Identification
- cli.String("instance-id", "Unique identifier for this instance. Auto-generated if not provided.",
- cli.Default(uid.New(uid.InstancePrefix, 4)), cli.EnvVar("UNKEY_METALD_INSTANCE_ID")),
- },
-
- Action: action,
-}
-
-func action(ctx context.Context, cmd *cli.Command) error {
- config := metald.Config{
- Server: metald.ServerConfig{
- Address: cmd.String("address"),
- Port: cmd.String("port"),
- },
- Backend: metald.BackendConfig{
- Type: cmd.String("backend"),
- Jailer: metald.JailerConfig{
- UID: uint32(cmd.Int("jailer-uid")),
- GID: uint32(cmd.Int("jailer-gid")),
- ChrootBaseDir: cmd.String("jailer-chroot-dir"),
- },
- },
- Database: metald.DatabaseConfig{
- DataDir: cmd.String("database-dir"),
- },
- AssetManager: metald.AssetManagerConfig{
- Enabled: cmd.Bool("assetmanager-enabled"),
- Endpoint: cmd.String("assetmanager-endpoint"),
- },
- Billing: metald.BillingConfig{
- Enabled: cmd.Bool("billing-enabled"),
- Endpoint: cmd.String("billing-endpoint"),
- MockMode: cmd.Bool("billing-mock"),
- },
- TLS: metald.TLSConfig{
- Mode: cmd.String("tls-mode"),
- CertFile: cmd.String("tls-cert-file"),
- KeyFile: cmd.String("tls-key-file"),
- CAFile: cmd.String("tls-ca-file"),
- SPIFFESocketPath: cmd.String("tls-spiffe-socket"),
- EnableCertCaching: true,
- CertCacheTTL: "5s",
- },
- OpenTelemetry: metald.OpenTelemetryConfig{
- Enabled: cmd.Bool("otel-enabled"),
- ServiceName: cmd.String("otel-service-name"),
- ServiceVersion: cmd.String("otel-service-version"),
- TracingSamplingRate: cmd.Float("otel-sampling-rate"),
- OTLPEndpoint: cmd.String("otel-endpoint"),
- PrometheusEnabled: cmd.Bool("otel-prometheus-enabled"),
- PrometheusPort: cmd.String("otel-prometheus-port"),
- PrometheusInterface: cmd.String("otel-prometheus-interface"),
- HighCardinalityLabelsEnabled: cmd.Bool("otel-high-cardinality"),
- },
- InstanceID: cmd.String("instance-id"),
- }
-
- // Validate configuration
- err := config.Validate()
- if err != nil {
- return cli.Exit("Invalid configuration: "+err.Error(), 1)
- }
-
- // Run metald
- return metald.Run(ctx, config)
-}
diff --git a/go/cmd/run/main.go b/go/cmd/run/main.go
index fa5538b836..bc4e9811c1 100644
--- a/go/cmd/run/main.go
+++ b/go/cmd/run/main.go
@@ -7,7 +7,7 @@ import (
"github.com/unkeyed/unkey/go/cmd/api"
"github.com/unkeyed/unkey/go/cmd/ctrl"
"github.com/unkeyed/unkey/go/cmd/gw"
- "github.com/unkeyed/unkey/go/cmd/metald"
+ "github.com/unkeyed/unkey/go/cmd/krane"
"github.com/unkeyed/unkey/go/pkg/cli"
)
@@ -22,30 +22,29 @@ AVAILABLE SERVICES:
- api: The main API server for validating and managing API keys
- ctrl: The control plane service for managing infrastructure and deployments
- gw: The gateway service for routing requests to the appropriate API
-- metald: The VM management service for infrastructure
+- krane: The VM management service for infrastructure
EXAMPLES:
unkey run api # Run the API server
unkey run ctrl # Run the control plane
unkey run gw # Run the gateway
-unkey run metald # Run the VM management service
unkey run --help # Show available services and their options
unkey run api --port 8080 --env production # Run API server with custom configuration`,
Commands: []*cli.Command{
api.Cmd,
ctrl.Cmd,
gw.Cmd,
- metald.Cmd,
+ krane.Cmd,
},
Action: runAction,
}
func runAction(ctx context.Context, cmd *cli.Command) error {
fmt.Println("Available services:")
- fmt.Println(" api - The main API server for validating and managing API keys")
- fmt.Println(" ctrl - The control plane service for managing infrastructure")
- fmt.Println(" gw - The gateway service")
- fmt.Println(" metald - The VM management service")
+ fmt.Println(" api - The main API server for validating and managing API keys")
+ fmt.Println(" ctrl - The control plane service for managing infrastructure")
+ fmt.Println(" gw - The gateway service")
+ fmt.Println(" krane - Manage containers and deployments in docker or kubernetes")
fmt.Println()
fmt.Println("Use 'unkey run ' to start a specific service")
fmt.Println("Use 'unkey run --help' for service-specific options")
diff --git a/go/demo_api/main.go b/go/demo_api/main.go
index ae701814e6..0f67d6ecbd 100644
--- a/go/demo_api/main.go
+++ b/go/demo_api/main.go
@@ -297,6 +297,12 @@ func main() {
fmt.Fprint(w, "OK")
})
+ mux.HandleFunc("/env", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/plain")
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprint(w, os.Environ())
+ })
+
// Debug endpoint - dumps request headers and body
mux.HandleFunc("/v1/debug", func(w http.ResponseWriter, r *http.Request) {
// Read body
diff --git a/go/deploy/.gitignore b/go/deploy/.gitignore
deleted file mode 100644
index 81b5230d36..0000000000
--- a/go/deploy/.gitignore
+++ /dev/null
@@ -1,87 +0,0 @@
-# Compiled binaries (SECURITY: Never commit compiled binaries)
-build/
-*.exe
-*.dll
-*.so
-*.dylib
-
-# Test binaries, built with `go test -c`
-*.test
-
-# Output of the go coverage tool
-*.out
-
-# Dependency directories (remove the comment below to include it)
-vendor/
-
-# Go workspace file
-go.work
-go.work.sum
-
-# IDE files
-.vscode/
-.idea/
-*.swp
-*.swo
-*~
-
-# OS files
-.DS_Store
-Thumbs.db
-
-# Local development files
-.env
-.env.local
-.env.development
-.env.test
-.env.production
-
-# Temporary files
-tmp/
-temp/
-*.tmp
-
-# Logs
-*.log
-logs/
-
-# Database files
-*.db
-*.sqlite
-*.sqlite3
-
-# Build artifacts and cache
-dist/
-cache/
-.cache/
-
-# Coverage reports
-coverage.html
-coverage.out
-profile.out
-
-# Backup files
-*.bak
-*.backup
-
-# Docker build context (if using dockerignore isn't sufficient)
-.dockerignore
-
-# Certificate files (never commit certificates or keys)
-*.pem
-*.key
-*.crt
-*.p12
-*.pfx
-
-# Secret files
-secrets.yaml
-secrets.json
-.secrets
-
-# Local storage directories for development
-data/
-storage/
-scratch/
-rootfs/
-workspace/
diff --git a/go/deploy/DOCKER_DEVELOPMENT_PLAN.md b/go/deploy/DOCKER_DEVELOPMENT_PLAN.md
deleted file mode 100644
index 971ebfafe0..0000000000
--- a/go/deploy/DOCKER_DEVELOPMENT_PLAN.md
+++ /dev/null
@@ -1,241 +0,0 @@
-# Docker Development Environment Plan
-
-## Overview
-
-This document outlines the plan to create a single Docker container that runs all four Unkey deploy services (`assetmanagerd`, `billaged`, `builderd`, `metald`) for local development purposes. The container will use systemd as the process manager and implement a Docker backend for metald to create containers instead of VMs.
-
-## Goals
-
-1. **Production Parity**: Mirror the production deployment process as closely as possible
-2. **Simplified Development**: No Firecracker/KVM setup required
-3. **Container-Native**: "VMs" are actually Docker containers managed by metald
-4. **Easy Debugging**: All services in one container with unified logging
-5. **Fast Iteration**: Instant container startup instead of VM boot times
-
-## Architecture
-
-### Base Container Design
-
-- **Base Image**: Fedora 42 (matches production environment)
-- **Process Manager**: systemd (matches production)
-- **Service Installation**: Use existing Makefiles and build process
-- **Configuration**: Modify existing systemd services with minimal changes
-
-### Docker Backend for metald
-
-Replace Firecracker backend with Docker backend:
-
-```
-VM Operations → Docker Container Operations
-├── CreateVM → docker create
-├── BootVM → docker start
-├── DeleteVM → docker rm
-├── ShutdownVM → docker stop
-├── PauseVM → docker pause
-├── ResumeVM → docker unpause
-├── RebootVM → docker restart
-└── GetVMMetrics → docker stats
-```
-
-### Service Architecture
-
-```
-Single Container:
-├── systemd (PID 1)
-├── assetmanagerd.service (port 8083)
-├── billaged.service (port 8081)
-├── builderd.service (port 8082)
-├── metald.service (port 8080)
-└── Docker socket (mounted from host)
-```
-
-## Implementation Plan
-
-### Phase 1: Docker Backend Implementation
-
-1. **Create Docker Backend** (`go/deploy/metald/internal/backend/docker/`)
- - Implement `types.Backend` interface
- - Use Docker API client for container operations
- - Map VM configurations to Docker container specs
- - Handle port mapping and networking
- - Implement metrics collection via Docker stats API
-
-2. **Asset Integration**
- - Reuse existing asset management for Docker images
- - Map VM rootfs assets to Docker images
- - Integrate with builderd for image building
-
-3. **Configuration Updates**
- - Add Docker backend option to metald configuration
- - Update backend factory to support Docker backend
-
-### Phase 2: Container Environment Setup
-
-1. **Fedora-based Dockerfile**
- - Multi-stage build following LOCAL_DEPLOYMENT_GUIDE.md
- - Install development tools and dependencies
- - Build all 4 services using existing Makefiles
- - Install systemd and create service directories
-
-2. **systemd Service Configuration**
- - Modify existing systemd service files for container environment
- - Disable TLS for all services (development only)
- - Update service endpoints from HTTPS to HTTP
- - Create billaged user and set up directories
-
-3. **Docker Socket Integration**
- - Mount Docker socket into container
- - Configure appropriate permissions
- - Test Docker API access from within container
-
-### Phase 3: Service Integration
-
-1. **Environment Configuration**
- ```bash
- # Disable TLS for all services
- UNKEY_ASSETMANAGERD_TLS_MODE=disabled
- UNKEY_BILLAGED_TLS_MODE=disabled
- UNKEY_BUILDERD_TLS_MODE=disabled
- UNKEY_METALD_TLS_MODE=disabled
-
- # Configure Docker backend
- UNKEY_METALD_BACKEND=docker
-
- # Update service endpoints
- UNKEY_METALD_BILLING_ENDPOINT=http://localhost:8081
- UNKEY_METALD_ASSETMANAGER_ENDPOINT=http://localhost:8083
- UNKEY_ASSETMANAGERD_BUILDERD_ENDPOINT=http://localhost:8082
- UNKEY_BUILDERD_ASSETMANAGER_ENDPOINT=http://localhost:8083
- ```
-
-2. **Service Dependencies**
- - Configure systemd service ordering
- - Add health checks and startup coordination
- - Handle service failures gracefully
-
-3. **Docker Compose Integration**
- - Add deploy services to existing `deployment/docker-compose.yaml`
- - Configure networking and volume mounts
- - Set up development environment variables
-
-### Phase 4: Testing and Validation
-
-1. **Unit Tests**
- - Test Docker backend implementation
- - Verify VM-to-container operation mapping
- - Test asset management integration
-
-2. **Integration Tests**
- - Test complete service startup sequence
- - Verify inter-service communication
- - Test "VM" (container) lifecycle operations
-
-3. **End-to-End Tests**
- - Deploy sample applications as containers
- - Test port forwarding and networking
- - Verify metrics collection and billing
-
-## Directory Structure
-
-```
-go/deploy/
-├── metald/internal/backend/docker/
-│ ├── client.go # Docker backend implementation
-│ ├── types.go # Docker-specific types
-│ └── metrics.go # Docker metrics collection
-├── Dockerfile.dev # Development container
-├── docker-compose.dev.yml # Development compose file
-└── DOCKER_DEVELOPMENT_PLAN.md
-```
-
-## Key Implementation Details
-
-### Docker Backend Interface Mapping
-
-| Backend Method | Docker Operation | Notes |
-|---------------|------------------|-------|
-| `CreateVM()` | `docker create` | Convert VM config to container spec |
-| `BootVM()` | `docker start` | Start container and configure networking |
-| `DeleteVM()` | `docker rm -f` | Force remove container |
-| `ShutdownVM()` | `docker stop` | Graceful shutdown with timeout |
-| `PauseVM()` | `docker pause` | Pause container execution |
-| `ResumeVM()` | `docker unpause` | Resume paused container |
-| `RebootVM()` | `docker restart` | Restart container |
-| `GetVMInfo()` | `docker inspect` | Get container state and config |
-| `GetVMMetrics()` | `docker stats` | Get resource usage statistics |
-
-### Configuration Changes
-
-1. **TLS Disabled**: All services use `TLS_MODE=disabled`
-2. **HTTP Endpoints**: Change all inter-service URLs from HTTPS to HTTP
-3. **Docker Backend**: Set `UNKEY_METALD_BACKEND=docker`
-4. **Socket Mount**: Mount `/var/run/docker.sock:/var/run/docker.sock`
-
-### systemd Service Modifications
-
-- **assetmanagerd**: Run as root, local storage backend
-- **billaged**: Run as billaged user, stateless design
-- **builderd**: Run as root, Docker integration enabled
-- **metald**: Run as root, Docker backend enabled
-
-## Benefits
-
-1. **Production Parity**: Uses exact same systemd services and installation process
-2. **Simplified Setup**: No Firecracker, KVM, or SPIRE setup required
-3. **Familiar Tools**: Standard Docker tooling for debugging and monitoring
-4. **Fast Development**: Instant container startup vs VM boot times
-5. **Easy Migration**: Can switch back to separate containers easily
-
-## Usage
-
-### Development Workflow
-
-```bash
-# Build development container
-make docker-dev-build
-
-# Start all services
-make docker-dev-up
-
-# Create and boot a "VM" (actually a container)
-docker exec -it unkey-deploy-dev metald-cli create-and-boot
-
-# View logs
-docker logs -f unkey-deploy-dev
-journalctl -f # inside container
-
-# Stop services
-make docker-dev-down
-```
-
-### Integration with Existing Docker Compose
-
-The development container will integrate with the existing `deployment/docker-compose.yaml` to provide a complete development environment alongside the web services.
-
-## Timeline
-
-- **Phase 1**: Docker backend implementation (2-3 days)
-- **Phase 2**: Container environment setup (1-2 days)
-- **Phase 3**: Service integration (1-2 days)
-- **Phase 4**: Testing and validation (1-2 days)
-
-**Total Estimated Time**: 5-9 days
-
-## Success Criteria
-
-1. ✅ All 4 services start successfully in single container
-2. ✅ Services communicate with each other over HTTP
-3. ✅ metald can create, boot, and manage Docker containers as "VMs"
-4. ✅ Asset management works with Docker images
-5. ✅ Port forwarding and networking function correctly
-6. ✅ Metrics collection and billing operate properly
-7. ✅ Integration with existing docker-compose.yaml
-8. ✅ Development workflow is faster than VM-based approach
-
-## Future Enhancements
-
-1. **Hot Reload**: Automatic service restart on code changes
-2. **Debug Mode**: Enhanced logging and debugging capabilities
-3. **Performance Monitoring**: Built-in observability stack
-4. **Multi-tenancy**: Support for multiple development environments
-5. **CI/CD Integration**: Automated testing and deployment
\ No newline at end of file
diff --git a/go/deploy/Dockerfile.dev b/go/deploy/Dockerfile.dev
deleted file mode 100644
index 82e12067a7..0000000000
--- a/go/deploy/Dockerfile.dev
+++ /dev/null
@@ -1,213 +0,0 @@
-# Dockerfile.dev - Development environment for all Unkey deploy services
-# Based on LOCAL_DEPLOYMENT_GUIDE.md for maximum production parity
-
-# Install stage - install all dependencies once
-FROM fedora:42 AS install
-
-# Install all dependencies (dev tools + runtime deps + Docker CLI)
-RUN dnf install -y dnf-plugins-core && \
- dnf group install -y development-tools && \
- dnf install -y git make golang curl wget iptables-legacy \
- systemd systemd-devel procps-ng util-linux && \
- dnf config-manager addrepo --from-repofile=https://download.docker.com/linux/fedora/docker-ce.repo && \
- dnf install -y docker-ce-cli && \
- dnf clean all
-
-# Set up Go environment
-ENV GOPATH=/go
-ENV PATH=$PATH:/go/bin:/usr/local/go/bin
-
-# Base build stage with source code
-FROM install AS build-base
-
-# Copy source code
-COPY . /src/go
-WORKDIR /src/go
-
-# Protobuf files are already generated in go/proto/ - no need to generate them again
-
-# Build assetmanagerd
-FROM build-base AS build-assetmanagerd
-WORKDIR /src/go/assetmanagerd
-RUN go build -o assetmanagerd ./cmd/assetmanagerd
-
-# Build billaged
-FROM build-base AS build-billaged
-WORKDIR /src/go/deploy/billaged
-RUN go build -o billaged ./cmd/billaged
-
-# Build builderd
-FROM build-base AS build-builderd
-WORKDIR /src/go/deploy/builderd
-RUN go build -o builderd ./cmd/builderd
-
-# Build metald
-FROM build-base AS build-metald
-WORKDIR /src/go/deploy/metald
-RUN go build -o metald ./cmd/metald
-
-# Runtime stage - reuse install stage (all deps already installed)
-FROM install AS runtime
-
-# Create billaged user (following systemd service requirements)
-RUN useradd -r -s /bin/false billaged
-
-# Create required directories (following LOCAL_DEPLOYMENT_GUIDE.md structure)
-RUN mkdir -p /opt/assetmanagerd/{cache,data} && \
- mkdir -p /opt/billaged && \
- mkdir -p /opt/builderd/{scratch,rootfs,workspace,data} && \
- mkdir -p /opt/metald/{sockets,logs,assets} && \
- mkdir -p /srv/jailer && \
- mkdir -p /var/log/{assetmanagerd,billaged,builderd,metald} && \
- mkdir -p /opt/vm-assets
-
-# Set ownership for service directories
-RUN chown -R billaged:billaged /opt/billaged /var/log/billaged
-
-# Copy built binaries from respective build stages
-COPY --from=build-assetmanagerd /src/go/assetmanagerd/assetmanagerd /usr/local/bin/
-COPY --from=build-billaged /src/go/deploy/billaged/billaged /usr/local/bin/
-COPY --from=build-builderd /src/go/deploy/builderd/builderd /usr/local/bin/
-COPY --from=build-metald /src/go/deploy/metald/metald /usr/local/bin/
-
-
-# Make binaries executable
-RUN chmod +x /usr/local/bin/assetmanagerd* /usr/local/bin/billaged* /usr/local/bin/builderd* /usr/local/bin/metald*
-
-# Copy systemd service files for container environment
-COPY deploy/docker/systemd/ /etc/systemd/system/
-
-# Create development environment file
-RUN echo '# Development environment variables for all services' > /etc/default/unkey-deploy && \
- echo '# TLS disabled for development' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_ASSETMANAGERD_TLS_MODE=disabled' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_BILLAGED_TLS_MODE=disabled' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_BUILDERD_TLS_MODE=disabled' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_METALD_TLS_MODE=disabled' >> /etc/default/unkey-deploy && \
- echo '' >> /etc/default/unkey-deploy && \
- echo '# Configure Docker backend for metald' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_METALD_BACKEND=docker' >> /etc/default/unkey-deploy && \
- echo '' >> /etc/default/unkey-deploy && \
- echo '# Service endpoints (HTTP for development)' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_METALD_BILLING_ENDPOINT=http://localhost:8081' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_METALD_ASSETMANAGER_ENDPOINT=http://localhost:8083' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_ASSETMANAGERD_BUILDERD_ENDPOINT=http://localhost:8082' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_BUILDERD_ASSETMANAGER_ENDPOINT=http://localhost:8083' >> /etc/default/unkey-deploy && \
- echo '' >> /etc/default/unkey-deploy && \
- echo '# Service configuration' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_ASSETMANAGERD_PORT=8083' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_ASSETMANAGERD_ADDRESS=0.0.0.0' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_BILLAGED_PORT=8081' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_BILLAGED_ADDRESS=0.0.0.0' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_BUILDERD_PORT=8082' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_BUILDERD_ADDRESS=0.0.0.0' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_METALD_PORT=8080' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_METALD_ADDRESS=0.0.0.0' >> /etc/default/unkey-deploy && \
- echo '' >> /etc/default/unkey-deploy && \
- echo '# Storage configuration' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_ASSETMANAGERD_STORAGE_BACKEND=local' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_ASSETMANAGERD_LOCAL_STORAGE_PATH=/opt/builderd/rootfs' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_ASSETMANAGERD_DATABASE_PATH=/opt/assetmanagerd/assets.db' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_ASSETMANAGERD_CACHE_DIR=/opt/assetmanagerd/cache' >> /etc/default/unkey-deploy && \
- echo '' >> /etc/default/unkey-deploy && \
- echo '# Build configuration' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_BUILDERD_SCRATCH_DIR=/opt/builderd/scratch' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_BUILDERD_ROOTFS_OUTPUT_DIR=/opt/builderd/rootfs' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_BUILDERD_WORKSPACE_DIR=/opt/builderd/workspace' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_BUILDERD_DATABASE_DATA_DIR=/opt/builderd/data' >> /etc/default/unkey-deploy && \
- echo '' >> /etc/default/unkey-deploy && \
- echo '# Disable observability for development' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_ASSETMANAGERD_OTEL_ENABLED=false' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_BILLAGED_OTEL_ENABLED=false' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_BUILDERD_OTEL_ENABLED=false' >> /etc/default/unkey-deploy && \
- echo 'UNKEY_METALD_OTEL_ENABLED=false' >> /etc/default/unkey-deploy
-
-# Enable services (following LOCAL_DEPLOYMENT_GUIDE.md order)
-RUN systemctl enable assetmanagerd.service && \
- systemctl enable billaged.service && \
- systemctl enable builderd.service && \
- systemctl enable metald.service
-
-# Create entrypoint script
-RUN echo '#!/bin/bash' > /entrypoint.sh && \
- echo 'set -e' >> /entrypoint.sh && \
- echo '' >> /entrypoint.sh && \
- echo '# Source environment variables' >> /entrypoint.sh && \
- echo 'source /etc/default/unkey-deploy' >> /entrypoint.sh && \
- echo '' >> /entrypoint.sh && \
- echo '# Check Docker socket access' >> /entrypoint.sh && \
- echo 'if [ -S /var/run/docker.sock ]; then' >> /entrypoint.sh && \
- echo ' echo "Docker socket found, testing access..."' >> /entrypoint.sh && \
- echo ' if timeout 10 docker version > /dev/null 2>&1; then' >> /entrypoint.sh && \
- echo ' echo "Docker access confirmed"' >> /entrypoint.sh && \
- echo ' else' >> /entrypoint.sh && \
- echo ' echo "Warning: Docker socket exists but not accessible"' >> /entrypoint.sh && \
- echo ' fi' >> /entrypoint.sh && \
- echo 'else' >> /entrypoint.sh && \
- echo ' echo "Warning: Docker socket not found at /var/run/docker.sock"' >> /entrypoint.sh && \
- echo 'fi' >> /entrypoint.sh && \
- echo '' >> /entrypoint.sh && \
- echo '# Start services directly (systemd is problematic in containers)' >> /entrypoint.sh && \
- echo 'echo "Starting services directly..."' >> /entrypoint.sh && \
- echo '' >> /entrypoint.sh && \
- echo '# Start services directly in background with proper ordering' >> /entrypoint.sh && \
- echo 'echo "Starting assetmanagerd..."' >> /entrypoint.sh && \
- echo '/usr/local/bin/assetmanagerd &' >> /entrypoint.sh && \
- echo 'ASSETMANAGERD_PID=$!' >> /entrypoint.sh && \
- echo '' >> /entrypoint.sh && \
- echo 'echo "Starting billaged..."' >> /entrypoint.sh && \
- echo '/usr/local/bin/billaged &' >> /entrypoint.sh && \
- echo 'BILLAGED_PID=$!' >> /entrypoint.sh && \
- echo '' >> /entrypoint.sh && \
- echo 'echo "Starting builderd..."' >> /entrypoint.sh && \
- echo '/usr/local/bin/builderd &' >> /entrypoint.sh && \
- echo 'BUILDERD_PID=$!' >> /entrypoint.sh && \
- echo '' >> /entrypoint.sh && \
- echo '# Wait a moment for dependencies to start' >> /entrypoint.sh && \
- echo 'sleep 5' >> /entrypoint.sh && \
- echo '' >> /entrypoint.sh && \
- echo 'echo "Starting metald..."' >> /entrypoint.sh && \
- echo '/usr/local/bin/metald &' >> /entrypoint.sh && \
- echo 'METALD_PID=$!' >> /entrypoint.sh && \
- echo '' >> /entrypoint.sh && \
- echo 'echo "All services started. PIDs: assetmanagerd=$ASSETMANAGERD_PID billaged=$BILLAGED_PID builderd=$BUILDERD_PID metald=$METALD_PID"' >> /entrypoint.sh && \
- echo '' >> /entrypoint.sh && \
- echo '# Function to handle shutdown' >> /entrypoint.sh && \
- echo 'shutdown_services() {' >> /entrypoint.sh && \
- echo ' echo "Shutting down services..."' >> /entrypoint.sh && \
- echo ' kill $METALD_PID $BUILDERD_PID $BILLAGED_PID $ASSETMANAGERD_PID 2>/dev/null || true' >> /entrypoint.sh && \
- echo ' wait' >> /entrypoint.sh && \
- echo '}' >> /entrypoint.sh && \
- echo '' >> /entrypoint.sh && \
- echo '# Set up signal handler' >> /entrypoint.sh && \
- echo 'trap shutdown_services SIGTERM SIGINT' >> /entrypoint.sh && \
- echo '' >> /entrypoint.sh && \
- echo '# Wait for all background processes' >> /entrypoint.sh && \
- echo 'wait' >> /entrypoint.sh
-
-RUN chmod +x /entrypoint.sh
-
-# Expose service ports
-EXPOSE 8080 8081 8082 8083
-
-# Set up systemd as PID 1
-ENTRYPOINT ["/entrypoint.sh"]
-
-# Health check
-HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
- CMD curl -f http://localhost:8080/health || exit 1
-
-# Labels for identification
-LABEL org.unkey.component="deploy-services" \
- org.unkey.version="dev" \
- org.unkey.description="Development environment for all Unkey deploy services"
-
-# AIDEV-NOTE: This Dockerfile follows the LOCAL_DEPLOYMENT_GUIDE.md as closely as possible
-# Key features:
-# 1. Uses Fedora 42 (production parity)
-# 2. Multi-stage build with parallel service compilation for faster builds
-# 3. systemd as process manager
-# 4. All services built using existing Makefiles
-# 5. TLS disabled for development
-# 6. Docker backend configured for metald
-# 7. Proper directory structure and permissions
diff --git a/go/deploy/LOCAL_DEPLOYMENT_GUIDE.md b/go/deploy/LOCAL_DEPLOYMENT_GUIDE.md
deleted file mode 100644
index 8a2c48be48..0000000000
--- a/go/deploy/LOCAL_DEPLOYMENT_GUIDE.md
+++ /dev/null
@@ -1,221 +0,0 @@
-# Unkey Services Local Development Environment Setup
-
-This guide provides detailed instructions for setting up a complete Unkey development environment on Linux (Fedora 42 or Ubuntu 22.04+).
-
-## Prerequisites
-
-### 1. System Requirements Check
-
-Before beginning installation, verify your system meets all requirements:
-
-```bash
-cd /path/to/unkey/go/deploy
-./scripts/check-system-readiness.sh
-```
-
-This script verifies:
-- Operating system compatibility (Fedora 42+ or Ubuntu 24.04+)
-- Required tools: Go 1.24+, Make, Git, systemd
-- Container runtime: Docker or Podman
-- Virtualization: Firecracker/Cloud Hypervisor, KVM support
-- Port availability: 8080-8085, 9464-9467
-- Disk space: minimum 5GB free
-- Network connectivity
-
-### 2. Fix Any Missing Prerequisites
-
-If the readiness check reports missing dependencies:
-
-
-For Fedora
-
-```bash
-# Install development tools
-sudo dnf group install -y development-tools
-sudo dnf install -y git make curl wget iptables-legacy
-
-# Install buf for protobuf generation
-sudo ./scripts/install-buf.sh
-```
-
-#### Install Docker (Official Method)
-
-Follow the official Docker installation for Fedora:
-
-```bash
-# Remove old versions
-sudo dnf remove docker \
- docker-client \
- docker-client-latest \
- docker-common \
- docker-latest \
- docker-latest-logrotate \
- docker-logrotate \
- docker-selinux \
- docker-engine-selinux \
- docker-engine
-
-# Set up the Docker repository
-sudo dnf -y install dnf-plugins-core
-sudo dnf config-manager addrepo --from-repofile=https://download.docker.com/linux/fedora/docker-ce.repo
-
-# Install Docker Engine
-sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
-
-# Start and enable Docker
-sudo systemctl start docker
-sudo systemctl enable docker
-
-# Add your user to the docker group
-sudo usermod -aG docker $USER
-
-# Verify installation
-sudo docker run hello-world
-```
-
-#### Complete Setup
-
-```bash
-# Install KVM support
-sudo dnf install -y qemu-kvm
-sudo usermod -aG kvm $USER
-
-# Install Firecracker with jailer (required for metald)
-sudo ./scripts/install-firecracker.sh
-
-# Log out and back in for group changes to take effect
-```
-
-
-
-### 3. Firecracker Setup (REQUIRED)
-
-Metald uses an integrated jailer approach that handles VM isolation automatically:
-
-```bash
-# Install Firecracker with jailer (required for metald)
-sudo ./scripts/install-firecracker.sh
-```
-
-**Notes**:
-- Metald v0.2.0+ includes integrated jailer functionality
-- No manual jailer user or cgroup configuration needed
-- The system automatically handles VM isolation and security
-
-
-For Ubuntu
-
-```bash
-# Install development tools
-sudo apt update
-sudo apt install -y build-essential git make golang curl wget
-
-# Install buf for protobuf generation
-sudo ./scripts/install-buf.sh
-```
-
-#### Install Docker (Official Method)
-
-Follow the official Docker installation for Ubuntu:
-
-```bash
-# Remove old versions
-for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do
- sudo apt-get remove $pkg;
-done
-
-# Update package index and install prerequisites
-sudo apt-get update
-sudo apt-get install -y \
- ca-certificates \
- curl \
- gnupg \
- lsb-release
-
-# Add Docker's official GPG key
-sudo install -m 0755 -d /etc/apt/keyrings
-curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
-sudo chmod a+r /etc/apt/keyrings/docker.gpg
-
-# Set up the repository
-echo \
- "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
- "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
- sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
-
-# Install Docker Engine
-sudo apt-get update
-sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
-```
-
-#### Complete Setup
-
-```bash
-# Install KVM support
-sudo apt install -y qemu-kvm
-sudo usermod -aG kvm $USER
-
-# Install Firecracker
-sudo ./scripts/install-firecracker.sh
-```
-
-
-
-## Service Installation Order
-
-1. **Observability Stack** (optional but recommended) - Grafana, Loki, Tempo, Mimir
-2. **SPIRE** (REQUIRED) - Service identity and mTLS for secure inter-service communication
-3. **assetmanagerd** - VM asset management
-4. **billaged** - Usage billing service
-5. **builderd** - Container build service
-6. **metald** - VM management service (depends on assetmanagerd and billaged)
-
-## Quick Installation
-
-Run from the `/path/to/unkey/go/deploy` directory:
-
-### Step 1: Observability Stack
-
-```bash
-# Start observability stack
-make o11y
-```
-
-Access Grafana at `http://localhost:3000` (admin/admin)
-
-### Step 2: SPIRE Installation and Setup
-
-```bash
-# Install and start SPIRE with all services registered
-make -C spire install
-make -C spire service-start-server
-make -C spire register-agent
-make -C spire service-start-agent
-make -C spire register-services
-```
-
-### Step 3: Install Services/Clients
-
-```bash
-# Install all services
-make assetmanagerd-install
-make builderd-install
-make billaged-install
-make metald-install
-
-# Install all Clients
-make -C assetmanagerd/client install
-make -C builderd/client install
-make -C billaged/client install
-make -C metaldd/client install
-```
-
-### Step 4: Launch a MicroVM
-
-```bash
-metald-cli -docker-image=ghcr.io/unkeyed/best-api:v1.1.0 create-and-boot
-```
-
-You should see output similar to:
-
-```bash
diff --git a/go/deploy/Makefile b/go/deploy/Makefile
deleted file mode 100644
index 41756502ce..0000000000
--- a/go/deploy/Makefile
+++ /dev/null
@@ -1,151 +0,0 @@
-# Deploy Services Makefile
-# Calls metald and billaged Makefiles
-
-.DEFAULT_GOAL := help
-
-# Variables
-SERVICES = assetmanagerd billaged builderd metald
-METALD_DIR = metald
-BILLAGED_DIR = billaged
-ASSETMANAGERD_DIR = assetmanagerd
-BUILDERD_DIR = builderd
-SPIRE_DIR = spire
-
-# Individual service targets (pattern rules - must come before other targets)
-assetmanagerd-%: ## Run any assetmanagerd target (e.g., make assetmanagerd-build)
- $(MAKE) -C $(ASSETMANAGERD_DIR) $*
-
-billaged-%: ## Run any billaged target (e.g., make billaged-build)
- $(MAKE) -C $(BILLAGED_DIR) $*
-
-builderd-%: ## Run any builderd target (e.g., make builderd-build)
- $(MAKE) -C $(BUILDERD_DIR) $*
-
-metald-%: ## Run any metald target (e.g., make metald-build)
- $(MAKE) -C $(METALD_DIR) $*
-
-spire-%: ## Run any SPIRE target (e.g., make spire-list-entries)
- $(MAKE) -C $(SPIRE_DIR) $*
-
-.PHONY: check
-check: fmt vet lint test ## Run all checks (fmt, vet, lint with proto, test)
-
-.PHONY: clean-all
-clean-all: ## Complete uninstall and cleanup of all services and data
- @echo "==================================="
- @echo "Complete System Cleanup"
- @echo "==================================="
- @echo "This will:"
- @echo "- Stop all running services"
- @echo "- Uninstall all service binaries"
- @echo "- Remove all service users"
- @echo "- Delete all service data"
- @echo "- Remove SPIRE installation"
- @echo "- Stop observability stack"
- @echo ""
- @read -p "Are you sure you want to completely clean the system? [y/N] " -n 1 -r; \
- echo ""; \
- if [[ $$REPLY =~ ^[Yy]$$ ]]; then \
- echo "Starting complete cleanup..."; \
- $(MAKE) clean-all-force; \
- else \
- echo "Cleanup cancelled."; \
- fi
-
-.PHONY: clean-all-force
-clean-all-force: ## Force complete cleanup without confirmation
- @for service in $(SERVICES); do \
- sudo systemctl stop $$service 2>/dev/null || true; \
- done
- @sudo systemctl stop spire-agent 2>/dev/null || true
- @sudo systemctl stop spire-server 2>/dev/null || true
- @$(MAKE) o11y-stop 2>/dev/null || true
- @for service in $(SERVICES); do \
- $(MAKE) -C $$service uninstall 2>/dev/null || true; \
- done
- @$(MAKE) -C $(SPIRE_DIR) uninstall 2>/dev/null || true
- @for user in $(SERVICES); do \
- if [ "$$user" != "metald" ]; then \
- sudo userdel -r $$user 2>/dev/null || true; \
- fi; \
- done
- @for service in $(SERVICES); do \
- sudo rm -rf /opt/$$service; \
- done
- @sudo rm -rf /opt/vm-assets
- @sudo rm -rf /opt/spire /var/lib/spire /etc/spire /run/spire
- @sudo systemctl daemon-reload
- @for service in $(SERVICES); do \
- $(MAKE) -C $$service clean 2>/dev/null || true; \
- done
- @$(MAKE) -C $(SPIRE_DIR) clean 2>/dev/null || true
- @echo "✓ System cleanup complete"
-
-.PHONY: help
-help: ## Show this help message
- @echo ""
- @echo "Deploy Services - Available targets:"
- @echo ""
- @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
-
-
-.PHONY: install
-install: builderd-install metald-install billaged-install
-
-.PHONY: lint
-lint: builderd-lint metald-lint billaged-lint
-
-.PHONY: o11y
-o11y: ## Start observability stack (Grafana LGTM)
- @docker run -d \
- --name unkey-o11y-lgtm \
- -p 3000:3000 \
- -p 4317:4317 \
- -p 4318:4318 \
- -p 9090:9090 \
- -p 3100:3100 \
- -e GF_SERVER_HTTP_ADDR=0.0.0.0 \
- grafana/otel-lgtm:latest >/dev/null
- @echo "✓ Observability stack started"
- @echo "Grafana UI: http://0.0.0.0:3000 (admin/admin)"
- @echo "OTLP gRPC: 0.0.0.0:4317 | OTLP HTTP: 0.0.0.0:4318"
- @echo "Prometheus: http://0.0.0.0:9090 | Loki: http://0.0.0.0:3100"
-
-.PHONY: o11y-logs
-o11y-logs: ## Show observability stack logs
- @docker logs -f unkey-o11y-lgtm
-
-.PHONY: o11y-restart
-o11y-restart: ## Restart observability stack
- @$(MAKE) o11y-stop
- @sleep 2
- @$(MAKE) o11y
-
-.PHONY: o11y-stop
-o11y-stop: ## Stop observability stack
- @docker stop unkey-o11y-lgtm 2>/dev/null || true
- @docker rm unkey-o11y-lgtm 2>/dev/null || true
- @echo "✓ Observability stack stopped"
-
-.PHONY: spire-start
-spire-start: ## Start SPIRE services and register Unkey services
- $(MAKE) -C $(SPIRE_DIR) service-start-server
- @sleep 3
- $(MAKE) -C $(SPIRE_DIR) bootstrap-agent
- $(MAKE) -C $(SPIRE_DIR) register-agent
- $(MAKE) -C $(SPIRE_DIR) service-start-agent
- @sleep 2
- $(MAKE) -C $(SPIRE_DIR) register-services
-
-.PHONY: spire-uninstall
-spire-uninstall: ## Uninstall SPIRE server and agent
- $(MAKE) -C $(SPIRE_DIR) uninstall
-
-.PHONY: test
-test: builderd-test metald-test billaged-test
-
-.PHONY: uninstall
-uninstall: builderd-uninstall metald-uninstall billaged-uninstall
-
-.PHONY: vet
-vet: builderd-vet metald-vet billaged-vet
diff --git a/go/deploy/README.dev.md b/go/deploy/README.dev.md
deleted file mode 100644
index d1485cd487..0000000000
--- a/go/deploy/README.dev.md
+++ /dev/null
@@ -1,256 +0,0 @@
-# Unkey Deploy Services - Development Environment
-
-This directory contains a containerized development environment for all Unkey deploy services. It provides a single Docker container running all four services (`assetmanagerd`, `billaged`, `builderd`, `metald`) with systemd as the process manager.
-
-## Quick Start
-
-```bash
-# Navigate to deployment directory
-cd deployment
-
-# Build and start all services (including deploy services)
-docker-compose up -d
-
-# Check status
-docker-compose ps
-
-# View deploy services logs
-docker-compose logs -f metald-aio
-
-# Open shell in deploy container
-docker exec -it metald-aio /bin/bash
-
-# Test services (from host)
-metald-cli -tls-mode=disabled -server=http://localhost:8090 list
-
-# Test services (from inside container)
-docker exec -it metald-aio metald-cli -tls-mode=disabled -server=http://localhost:8080 list
-```
-
-## Architecture
-
-### Single Container Design
-- **Base Image**: Fedora 42 (production parity)
-- **Process Manager**: systemd (production parity)
-- **Services**: All 4 deploy services in one container
-- **Backend**: Docker backend for metald (containers instead of VMs)
-
-### Service Ports
-- `metald`: 8090 (exposed), 8080 (internal)
-- `billaged`: 8081 (internal only)
-- `builderd`: 8082 (internal only)
-- `assetmanagerd`: 8083 (internal only)
-
-### Key Features
-- **TLS Disabled**: All services use `TLS_MODE=disabled` for development
-- **Docker Backend**: metald creates Docker containers instead of VMs
-- **Production Parity**: Uses same systemd services and build process
-- **Simplified Setup**: No Firecracker/KVM/SPIRE required
-
-## Files and Structure
-
-```
-go/deploy/
-├── Dockerfile.dev # Development container
-├── docker-compose.dev.yml # Docker Compose configuration
-├── Makefile.dev # Development commands
-├── README.dev.md # This file
-└── docker/
- └── systemd/ # systemd service files
- ├── assetmanagerd.service
- ├── billaged.service
- ├── builderd.service
- └── metald.service
-```
-
-## Usage
-
-### Starting Services
-
-```bash
-# Build container (first time or after changes)
-make -f Makefile.dev build
-
-# Start all services
-make -f Makefile.dev up
-
-# Check if services are running
-make -f Makefile.dev status
-```
-
-### Working with Services
-
-```bash
-# View service logs
-make -f Makefile.dev logs
-
-# Open shell in container
-make -f Makefile.dev shell
-
-# Inside container, check systemd status
-systemctl status assetmanagerd.service
-systemctl status billaged.service
-systemctl status builderd.service
-systemctl status metald.service
-
-# View journal logs
-journalctl -f -u metald.service
-```
-
-### Testing Services
-
-```bash
-# Run basic health checks
-make -f Makefile.dev test
-
-# Create a test VM (Docker container)
-make -f Makefile.dev test-vm
-
-# Inside container, use CLI tools
-assetmanagerd-cli -tls-mode=disabled -server=http://localhost:8083 list
-billaged-cli -tls-mode=disabled -server=http://localhost:8081 health
-builderd-cli -tls-mode=disabled -server=http://localhost:8082 health
-metald-cli -tls-mode=disabled -server=http://localhost:8080 list
-```
-
-### Creating VMs (Docker Containers)
-
-```bash
-# Inside container or using docker exec
-metald-cli -tls-mode=disabled -server=http://localhost:8080 \
- -docker-image=alpine:latest create-and-boot
-
-# List VMs
-metald-cli -tls-mode=disabled -server=http://localhost:8080 list
-
-# Get VM info
-metald-cli -tls-mode=disabled -server=http://localhost:8080 info
-```
-
-## Configuration
-
-### Environment Variables
-
-The container uses `/etc/default/unkey-deploy` for configuration:
-
-```bash
-# TLS disabled for development
-UNKEY_ASSETMANAGERD_TLS_MODE=disabled
-UNKEY_BILLAGED_TLS_MODE=disabled
-UNKEY_BUILDERD_TLS_MODE=disabled
-UNKEY_METALD_TLS_MODE=disabled
-
-# Docker backend for metald
-UNKEY_METALD_BACKEND=docker
-
-# Service endpoints (HTTP)
-UNKEY_METALD_BILLING_ENDPOINT=http://localhost:8081
-UNKEY_METALD_ASSETMANAGER_ENDPOINT=http://localhost:8083
-UNKEY_ASSETMANAGERD_BUILDERD_ENDPOINT=http://localhost:8082
-UNKEY_BUILDERD_ASSETMANAGER_ENDPOINT=http://localhost:8083
-```
-
-### Docker Backend Configuration
-
-metald uses Docker backend with these settings:
-- **Docker Host**: `unix:///var/run/docker.sock`
-- **Network**: `bridge`
-- **Port Range**: 30000-40000
-- **Container Prefix**: `unkey-vm-`
-
-## Troubleshooting
-
-### Services Not Starting
-
-```bash
-# Check systemd status
-make -f Makefile.dev shell
-systemctl status assetmanagerd.service
-systemctl status billaged.service
-systemctl status builderd.service
-systemctl status metald.service
-
-# Check logs
-journalctl -u metald.service
-journalctl -u assetmanagerd.service
-```
-
-### Docker Access Issues
-
-```bash
-# Check Docker socket access
-docker exec unkey-deploy-dev ls -la /var/run/docker.sock
-docker exec unkey-deploy-dev docker version
-
-# Ensure Docker socket is mounted
-docker-compose -f docker-compose.dev.yml config
-```
-
-### Port Conflicts
-
-```bash
-# Check if ports are available
-netstat -tlnp | grep -E ':(8080|8081|8082|8083)'
-
-# Use different ports if needed
-docker-compose -f docker-compose.dev.yml down
-# Edit docker-compose.dev.yml ports section
-docker-compose -f docker-compose.dev.yml up -d
-```
-
-## Development Workflow
-
-### Making Changes
-
-1. Edit service code
-2. Rebuild container: `make -f Makefile.dev build`
-3. Restart services: `make -f Makefile.dev down up`
-4. Test changes: `make -f Makefile.dev test`
-
-### Debugging
-
-```bash
-# Enter container
-make -f Makefile.dev shell
-
-# Check service logs
-journalctl -f -u metald.service
-
-# Test individual services
-assetmanagerd-cli -tls-mode=disabled -server=http://localhost:8083 list
-metald-cli -tls-mode=disabled -server=http://localhost:8080 list
-
-# Check Docker containers created by metald
-docker ps --filter "label=unkey.vm.created_by=metald"
-```
-
-## Integration with Main Docker Compose
-
-This development environment is designed to work alongside the main `deployment/docker-compose.yaml`. The services will be available at:
-
-- **Web services**: Ports 3000, 8787, etc. (from main compose)
-- **Deploy services**: Ports 8080-8083 (from this dev environment)
-
-Both environments share the same Docker network for seamless integration.
-
-## Benefits
-
-1. **Production Parity**: Uses Fedora + systemd + same build process
-2. **Simplified Development**: No complex VM setup required
-3. **Fast Iteration**: Instant container startup vs VM boot times
-4. **Familiar Tools**: Standard Docker debugging and monitoring
-5. **Easy Integration**: Works with existing docker-compose setup
-
-## Limitations
-
-1. **Single Container**: All services in one container (less isolation)
-2. **Development Only**: TLS disabled, simplified configuration
-3. **Docker Dependency**: Requires Docker socket access
-4. **systemd Overhead**: Requires privileged container for systemd
-
-## Next Steps
-
-- [ ] Add hot reload for development
-- [ ] Integrate with VS Code dev containers
-- [ ] Add performance monitoring
-- [ ] Create CI/CD pipeline for testing
\ No newline at end of file
diff --git a/go/deploy/cleanup-unkey-deploy.sh b/go/deploy/cleanup-unkey-deploy.sh
deleted file mode 100755
index a39c78b44f..0000000000
--- a/go/deploy/cleanup-unkey-deploy.sh
+++ /dev/null
@@ -1,277 +0,0 @@
-#!/bin/bash
-# Cleanup script for Unkey Deploy services and components
-# This script removes all installed services, configurations, and data
-
-set -euo pipefail
-
-# Color codes for output
-GREEN='\033[0;32m'
-RED='\033[0;31m'
-YELLOW='\033[1;33m'
-NC='\033[0m' # No Color
-
-echo "============================================="
-echo "Unkey Deploy Complete Cleanup Script"
-echo "============================================="
-echo ""
-echo -e "${YELLOW}WARNING: This will remove all Unkey Deploy services and data!${NC}"
-echo "Services to be removed:"
-echo " - metald"
-echo " - builderd"
-echo " - assetmanagerd"
-echo " - SPIRE Server and Agent"
-echo " - All VM bridges and network configurations"
-echo " - All data directories"
-echo ""
-read -p "Are you sure you want to continue? [y/N] " -n 1 -r
-echo
-if [[ ! $REPLY =~ ^[Yy]$ ]]; then
- echo "Cleanup cancelled."
- exit 0
-fi
-
-# Check if running as root
-if [ "$EUID" -ne 0 ]; then
- echo -e "${RED}Error: This script must be run as root${NC}"
- exit 1
-fi
-
-echo ""
-echo "Starting cleanup process..."
-
-# Function to safely stop and disable a service
-stop_and_disable_service() {
- local service=$1
- if systemctl list-unit-files | grep -q "^${service}.service"; then
- echo "Stopping and disabling ${service}..."
- systemctl stop "${service}" 2>/dev/null || true
- systemctl disable "${service}" 2>/dev/null || true
- fi
-}
-
-# Function to remove systemd service files
-remove_service_files() {
- local service=$1
- echo "Removing ${service} service files..."
- rm -f "/etc/systemd/system/${service}.service"
- rm -f "/etc/systemd/system/${service}@.service"
- rm -f "/usr/lib/systemd/system/${service}.service"
- rm -f "/usr/lib/systemd/system/${service}@.service"
-}
-
-# 1. Stop all services
-echo ""
-echo "=== Stopping Services ==="
-stop_and_disable_service "metald"
-stop_and_disable_service "metald-bridge-8"
-stop_and_disable_service "metald-bridge-32"
-stop_and_disable_service "builderd"
-stop_and_disable_service "assetmanagerd"
-stop_and_disable_service "spire-agent"
-stop_and_disable_service "spire-server"
-
-# 2. Kill any remaining Firecracker processes
-echo ""
-echo "=== Cleaning up Firecracker VMs ==="
-pkill -9 firecracker 2>/dev/null || true
-pkill -9 jailer 2>/dev/null || true
-
-# Clean up any remaining VM tap interfaces
-for tap in $(ip link show | grep -o 'tap[0-9a-f_-]*' | sort -u); do
- echo "Removing tap interface: $tap"
- ip link delete "$tap" 2>/dev/null || true
-done
-
-# Clean up veth interfaces
-for veth in $(ip link show | grep -o 'vh_[0-9a-f]*' | sort -u); do
- echo "Removing veth interface: $veth"
- ip link delete "$veth" 2>/dev/null || true
-done
-
-# 3. Remove network bridges
-echo ""
-echo "=== Removing Network Bridges ==="
-for i in {0..31}; do
- bridge="br-tenant-$i"
- if ip link show "$bridge" &>/dev/null; then
- echo "Removing bridge: $bridge"
- ip link set "$bridge" down 2>/dev/null || true
- ip link delete "$bridge" 2>/dev/null || true
- fi
-done
-
-# Remove systemd-networkd configurations
-echo "Removing network configurations..."
-rm -rf /etc/systemd/network/10-br-tenant-*.net{dev,work}
-rm -rf /run/systemd/network/10-br-tenant-*.net{dev,work}
-
-# 4. Remove binaries
-echo ""
-echo "=== Removing Binaries ==="
-binaries=(
- "/usr/local/bin/metald"
- "/usr/local/bin/metald-cli"
- "/usr/local/bin/metald-init"
- "/usr/local/bin/builderd"
- "/usr/local/bin/builderd-cli"
- "/usr/local/bin/assetmanagerd"
- "/usr/local/bin/assetmanagerd-cli"
- "/usr/local/bin/firecracker"
- "/usr/local/bin/jailer"
- "/opt/spire/bin/spire-server"
- "/opt/spire/bin/spire-agent"
- "/opt/spire/bin/spire"
-)
-
-for binary in "${binaries[@]}"; do
- if [ -f "$binary" ]; then
- echo "Removing: $binary"
- rm -f "$binary"
- fi
-done
-
-# 5. Remove service files
-echo ""
-echo "=== Removing Service Files ==="
-remove_service_files "metald"
-remove_service_files "metald-bridge-8"
-remove_service_files "metald-bridge-32"
-remove_service_files "builderd"
-remove_service_files "assetmanagerd"
-remove_service_files "spire-server"
-remove_service_files "spire-agent"
-
-# 6. Remove configuration files
-echo ""
-echo "=== Removing Configuration Files ==="
-rm -rf /etc/metald
-rm -rf /etc/builderd
-rm -rf /etc/assetmanagerd
-rm -rf /etc/spire
-rm -rf /etc/default/unkey-deploy
-rm -f /etc/default/metald
-rm -f /etc/default/builderd
-rm -f /etc/default/assetmanagerd
-
-# 7. Remove data directories
-echo ""
-echo "=== Removing Data Directories ==="
-echo -e "${YELLOW}Warning: This will delete all VM images and assets!${NC}"
-read -p "Remove all data directories? [y/N] " -n 1 -r
-echo
-if [[ $REPLY =~ ^[Yy]$ ]]; then
- # Service data directories
- rm -rf /opt/metald
- rm -rf /opt/builderd
- rm -rf /opt/assetmanagerd
- rm -rf /opt/vm-assets
- rm -rf /opt/spire
-
- # Runtime directories
- rm -rf /var/lib/metald
- rm -rf /var/lib/builderd
- rm -rf /var/lib/assetmanagerd
- rm -rf /var/lib/spire
- rm -rf /var/lib/firecracker
-
- # Jailer directories
- rm -rf /srv/jailer
- rm -rf /var/run/firecracker
-
- # Log directories
- rm -rf /var/log/metald
- rm -rf /var/log/builderd
- rm -rf /var/log/assetmanagerd
- rm -rf /var/log/spire
-
- echo -e "${GREEN}✓${NC} Data directories removed"
-else
- echo "Skipping data directory removal"
-fi
-
-# 8. Remove users and groups
-echo ""
-echo "=== Removing Service Users ==="
-for user in metald builderd assetmanagerd firecracker spire; do
- if id -u "$user" &>/dev/null; then
- echo "Removing user: $user"
- userdel "$user" 2>/dev/null || true
- fi
- if getent group "$user" &>/dev/null; then
- echo "Removing group: $user"
- groupdel "$user" 2>/dev/null || true
- fi
-done
-
-# 9. Clean up iptables rules
-echo ""
-echo "=== Cleaning up iptables rules ==="
-# Remove FORWARD rules for VM bridges
-for i in {0..31}; do
- iptables -D FORWARD -i br-tenant-$i -j ACCEPT 2>/dev/null || true
- iptables -D FORWARD -o br-tenant-$i -j ACCEPT 2>/dev/null || true
-done
-
-# Remove NAT rules
-iptables -t nat -F 2>/dev/null || true
-iptables -t nat -X 2>/dev/null || true
-
-# 10. Clean up cgroups
-echo ""
-echo "=== Cleaning up cgroups ==="
-if [ -d /sys/fs/cgroup/firecracker ]; then
- rmdir /sys/fs/cgroup/firecracker 2>/dev/null || true
-fi
-
-# Clean up any VM-specific cgroups
-for cg in $(find /sys/fs/cgroup -name "*firecracker*" -type d 2>/dev/null); do
- rmdir "$cg" 2>/dev/null || true
-done
-
-# 11. Reload systemd
-echo ""
-echo "=== Reloading systemd ==="
-systemctl daemon-reload
-systemctl restart systemd-networkd
-
-# 12. Clean up any remaining artifacts
-echo ""
-echo "=== Final cleanup ==="
-# Remove any temporary VM files
-rm -rf /tmp/firecracker-*
-rm -rf /tmp/vm-*
-rm -f /tmp/*-vm-console.log
-
-# Remove any socket files
-rm -f /var/run/firecracker.sock*
-rm -f /var/run/metald.sock
-rm -f /var/run/builderd.sock
-rm -f /var/run/assetmanagerd.sock
-rm -f /var/lib/spire/agent/agent.sock
-
-# Clean up any remaining systemd runtime directories
-rm -rf /run/systemd/system/metald.service.d
-rm -rf /run/systemd/system/builderd.service.d
-rm -rf /run/systemd/system/assetmanagerd.service.d
-
-echo ""
-echo "============================================="
-echo -e "${GREEN}✓ Cleanup completed successfully!${NC}"
-echo "============================================="
-echo ""
-echo "The following have been removed:"
-echo " - All Unkey Deploy services and binaries"
-echo " - All network bridges and configurations"
-echo " - All service users and groups"
-echo " - All configuration files"
-if [[ $REPLY =~ ^[Yy]$ ]]; then
- echo " - All data directories and VM assets"
-fi
-echo ""
-echo "System has been restored to pre-installation state."
-echo ""
-echo "Note: If you want to reinstall, you'll need to:"
-echo " 1. Reinstall SPIRE Server and Agent"
-echo " 2. Reinstall and configure all services"
-echo " 3. Re-run network bridge setup"
-echo " 4. Re-download base VM assets"
diff --git a/go/deploy/ctrl/Makefile b/go/deploy/ctrl/Makefile
deleted file mode 100644
index 1565ce7d33..0000000000
--- a/go/deploy/ctrl/Makefile
+++ /dev/null
@@ -1,108 +0,0 @@
-# Ctrl Control Plane Service - Docker Makefile
-
-.DEFAULT_GOAL := help
-
-# Variables
-SERVICE_NAME := ctrl
-IMAGE_NAME := unkey/$(SERVICE_NAME)
-TRUST_DOMAIN := development.unkey.app
-SPIRE_SOCKET := /var/lib/spire/server/server.sock
-SPIRE_DIR := /opt/spire
-
-# Colors for output
-GREEN := \033[0;32m
-YELLOW := \033[1;33m
-RED := \033[0;31m
-BLUE := \033[0;34m
-CYAN := \033[36m
-NC := \033[0m
-RESET := \033[0m
-
-.PHONY: help build run stop logs spire-register clean compose-up compose-down
-
-help: ## Display this help message
- @echo ""
- @echo "$(CYAN)Ctrl Control Plane Service - Docker Management$(RESET)"
- @echo ""
- @awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make $(CYAN)$(RESET)\n"} /^[a-zA-Z_-]+:.*?##/ { printf " $(CYAN)%-20s$(RESET) %s\n", $$1, $$2 }' $(MAKEFILE_LIST)
- @echo ""
-
-build: ## Build Docker image
- @echo "$(BLUE)Building Docker image...$(NC)"
- @docker build -t $(IMAGE_NAME):latest --build-arg VERSION=latest ../../
- @echo "$(GREEN)✓ Image built: $(IMAGE_NAME):latest$(NC)"
-
-run: ## Run Docker container
- @echo "$(BLUE)Starting ctrl container...$(NC)"
- @docker run -d --name unkey-$(SERVICE_NAME) \
- -p 8084:8084 \
- -v /var/lib/spire/agent/agent.sock:/var/lib/spire/agent/agent.sock \
- -e UNKEY_DATABASE_PRIMARY="$${UNKEY_DATABASE_PRIMARY}" \
- -e UNKEY_DATABASE_HYDRA="$${UNKEY_DATABASE_HYDRA}" \
- -e UNKEY_METALD_ADDRESS="$${UNKEY_METALD_ADDRESS:-https://host.docker.internal:8080}" \
- -e UNKEY_HTTP_PORT="8084" \
- -e UNKEY_PLATFORM="docker" \
- -e UNKEY_REGION="docker" \
- -e UNKEY_OTEL="true" \
- -e UNKEY_SPIFFE_SOCKET_PATH="/var/lib/spire/agent/agent.sock" \
- $(IMAGE_NAME):latest run ctrl
- @echo "$(GREEN)✓ Container started: unkey-$(SERVICE_NAME)$(NC)"
- @echo "$(YELLOW)Next: Run 'make spire-register' to register with SPIRE$(NC)"
-
-stop: ## Stop and remove Docker container
- @echo "$(BLUE)Stopping ctrl container...$(NC)"
- @docker stop unkey-$(SERVICE_NAME) 2>/dev/null || true
- @docker rm unkey-$(SERVICE_NAME) 2>/dev/null || true
- @echo "$(GREEN)✓ Container stopped$(NC)"
-
-logs: ## Follow container logs
- @docker logs -f unkey-$(SERVICE_NAME)
-
-spire-register: ## Register Docker container with SPIRE (stable selectors)
- @echo "$(BLUE)Registering ctrl container with SPIRE...$(NC)"
- @echo "Trust Domain: $(YELLOW)$(TRUST_DOMAIN)$(NC)"
- @if [ ! -S "$(SPIRE_SOCKET)" ]; then \
- echo "$(RED)Error: SPIRE server socket not available at $(SPIRE_SOCKET)$(NC)"; \
- exit 1; \
- fi
- @PARENT_ID="spiffe://$(TRUST_DOMAIN)/agent/node1"; \
- SPIFFE_ID="spiffe://$(TRUST_DOMAIN)/service/$(SERVICE_NAME)"; \
- echo "$(BLUE)Cleaning up any existing registrations...$(NC)"; \
- sudo $(SPIRE_DIR)/bin/spire-server entry show -socketPath "$(SPIRE_SOCKET)" -spiffeID "$$SPIFFE_ID" 2>/dev/null | grep "Entry ID" | awk '{print $$4}' | while read entry_id; do \
- if [ ! -z "$$entry_id" ]; then \
- echo "$(YELLOW)Deleting existing entry: $$entry_id$(NC)"; \
- sudo $(SPIRE_DIR)/bin/spire-server entry delete -socketPath "$(SPIRE_SOCKET)" -entryID "$$entry_id" 2>/dev/null || true; \
- fi; \
- done; \
- echo "$(BLUE)Creating new stable registration...$(NC)"; \
- sudo $(SPIRE_DIR)/bin/spire-server entry create \
- -socketPath "$(SPIRE_SOCKET)" \
- -parentID "$$PARENT_ID" \
- -spiffeID "$$SPIFFE_ID" \
- -selector "docker:label:com.docker.compose.service:$(SERVICE_NAME)" \
- -x509SVIDTTL 3600 \
- && echo "$(GREEN)✓ $(SERVICE_NAME) registered with SPIRE using stable selectors$(NC)" \
- || (echo "$(RED)✗ Failed to register $(SERVICE_NAME)$(NC)" && exit 1)
-
-clean: ## Remove Docker image
- @echo "$(BLUE)Cleaning up...$(NC)"
- @docker rmi $(IMAGE_NAME):latest 2>/dev/null || true
- @echo "$(GREEN)✓ Cleanup complete$(NC)"
-
-# Combined targets
-up: build run spire-register ## Build, run, and register with SPIRE
- @echo "$(GREEN)✓ Ctrl service is up and running!$(NC)"
-
-down: stop ## Stop container
- @echo "$(GREEN)✓ Ctrl service stopped$(NC)"
-
-compose-up: ## Build and start with docker-compose
- @echo "$(BLUE)Building and starting with docker-compose...$(NC)"
- @docker compose up -d --build
- @echo "$(GREEN)✓ Container built and started with docker-compose$(NC)"
- @echo "$(YELLOW)Next: Run 'make spire-register' to register with SPIRE$(NC)"
-
-compose-down: ## Stop docker-compose
- @echo "$(BLUE)Stopping docker-compose...$(NC)"
- @docker compose down
- @echo "$(GREEN)✓ Docker-compose stopped$(NC)"
diff --git a/go/deploy/ctrl/docker-compose.yml b/go/deploy/ctrl/docker-compose.yml
deleted file mode 100644
index 2bd1fb1ea1..0000000000
--- a/go/deploy/ctrl/docker-compose.yml
+++ /dev/null
@@ -1,74 +0,0 @@
-services:
- mysql:
- container_name: ctrl-mysql
- build:
- context: ../../../
- dockerfile: deployment/Dockerfile.mysql
- restart: always
- environment:
- MYSQL_ROOT_PASSWORD: root
- MYSQL_DATABASE: unkey
- MYSQL_USER: unkey
- MYSQL_PASSWORD: password
- command: ["--max_connections=1000"]
- ports:
- - 3306:3306
- volumes:
- - mysql:/var/lib/mysql
- - ../../../deployment/init-databases.sql:/docker-entrypoint-initdb.d/init-databases.sql
-
- ctrl:
- build:
- context: ../../
- dockerfile: Dockerfile
- args:
- VERSION: "latest"
- container_name: unkey-ctrl
- ports:
- - "8084:8084"
- depends_on:
- - mysql
- extra_hosts:
- - "host.docker.internal:host-gateway"
- environment:
- # Database configuration
- UNKEY_DATABASE_PRIMARY: ${UNKEY_DATABASE_PRIMARY}
- UNKEY_DATABASE_HYDRA: "unkey:password@tcp(mysql:3306)/hydra?parseTime=true"
-
- # Control plane configuration
- UNKEY_HTTP_PORT: "8084"
- UNKEY_METALD_ADDRESS: ${UNKEY_METALD_ADDRESS:-https://host.docker.internal:8080}
-
- # Instance configuration
- UNKEY_PLATFORM: "docker"
- UNKEY_REGION: "docker"
- UNKEY_OTEL: "true"
- UNKEY_SPIFFE_SOCKET_PATH: "/var/lib/spire/agent/agent.sock"
-
- volumes:
- # Mount SPIFFE agent socket from host
- - /var/lib/spire/agent/agent.sock:/var/lib/spire/agent/agent.sock
-
- restart: unless-stopped
-
- # Override the entrypoint to run ctrl command
- command: ["run", "ctrl"]
-
- # Health check
- healthcheck:
- test:
- [
- "CMD",
- "wget",
- "--no-verbose",
- "--tries=1",
- "--spider",
- "http://localhost:8084/_/health",
- ]
- interval: 30s
- timeout: 5s
- retries: 3
- start_period: 10s
-
-volumes:
- mysql:
diff --git a/go/deploy/docker/systemd/assetmanagerd.service b/go/deploy/docker/systemd/assetmanagerd.service
deleted file mode 100644
index 4d20e011ab..0000000000
--- a/go/deploy/docker/systemd/assetmanagerd.service
+++ /dev/null
@@ -1,56 +0,0 @@
-[Unit]
-Description=AssetManagerd VM Asset Management Service (Development)
-Documentation=https://github.com/unkeyed/unkey/go/assetmanagerd
-After=network.target
-Wants=network.target
-
-[Service]
-Type=simple
-# Run as root for development simplicity
-User=root
-Group=root
-# Load development environment variables
-EnvironmentFile=/etc/default/unkey-deploy
-# Service will start in the required directories
-ExecStart=/usr/local/bin/assetmanagerd
-Restart=always
-RestartSec=5
-StandardOutput=journal
-StandardError=journal
-SyslogIdentifier=assetmanagerd
-
-# Development-specific environment overrides
-Environment=UNKEY_ASSETMANAGERD_TLS_MODE=disabled
-Environment=UNKEY_ASSETMANAGERD_OTEL_ENABLED=false
-Environment=UNKEY_ASSETMANAGERD_PORT=8083
-Environment=UNKEY_ASSETMANAGERD_ADDRESS=0.0.0.0
-
-# Storage configuration for development
-Environment=UNKEY_ASSETMANAGERD_STORAGE_BACKEND=local
-Environment=UNKEY_ASSETMANAGERD_LOCAL_STORAGE_PATH=/opt/builderd/rootfs
-Environment=UNKEY_ASSETMANAGERD_DATABASE_PATH=/opt/assetmanagerd/assets.db
-Environment=UNKEY_ASSETMANAGERD_CACHE_DIR=/opt/assetmanagerd/cache
-
-# Garbage collection
-Environment=UNKEY_ASSETMANAGERD_GC_ENABLED=true
-Environment=UNKEY_ASSETMANAGERD_GC_INTERVAL=1h
-Environment=UNKEY_ASSETMANAGERD_GC_MAX_AGE=24h
-
-# Builderd integration configuration
-Environment=UNKEY_ASSETMANAGERD_BUILDERD_ENABLED=true
-Environment=UNKEY_ASSETMANAGERD_BUILDERD_ENDPOINT=http://localhost:8082
-Environment=UNKEY_ASSETMANAGERD_BUILDERD_TIMEOUT=30m
-Environment=UNKEY_ASSETMANAGERD_BUILDERD_AUTO_REGISTER=true
-Environment=UNKEY_ASSETMANAGERD_BUILDERD_MAX_RETRIES=3
-Environment=UNKEY_ASSETMANAGERD_BUILDERD_RETRY_DELAY=5s
-
-# Resource limits
-LimitNOFILE=65536
-LimitNPROC=4096
-
-# Basic security settings
-NoNewPrivileges=true
-PrivateTmp=true
-
-[Install]
-WantedBy=multi-user.target
diff --git a/go/deploy/docker/systemd/billaged.service b/go/deploy/docker/systemd/billaged.service
deleted file mode 100644
index 863246f251..0000000000
--- a/go/deploy/docker/systemd/billaged.service
+++ /dev/null
@@ -1,33 +0,0 @@
-[Unit]
-Description=Billaged VM Usage Billing Service (Development)
-Documentation=https://github.com/unkeyed/unkey/go/deploy/billaged
-After=network.target
-Wants=network.target
-
-[Service]
-Type=simple
-User=billaged
-Group=billaged
-# Load development environment variables
-EnvironmentFile=/etc/default/unkey-deploy
-# Service will start in the required directories
-ExecStart=/usr/local/bin/billaged
-Restart=always
-RestartSec=5
-StandardOutput=journal
-StandardError=journal
-SyslogIdentifier=billaged
-
-# Development-specific environment overrides
-Environment=UNKEY_BILLAGED_TLS_MODE=disabled
-Environment=UNKEY_BILLAGED_OTEL_ENABLED=false
-Environment=UNKEY_BILLAGED_PORT=8081
-Environment=UNKEY_BILLAGED_ADDRESS=0.0.0.0
-Environment=UNKEY_BILLAGED_AGGREGATION_INTERVAL=60s
-
-# Resource limits
-LimitNOFILE=65536
-LimitNPROC=4096
-
-[Install]
-WantedBy=multi-user.target
\ No newline at end of file
diff --git a/go/deploy/docker/systemd/builderd.service b/go/deploy/docker/systemd/builderd.service
deleted file mode 100644
index 67b0bd93d0..0000000000
--- a/go/deploy/docker/systemd/builderd.service
+++ /dev/null
@@ -1,62 +0,0 @@
-[Unit]
-Description=Builderd Multi-Tenant Build Service (Development)
-Documentation=https://github.com/unkeyed/unkey/go/deploy/builderd
-After=network.target
-Wants=network.target
-
-[Service]
-Type=simple
-# Run as root for Docker access
-User=root
-Group=root
-# Load development environment variables
-EnvironmentFile=/etc/default/unkey-deploy
-# Service will start in the required directories
-ExecStart=/usr/local/bin/builderd
-Restart=always
-RestartSec=5
-StandardOutput=journal
-StandardError=journal
-SyslogIdentifier=builderd
-
-# Development-specific environment overrides
-Environment=UNKEY_BUILDERD_TLS_MODE=disabled
-Environment=UNKEY_BUILDERD_OTEL_ENABLED=false
-Environment=UNKEY_BUILDERD_PORT=8082
-Environment=UNKEY_BUILDERD_ADDRESS=0.0.0.0
-
-# Build configuration for development
-Environment=UNKEY_BUILDERD_MAX_CONCURRENT_BUILDS=3
-Environment=UNKEY_BUILDERD_BUILD_TIMEOUT=15m
-Environment=UNKEY_BUILDERD_SCRATCH_DIR=/opt/builderd/scratch
-Environment=UNKEY_BUILDERD_ROOTFS_OUTPUT_DIR=/opt/builderd/rootfs
-Environment=UNKEY_BUILDERD_WORKSPACE_DIR=/opt/builderd/workspace
-
-# Storage configuration
-Environment=UNKEY_BUILDERD_STORAGE_BACKEND=local
-Environment=UNKEY_BUILDERD_STORAGE_RETENTION_DAYS=7
-
-# Database configuration
-Environment=UNKEY_BUILDERD_DATABASE_TYPE=sqlite
-Environment=UNKEY_BUILDERD_DATABASE_DATA_DIR=/opt/builderd/data
-
-# Docker configuration
-Environment=UNKEY_BUILDERD_DOCKER_MAX_IMAGE_SIZE_GB=2
-
-# Tenant isolation (disabled for development)
-Environment=UNKEY_BUILDERD_TENANT_ISOLATION_ENABLED=false
-
-# AssetManagerd integration
-Environment=UNKEY_BUILDERD_ASSETMANAGER_ENABLED=true
-Environment=UNKEY_BUILDERD_ASSETMANAGER_ENDPOINT=http://localhost:8083
-
-# Resource limits
-LimitNOFILE=65536
-LimitNPROC=4096
-
-# Shutdown configuration
-TimeoutStopSec=30
-KillMode=mixed
-
-[Install]
-WantedBy=multi-user.target
\ No newline at end of file
diff --git a/go/deploy/docker/systemd/metald.service b/go/deploy/docker/systemd/metald.service
deleted file mode 100644
index 5d3627ccfe..0000000000
--- a/go/deploy/docker/systemd/metald.service
+++ /dev/null
@@ -1,58 +0,0 @@
-[Unit]
-Description=Metald VM Management Service (Development)
-Documentation=https://github.com/unkeyed/unkey/go/deploy/metald
-After=network.target assetmanagerd.service billaged.service
-Wants=network.target
-# Start after assetmanagerd and billaged for proper service order
-
-[Service]
-Type=simple
-# Run as root for Docker access
-User=root
-Group=root
-# Load development environment variables
-EnvironmentFile=/etc/default/unkey-deploy
-# Service will start in the required directories
-ExecStart=/usr/local/bin/metald
-Restart=always
-RestartSec=5
-StandardOutput=journal
-StandardError=journal
-SyslogIdentifier=metald
-
-# Development-specific environment overrides
-Environment=UNKEY_METALD_TLS_MODE=disabled
-Environment=UNKEY_METALD_OTEL_ENABLED=false
-Environment=UNKEY_METALD_PORT=8080
-Environment=UNKEY_METALD_ADDRESS=0.0.0.0
-
-# Docker backend configuration
-Environment=UNKEY_METALD_BACKEND=docker
-Environment=UNKEY_METALD_DOCKER_HOST=unix:///var/run/docker.sock
-
-# Process Manager Configuration
-Environment=UNKEY_METALD_SOCKET_DIR=/opt/metald/sockets
-Environment=UNKEY_METALD_LOG_DIR=/opt/metald/logs
-Environment=UNKEY_METALD_MAX_PROCESSES=100
-
-# Billing Configuration
-Environment=UNKEY_METALD_BILLING_ENABLED=true
-Environment=UNKEY_METALD_BILLING_ENDPOINT=http://localhost:8081
-Environment=UNKEY_METALD_BILLING_MOCK_MODE=false
-
-# AssetManager Configuration
-Environment=UNKEY_METALD_ASSETMANAGER_ENABLED=true
-Environment=UNKEY_METALD_ASSETMANAGER_ENDPOINT=http://localhost:8083
-Environment=UNKEY_METALD_ASSETMANAGER_CACHE_DIR=/opt/metald/assets
-
-# Network configuration for Docker backend
-Environment=UNKEY_METALD_DOCKER_NETWORK=bridge
-Environment=UNKEY_METALD_DOCKER_PORT_RANGE_MIN=30000
-Environment=UNKEY_METALD_DOCKER_PORT_RANGE_MAX=40000
-
-# Resource limits
-LimitNOFILE=65536
-LimitNPROC=4096
-
-[Install]
-WantedBy=multi-user.target
\ No newline at end of file
diff --git a/go/deploy/pkg/health/go.mod b/go/deploy/pkg/health/go.mod
deleted file mode 100644
index 5bb0ff7f1c..0000000000
--- a/go/deploy/pkg/health/go.mod
+++ /dev/null
@@ -1,5 +0,0 @@
-module github.com/unkeyed/unkey/go/deploy/pkg/health
-
-go 1.25
-
-toolchain go1.25.1
diff --git a/go/deploy/pkg/health/health.go b/go/deploy/pkg/health/health.go
deleted file mode 100644
index 0915f42484..0000000000
--- a/go/deploy/pkg/health/health.go
+++ /dev/null
@@ -1,55 +0,0 @@
-// Package health provides HTTP health check handlers.
-package health
-
-import (
- "encoding/json"
- "net/http"
- "time"
-)
-
-// Response represents a health check response.
-type Response struct {
- // Status is the health status, typically "ok".
- Status string `json:"status"`
-
- // Service is the service name.
- Service string `json:"service"`
-
- // Version is the service version.
- Version string `json:"version"`
-
- // Uptime is the service uptime in seconds.
- Uptime float64 `json:"uptime_seconds"`
-}
-
-// Handler returns an HTTP handler that responds with JSON health status.
-// The handler calculates uptime from startTime and always returns 200 OK.
-// If JSON encoding fails, it returns "OK" as plain text.
-func Handler(serviceName, version string, startTime time.Time) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- response := Response{
- Status: "ok",
- Service: serviceName,
- Version: version,
- Uptime: time.Since(startTime).Seconds(),
- }
-
- w.Header().Set("Content-Type", "application/json")
- w.WriteHeader(http.StatusOK)
-
- if err := json.NewEncoder(w).Encode(response); err != nil {
- // If we can't encode the response, return a simple text response
- w.Header().Set("Content-Type", "text/plain")
- w.Write([]byte("OK"))
- }
- }
-}
-
-// SimpleHandler returns an HTTP handler that responds with "OK" as plain text.
-// The handler always returns 200 OK with no JSON overhead.
-func SimpleHandler() http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusOK)
- w.Write([]byte("OK"))
- }
-}
diff --git a/go/deploy/pkg/observability/interceptors/client.go b/go/deploy/pkg/observability/interceptors/client.go
deleted file mode 100644
index 62b43720be..0000000000
--- a/go/deploy/pkg/observability/interceptors/client.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package interceptors
-
-import (
- "context"
- "log/slog"
-
- "connectrpc.com/connect"
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/codes"
- "go.opentelemetry.io/otel/propagation"
- "go.opentelemetry.io/otel/trace"
-)
-
-// NewClientTracePropagationInterceptor creates a ConnectRPC interceptor that propagates
-// OpenTelemetry trace context to outgoing RPC requests. This ensures distributed traces
-// span across service boundaries.
-//
-// AIDEV-NOTE: This interceptor is essential for distributed tracing in microservices.
-// It must be the first interceptor in the chain to ensure trace context is available
-// for all subsequent interceptors and the actual RPC call.
-func NewClientTracePropagationInterceptor(logger *slog.Logger) connect.UnaryInterceptorFunc {
- return func(next connect.UnaryFunc) connect.UnaryFunc {
- return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
- // Use the global propagator to inject trace context into headers
- propagator := otel.GetTextMapPropagator()
- propagator.Inject(ctx, propagation.HeaderCarrier(req.Header()))
-
- // Log trace propagation for debugging
- if span := trace.SpanFromContext(ctx); span.SpanContext().IsValid() {
- logger.LogAttrs(ctx, slog.LevelDebug, "propagating trace context",
- slog.String("trace_id", span.SpanContext().TraceID().String()),
- slog.String("span_id", span.SpanContext().SpanID().String()),
- slog.String("procedure", req.Spec().Procedure),
- slog.Bool("sampled", span.SpanContext().IsSampled()),
- )
- }
-
- return next(ctx, req)
- }
- }
-}
-
-// NewClientMetricsInterceptor creates a ConnectRPC interceptor for client-side metrics.
-// It creates spans for outgoing RPC calls and tracks their duration and status.
-//
-// AIDEV-NOTE: This creates CLIENT spans, which are different from SERVER spans.
-// The trace propagation interceptor ensures these spans are properly linked to
-// the parent trace.
-func NewClientMetricsInterceptor(serviceName string, logger *slog.Logger) connect.UnaryInterceptorFunc {
- tracer := otel.Tracer(serviceName)
-
- return func(next connect.UnaryFunc) connect.UnaryFunc {
- return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
- // Extract procedure info
- procedure := req.Spec().Procedure
-
- // Create a client span
- ctx, span := tracer.Start(ctx, procedure,
- trace.WithSpanKind(trace.SpanKindClient),
- trace.WithAttributes(
- attribute.String("rpc.system", "connect_rpc"),
- attribute.String("rpc.service", serviceName),
- attribute.String("rpc.method", procedure),
- ),
- )
- defer span.End()
-
- // Execute the RPC call
- resp, err := next(ctx, req)
-
- // Record the result
- if err != nil {
- span.RecordError(err)
- span.SetStatus(codes.Error, err.Error())
- } else {
- span.SetStatus(codes.Ok, "")
- }
-
- return resp, err
- }
- }
-}
-
-// NewDefaultClientInterceptors returns a set of default interceptors for RPC clients.
-// The interceptors are returned in the correct order for optimal functionality:
-// 1. Trace propagation - ensures trace context is in headers
-// 2. Client metrics - creates client spans
-// 3. Tenant forwarding - adds tenant headers
-//
-// AIDEV-NOTE: The order matters! Trace propagation must happen first so that
-// the client metrics interceptor can create spans that are properly linked
-// to the parent trace.
-func NewDefaultClientInterceptors(serviceName string, logger *slog.Logger) []connect.UnaryInterceptorFunc {
- return []connect.UnaryInterceptorFunc{
- NewClientTracePropagationInterceptor(logger),
- NewClientMetricsInterceptor(serviceName, logger),
- }
-}
diff --git a/go/deploy/pkg/observability/interceptors/go.mod b/go/deploy/pkg/observability/interceptors/go.mod
deleted file mode 100644
index b42a4b3939..0000000000
--- a/go/deploy/pkg/observability/interceptors/go.mod
+++ /dev/null
@@ -1,22 +0,0 @@
-module github.com/unkeyed/unkey/go/deploy/pkg/observability/interceptors
-
-go 1.25
-
-toolchain go1.25.1
-
-require (
- connectrpc.com/connect v1.18.1
- github.com/unkeyed/unkey/go/deploy/pkg/tracing v0.0.0-00010101000000-000000000000
- go.opentelemetry.io/otel v1.38.0
- go.opentelemetry.io/otel/metric v1.38.0
- go.opentelemetry.io/otel/trace v1.38.0
-)
-
-require (
- github.com/go-logr/logr v1.4.3 // indirect
- github.com/go-logr/stdr v1.2.2 // indirect
- go.opentelemetry.io/auto/sdk v1.2.0 // indirect
- google.golang.org/protobuf v1.36.6 // indirect
-)
-
-replace github.com/unkeyed/unkey/go/deploy/pkg/tracing => ../../tracing
diff --git a/go/deploy/pkg/observability/interceptors/go.sum b/go/deploy/pkg/observability/interceptors/go.sum
deleted file mode 100644
index b4a61da062..0000000000
--- a/go/deploy/pkg/observability/interceptors/go.sum
+++ /dev/null
@@ -1,31 +0,0 @@
-connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw=
-connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
-github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
-github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
-github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
-github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
-github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
-github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
-go.opentelemetry.io/auto/sdk v1.2.0 h1:YpRtUFjvhSymycLS2T81lT6IGhcUP+LUPtv0iv1N8bM=
-go.opentelemetry.io/auto/sdk v1.2.0/go.mod h1:1deq2zL7rwjwC8mR7XgY2N+tlIl6pjmEUoLDENMEzwk=
-go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
-go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
-go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
-go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
-go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
-go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
-golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
-golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
-golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
-google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/go/deploy/pkg/observability/interceptors/interceptors.go b/go/deploy/pkg/observability/interceptors/interceptors.go
deleted file mode 100644
index aaea772d57..0000000000
--- a/go/deploy/pkg/observability/interceptors/interceptors.go
+++ /dev/null
@@ -1,77 +0,0 @@
-// Package interceptors provides shared ConnectRPC interceptors for observability and tenant management.
-//
-// This package consolidates common interceptor functionality across all Unkey services:
-// - Metrics collection with OpenTelemetry
-// - Distributed tracing
-// - Structured logging
-// - Tenant authentication and context propagation
-//
-// Usage example:
-//
-// import (
-// "github.com/unkeyed/unkey/go/deploy/pkg/observability/interceptors"
-// "go.opentelemetry.io/otel"
-// )
-//
-// // Create interceptors with service-specific configuration
-// metricsInterceptor := interceptors.NewMetricsInterceptor(
-// interceptors.WithServiceName("metald"),
-// interceptors.WithMeter(otel.Meter("metald")),
-// interceptors.WithActiveRequestsMetric(true),
-// )
-//
-// loggingInterceptor := interceptors.NewLoggingInterceptor(
-// interceptors.WithServiceName("metald"),
-// interceptors.WithLogger(logger),
-// )
-//
-// tenantInterceptor := interceptors.NewTenantAuthInterceptor(
-// interceptors.WithServiceName("metald"),
-// interceptors.WithTenantAuth(true, "/health.v1.HealthService/Check"),
-// )
-//
-// // Apply interceptors to ConnectRPC handler
-// handler := connect.NewUnaryHandler(
-// procedure,
-// svc.Method,
-// connect.WithInterceptors(
-// tenantInterceptor,
-// metricsInterceptor,
-// loggingInterceptor,
-// ),
-// )
-package interceptors
-
-import (
- "connectrpc.com/connect"
- "go.opentelemetry.io/otel"
-)
-
-// NewDefaultInterceptors creates a standard set of interceptors with sensible defaults.
-// This includes metrics, logging, and tenant authentication interceptors configured
-// for the specified service.
-//
-// The interceptors are returned in the recommended order:
-// 1. Tenant auth (extracts tenant context first)
-// 2. Metrics (tracks all requests including auth failures)
-// 3. Logging (logs final request/response details)
-func NewDefaultInterceptors(serviceName string, opts ...Option) []connect.UnaryInterceptorFunc {
- // Merge service name with any provided options
- allOpts := append([]Option{WithServiceName(serviceName)}, opts...)
-
- // Create default meter if not provided
- defaultOpts := []Option{
- WithMeter(otel.Meter(serviceName)),
- }
- allOpts = append(defaultOpts, allOpts...)
-
- return []connect.UnaryInterceptorFunc{
- NewMetricsInterceptor(allOpts...),
- NewLoggingInterceptor(allOpts...),
- }
-}
-
-// AIDEV-NOTE: Interceptors are ordered for proper context propagation:
-// 1. Tenant auth must run first to add tenant context
-// 2. Metrics can then include tenant info in metrics
-// 3. Logging runs last to capture the complete request lifecycle
diff --git a/go/deploy/pkg/observability/interceptors/logging.go b/go/deploy/pkg/observability/interceptors/logging.go
deleted file mode 100644
index 4d9d2bcb1f..0000000000
--- a/go/deploy/pkg/observability/interceptors/logging.go
+++ /dev/null
@@ -1,139 +0,0 @@
-package interceptors
-
-import (
- "context"
- "errors"
- "fmt"
- "log/slog"
- "runtime/debug"
- "time"
-
- "connectrpc.com/connect"
- "github.com/unkeyed/unkey/go/deploy/pkg/tracing"
- "go.opentelemetry.io/otel/trace"
-)
-
-// NewLoggingInterceptor creates a ConnectRPC interceptor that provides structured logging
-// for all RPC calls, including request/response details, timing, and error information.
-func NewLoggingInterceptor(opts ...Option) connect.UnaryInterceptorFunc {
- options := applyOptions(opts)
-
- // Use default logger if none provided
- logger := options.Logger
- if logger == nil {
- logger = slog.Default()
- }
-
- return func(next connect.UnaryFunc) connect.UnaryFunc {
- return func(ctx context.Context, req connect.AnyRequest) (resp connect.AnyResponse, err error) {
- // Preserves existing errors and logs panic details for debugging
- defer func() {
- if r := recover(); r != nil {
- attrs := []any{
- slog.String("service", options.ServiceName),
- slog.String("procedure", req.Spec().Procedure),
- slog.Any("panic", r),
- slog.String("panic_type", fmt.Sprintf("%T", r)),
- }
- if options.EnablePanicStackTrace {
- attrs = append(attrs, slog.String("stack_trace", string(debug.Stack())))
- }
- logger.Error("panic in logging interceptor", attrs...)
-
- // Only override err if it's not already set
- if err == nil {
- err = connect.NewError(connect.CodeInternal, fmt.Errorf("internal server error: %v", r))
- }
- }
- }()
-
- start := time.Now()
- procedure := req.Spec().Procedure
- methodName := tracing.ExtractMethodName(procedure)
-
- // Extract trace ID if available
- var traceID string
- if span := trace.SpanFromContext(ctx); span.SpanContext().IsValid() {
- traceID = span.SpanContext().TraceID().String()
- }
-
- // Build request attributes
- requestAttrs := []slog.Attr{
- slog.String("service", options.ServiceName),
- slog.String("procedure", procedure),
- slog.String("method", methodName),
- slog.String("protocol", req.Peer().Protocol),
- slog.String("trace_id", traceID),
- }
-
- // Log request
- logger.LogAttrs(ctx, slog.LevelInfo, "rpc request started", requestAttrs...)
-
- // Execute request with panic recovery
- func() {
- defer func() {
- if r := recover(); r != nil {
- err = connect.NewError(connect.CodeInternal, fmt.Errorf("logging handler panic: %v", r))
- }
- }()
- resp, err = next(ctx, req)
- }()
-
- // Calculate duration
- duration := time.Since(start)
-
- // Build response attributes
- responseAttrs := []slog.Attr{
- slog.String("service", options.ServiceName),
- slog.String("procedure", procedure),
- slog.Duration("duration", duration),
- slog.String("trace_id", traceID),
- }
-
- // Log response based on error status
- if err != nil {
- // Determine log level based on error type
- logLevel := slog.LevelError
- var connectErr *connect.Error
- if errors.As(err, &connectErr) {
- responseAttrs = append(responseAttrs,
- slog.String("error", err.Error()),
- slog.String("code", connectErr.Code().String()),
- )
-
- // Use warning level for client-side errors
- switch connectErr.Code() {
- case connect.CodeNotFound,
- connect.CodeAlreadyExists,
- connect.CodeInvalidArgument,
- connect.CodeFailedPrecondition,
- connect.CodeUnauthenticated,
- connect.CodePermissionDenied,
- connect.CodeCanceled,
- connect.CodeDeadlineExceeded,
- connect.CodeResourceExhausted,
- connect.CodeAborted,
- connect.CodeOutOfRange:
- logLevel = slog.LevelWarn
- case connect.CodeUnknown,
- connect.CodeUnimplemented,
- connect.CodeInternal,
- connect.CodeUnavailable,
- connect.CodeDataLoss:
- logLevel = slog.LevelError
- }
- } else {
- responseAttrs = append(responseAttrs,
- slog.String("error", err.Error()),
- )
- }
-
- logger.LogAttrs(ctx, logLevel, "rpc request failed", responseAttrs...)
- } else {
- logger.LogAttrs(ctx, slog.LevelInfo, "rpc request completed", responseAttrs...)
- }
-
- return resp, err
- }
- }
-}
diff --git a/go/deploy/pkg/observability/interceptors/metrics.go b/go/deploy/pkg/observability/interceptors/metrics.go
deleted file mode 100644
index f03d6fcbec..0000000000
--- a/go/deploy/pkg/observability/interceptors/metrics.go
+++ /dev/null
@@ -1,240 +0,0 @@
-package interceptors
-
-import (
- "context"
- "errors"
- "fmt"
- "log/slog"
- "runtime/debug"
- "time"
-
- "connectrpc.com/connect"
- "github.com/unkeyed/unkey/go/deploy/pkg/tracing"
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/codes"
- "go.opentelemetry.io/otel/metric"
- "go.opentelemetry.io/otel/trace"
-)
-
-// Metrics holds the OTEL metrics for the interceptor.
-type Metrics struct {
- requestCounter metric.Int64Counter
- requestDuration metric.Float64Histogram
- activeRequests metric.Int64UpDownCounter
- panicCounter metric.Int64Counter
-}
-
-// NewMetrics creates new metrics using the provided meter.
-func NewMetrics(meter metric.Meter) (*Metrics, error) {
- requestCounter, err := meter.Int64Counter(
- "rpc_server_requests_total",
- metric.WithDescription("Total number of RPC requests"),
- metric.WithUnit("1"),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create request counter: %w", err)
- }
-
- requestDuration, err := meter.Float64Histogram(
- "rpc_server_request_duration_seconds",
- metric.WithDescription("RPC request duration in seconds"),
- metric.WithUnit("s"),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create request duration histogram: %w", err)
- }
-
- activeRequests, err := meter.Int64UpDownCounter(
- "rpc_server_active_requests",
- metric.WithDescription("Number of active RPC requests"),
- metric.WithUnit("1"),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create active requests counter: %w", err)
- }
-
- panicCounter, err := meter.Int64Counter(
- "rpc_server_panics_total",
- metric.WithDescription("Total number of RPC server panics"),
- metric.WithUnit("1"),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create panic counter: %w", err)
- }
-
- return &Metrics{
- requestCounter: requestCounter,
- requestDuration: requestDuration,
- activeRequests: activeRequests,
- panicCounter: panicCounter,
- }, nil
-}
-
-// NewMetricsInterceptor creates a ConnectRPC interceptor that collects OpenTelemetry metrics
-// and provides distributed tracing for all RPC calls.
-//
-// AIDEV-NOTE: This interceptor provides consistent metrics collection across all Unkey services
-func NewMetricsInterceptor(opts ...Option) connect.UnaryInterceptorFunc {
- options := applyOptions(opts)
-
- // Create metrics if meter is provided
- var metrics *Metrics
- if options.Meter != nil {
- m, err := NewMetrics(options.Meter)
- if err != nil {
- // Log error but continue without metrics
- if options.Logger != nil {
- options.Logger.Error("failed to create metrics",
- slog.String("service", options.ServiceName),
- slog.String("error", err.Error()),
- )
- } else {
- slog.Default().Error("failed to create metrics",
- slog.String("service", options.ServiceName),
- slog.String("error", err.Error()),
- )
- }
- } else {
- metrics = m
- }
- }
-
- return func(next connect.UnaryFunc) connect.UnaryFunc {
- return func(ctx context.Context, req connect.AnyRequest) (resp connect.AnyResponse, err error) {
- start := time.Now()
-
- // Extract procedure info using shared utilities
- procedure := req.Spec().Procedure
- methodName := tracing.ExtractMethodName(procedure)
- serviceName := tracing.ExtractServiceName(procedure)
-
- // AIDEV-NOTE: Using unified span naming convention: service.method
- spanName := tracing.FormatSpanName(options.ServiceName, methodName)
-
- // Start span
- tracer := otel.Tracer(options.ServiceName)
- ctx, span := tracer.Start(ctx, spanName,
- trace.WithSpanKind(trace.SpanKindServer),
- trace.WithAttributes(
- attribute.String("rpc.system", "connect_rpc"),
- attribute.String("rpc.service", serviceName),
- attribute.String("rpc.method", methodName),
- ),
- )
-
- defer func() {
- if r := recover(); r != nil {
- // Log panic with optional stack trace
- if options.Logger != nil {
- attrs := []any{
- slog.String("service", options.ServiceName),
- slog.String("procedure", procedure),
- slog.Any("panic", r),
- slog.String("panic_type", fmt.Sprintf("%T", r)),
- }
- if options.EnablePanicStackTrace {
- attrs = append(attrs, slog.String("stack_trace", string(debug.Stack())))
- }
- options.Logger.Error("panic in metrics interceptor", attrs...)
- }
-
- // Record panic metrics
- if metrics != nil {
- attrs := []attribute.KeyValue{
- attribute.String("rpc.method", procedure),
- attribute.String("panic.type", fmt.Sprintf("%T", r)),
- }
- metrics.panicCounter.Add(ctx, 1, metric.WithAttributes(attrs...))
- }
-
- span.RecordError(fmt.Errorf("panic: %v", r))
- span.SetStatus(codes.Error, fmt.Sprintf("panic: %v", r))
-
- // Only override err if it's not already set
- if err == nil {
- err = connect.NewError(connect.CodeInternal, fmt.Errorf("internal server error: %v", r))
- }
- }
- span.End()
- }()
-
- // Track active requests if enabled
- if metrics != nil && options.EnableActiveRequestsMetric {
- attrs := []attribute.KeyValue{
- attribute.String("rpc.method", procedure),
- }
- metrics.activeRequests.Add(ctx, 1, metric.WithAttributes(attrs...))
- defer metrics.activeRequests.Add(ctx, -1, metric.WithAttributes(attrs...))
- }
-
- // Call the handler with panic recovery
- func() {
- defer func() {
- if r := recover(); r != nil {
- err = connect.NewError(connect.CodeInternal, fmt.Errorf("metrics handler panic: %v", r))
- span.RecordError(err)
- }
- }()
- resp, err = next(ctx, req)
- }()
-
- // Calculate duration
- duration := time.Since(start)
-
- // Record error and status
- statusCode := "ok"
- if err != nil {
- var connectErr *connect.Error
- if errors.As(err, &connectErr) {
- statusCode = connectErr.Code().String()
- span.SetAttributes(
- attribute.String("rpc.connect.code", statusCode),
- attribute.String("rpc.connect.message", connectErr.Message()),
- )
- }
-
- // Record error in span
- span.RecordError(err)
- span.SetStatus(codes.Error, err.Error())
-
- // Error resampling: create a new span that's always sampled
- // This ensures errors are captured even with low sampling rates
- if options.EnableErrorResampling && !span.SpanContext().IsSampled() {
- _, errorSpan := tracer.Start(ctx, spanName+".error",
- trace.WithSpanKind(trace.SpanKindServer),
- trace.WithAttributes(
- attribute.String("rpc.system", "connect_rpc"),
- attribute.String("rpc.service", serviceName),
- attribute.String("rpc.method", methodName),
- attribute.Bool("error.resampled", true),
- ),
- )
- errorSpan.RecordError(err)
- errorSpan.SetStatus(codes.Error, err.Error())
- errorSpan.End()
- }
- } else {
- span.SetStatus(codes.Ok, "")
- }
-
- // Record metrics
- if metrics != nil {
- attrs := []attribute.KeyValue{
- attribute.String("rpc.method", procedure),
- attribute.String("rpc.status", statusCode),
- }
-
- // Increment request counter
- metrics.requestCounter.Add(ctx, 1, metric.WithAttributes(attrs...))
-
- // Record duration if enabled
- if options.EnableRequestDurationMetric {
- metrics.requestDuration.Record(ctx, duration.Seconds(), metric.WithAttributes(attrs...))
- }
- }
-
- return resp, err
- }
- }
-}
diff --git a/go/deploy/pkg/observability/interceptors/options.go b/go/deploy/pkg/observability/interceptors/options.go
deleted file mode 100644
index 33a990ef2a..0000000000
--- a/go/deploy/pkg/observability/interceptors/options.go
+++ /dev/null
@@ -1,105 +0,0 @@
-// Package interceptors provides shared ConnectRPC interceptors for observability and tenant management
-// across all Unkey services. These interceptors handle metrics collection, distributed tracing,
-// structured logging, and tenant authentication in a consistent manner.
-package interceptors
-
-import (
- "log/slog"
-
- "go.opentelemetry.io/otel/metric"
-)
-
-// Options holds configuration for interceptors.
-type Options struct {
- // ServiceName is the name of the service using the interceptor.
- ServiceName string
-
- // Logger is the structured logger to use. If nil, logging interceptor is disabled.
- Logger *slog.Logger
-
- // Meter is the OpenTelemetry meter for metrics. If nil, metrics are disabled.
- Meter metric.Meter
-
- // EnableActiveRequestsMetric controls whether to track active requests count.
- EnableActiveRequestsMetric bool
-
- // EnableRequestDurationMetric controls whether to record request duration histogram.
- EnableRequestDurationMetric bool
-
- // EnablePanicStackTrace controls whether to log full stack traces on panic.
- EnablePanicStackTrace bool
-
- // EnableErrorResampling controls whether to create additional spans for errors
- // when the main span is not sampled.
- EnableErrorResampling bool
-}
-
-// Option is a function that configures Options.
-type Option func(*Options)
-
-// WithServiceName sets the service name for interceptors.
-func WithServiceName(name string) Option {
- return func(o *Options) {
- o.ServiceName = name
- }
-}
-
-// WithLogger sets the logger for the logging interceptor.
-func WithLogger(logger *slog.Logger) Option {
- return func(o *Options) {
- o.Logger = logger
- }
-}
-
-// WithMeter sets the OpenTelemetry meter for metrics collection.
-func WithMeter(meter metric.Meter) Option {
- return func(o *Options) {
- o.Meter = meter
- }
-}
-
-// WithActiveRequestsMetric enables tracking of active requests count.
-func WithActiveRequestsMetric(enabled bool) Option {
- return func(o *Options) {
- o.EnableActiveRequestsMetric = enabled
- }
-}
-
-// WithRequestDurationMetric enables request duration histogram.
-func WithRequestDurationMetric(enabled bool) Option {
- return func(o *Options) {
- o.EnableRequestDurationMetric = enabled
- }
-}
-
-// WithPanicStackTrace enables logging of full stack traces on panic.
-func WithPanicStackTrace(enabled bool) Option {
- return func(o *Options) {
- o.EnablePanicStackTrace = enabled
- }
-}
-
-// WithErrorResampling enables creation of additional spans for errors
-// when the main span is not sampled.
-func WithErrorResampling(enabled bool) Option {
- return func(o *Options) {
- o.EnableErrorResampling = enabled
- }
-}
-
-// applyOptions creates an Options struct from the provided options.
-func applyOptions(opts []Option) *Options {
- options := &Options{
- ServiceName: "unknown",
- EnableActiveRequestsMetric: true,
- EnableRequestDurationMetric: false,
- EnablePanicStackTrace: true,
- EnableErrorResampling: true,
- }
-
- for _, opt := range opts {
- opt(options)
- }
-
- return options
-}
diff --git a/go/deploy/pkg/spiffe/go.mod b/go/deploy/pkg/spiffe/go.mod
deleted file mode 100644
index 2cc5d1ea64..0000000000
--- a/go/deploy/pkg/spiffe/go.mod
+++ /dev/null
@@ -1,19 +0,0 @@
-module github.com/unkeyed/unkey/go/deploy/pkg/spiffe
-
-go 1.25
-
-toolchain go1.25.1
-
-require github.com/spiffe/go-spiffe/v2 v2.6.0
-
-require (
- github.com/Microsoft/go-winio v0.6.2 // indirect
- github.com/go-jose/go-jose/v4 v4.1.2 // indirect
- golang.org/x/crypto v0.39.0 // indirect
- golang.org/x/net v0.41.0 // indirect
- golang.org/x/sys v0.33.0 // indirect
- golang.org/x/text v0.26.0 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
- google.golang.org/grpc v1.75.0 // indirect
- google.golang.org/protobuf v1.36.7 // indirect
-)
diff --git a/go/deploy/pkg/spiffe/go.sum b/go/deploy/pkg/spiffe/go.sum
deleted file mode 100644
index df79effe25..0000000000
--- a/go/deploy/pkg/spiffe/go.sum
+++ /dev/null
@@ -1,52 +0,0 @@
-github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
-github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
-github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
-github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
-github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
-github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
-github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
-github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
-github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
-github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
-github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
-github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
-github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
-github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
-github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
-github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
-go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
-go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
-go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
-go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
-go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
-go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
-go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
-go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
-go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
-go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
-golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
-golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
-golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
-golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
-golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
-golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
-golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
-golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
-gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
-gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
-google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
-google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
-google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
-google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/go/deploy/pkg/telemetry/go.mod b/go/deploy/pkg/telemetry/go.mod
deleted file mode 100644
index 0d3a9905f1..0000000000
--- a/go/deploy/pkg/telemetry/go.mod
+++ /dev/null
@@ -1,44 +0,0 @@
-module github.com/unkeyed/unkey/go/deploy/pkg/telemetry
-
-go 1.25
-
-toolchain go1.25.1
-
-require (
- github.com/prometheus/client_golang v1.23.2
- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
- go.opentelemetry.io/otel v1.38.0
- go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0
- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0
- go.opentelemetry.io/otel/exporters/prometheus v0.60.0
- go.opentelemetry.io/otel/sdk v1.38.0
- go.opentelemetry.io/otel/sdk/metric v1.38.0
-)
-
-require (
- github.com/beorn7/perks v1.0.1 // indirect
- github.com/cenkalti/backoff/v5 v5.0.3 // indirect
- github.com/cespare/xxhash/v2 v2.3.0 // indirect
- github.com/felixge/httpsnoop v1.0.4 // indirect
- github.com/go-logr/logr v1.4.3 // indirect
- github.com/go-logr/stdr v1.2.2 // indirect
- github.com/google/uuid v1.6.0 // indirect
- github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
- github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
- github.com/prometheus/client_model v0.6.2 // indirect
- github.com/prometheus/common v0.66.1 // indirect
- github.com/prometheus/otlptranslator v1.0.0 // indirect
- github.com/prometheus/procfs v0.17.0 // indirect
- go.opentelemetry.io/auto/sdk v1.2.0 // indirect
- go.opentelemetry.io/otel/metric v1.38.0 // indirect
- go.opentelemetry.io/otel/trace v1.38.0 // indirect
- go.opentelemetry.io/proto/otlp v1.8.0 // indirect
- go.yaml.in/yaml/v2 v2.4.3 // indirect
- golang.org/x/net v0.44.0 // indirect
- golang.org/x/sys v0.36.0 // indirect
- golang.org/x/text v0.29.0 // indirect
- google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect
- google.golang.org/grpc v1.75.1 // indirect
- google.golang.org/protobuf v1.36.9 // indirect
-)
diff --git a/go/deploy/pkg/telemetry/go.sum b/go/deploy/pkg/telemetry/go.sum
deleted file mode 100644
index e8a4806b88..0000000000
--- a/go/deploy/pkg/telemetry/go.sum
+++ /dev/null
@@ -1,96 +0,0 @@
-github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
-github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
-github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
-github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
-github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
-github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
-github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
-github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
-github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
-github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
-github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
-github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
-github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
-github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
-github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
-github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
-github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
-github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
-github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
-github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
-github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
-github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
-github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
-github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
-github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
-github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
-github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
-github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
-github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=
-github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=
-github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
-github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
-github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
-github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
-github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
-github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
-go.opentelemetry.io/auto/sdk v1.2.0 h1:YpRtUFjvhSymycLS2T81lT6IGhcUP+LUPtv0iv1N8bM=
-go.opentelemetry.io/auto/sdk v1.2.0/go.mod h1:1deq2zL7rwjwC8mR7XgY2N+tlIl6pjmEUoLDENMEzwk=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
-go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
-go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
-go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo=
-go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk=
-go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
-go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
-go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
-go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
-go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
-go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
-go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
-go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
-go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
-go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=
-go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
-go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
-go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
-go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
-golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
-golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
-golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
-golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
-golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
-golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
-gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
-gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
-google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 h1:d8Nakh1G+ur7+P3GcMjpRDEkoLUcLW2iU92XVqR+XMQ=
-google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090/go.mod h1:U8EXRNSd8sUYyDfs/It7KVWodQr+Hf9xtxyxWudSwEw=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 h1:/OQuEa4YWtDt7uQWHd3q3sUMb+QOLQUg1xa8CEsRv5w=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
-google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
-google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
-google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
-google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/go/deploy/pkg/telemetry/server.go b/go/deploy/pkg/telemetry/server.go
deleted file mode 100644
index 5911fa4685..0000000000
--- a/go/deploy/pkg/telemetry/server.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package telemetry
-
-import (
- "fmt"
- "log/slog"
- "net/http"
- "time"
-
- "github.com/prometheus/client_golang/prometheus/promhttp"
-)
-
-// MetricsServerConfig holds configuration for the Prometheus metrics HTTP server.
-//
-// The metrics server runs separately from the main application server to provide
-// monitoring endpoints without requiring authentication or TLS.
-type MetricsServerConfig struct {
- // Interface specifies the network interface to bind to (e.g., "127.0.0.1", "0.0.0.0").
- Interface string
- // Port specifies the TCP port for the metrics server.
- Port string
- // HealthHandler provides the /health endpoint handler. Optional.
- HealthHandler http.HandlerFunc
- // MetricsHandler provides the /metrics endpoint handler. Defaults to promhttp.Handler().
- MetricsHandler http.Handler
- // Logger is used for server lifecycle and error logging.
- Logger *slog.Logger
-}
-
-// NewMetricsServer creates a new HTTP server for Prometheus metrics and health checks.
-//
-// The server exposes /metrics for Prometheus scraping and optionally /health for
-// health checks. It runs without TLS and uses conservative timeout settings
-// suitable for monitoring workloads.
-func NewMetricsServer(cfg *MetricsServerConfig) *http.Server {
- mux := http.NewServeMux()
-
- // Use provided metrics handler or default to promhttp
- metricsHandler := cfg.MetricsHandler
- if metricsHandler == nil {
- metricsHandler = promhttp.Handler()
- }
- mux.Handle("/metrics", metricsHandler)
-
- // Add health endpoint if handler provided
- if cfg.HealthHandler != nil {
- mux.HandleFunc("/health", cfg.HealthHandler)
- }
-
- addr := fmt.Sprintf("%s:%s", cfg.Interface, cfg.Port)
-
- return &http.Server{
- Addr: addr,
- Handler: mux,
- ReadTimeout: 30 * time.Second,
- WriteTimeout: 30 * time.Second,
- IdleTimeout: 120 * time.Second,
- }
-}
-
-// StartMetricsServer starts the metrics server in a background goroutine.
-//
-// The server begins listening immediately and logs startup information including
-// the bound address and whether it's restricted to localhost. Server errors
-// are logged but do not cause the function to return an error.
-func StartMetricsServer(cfg *MetricsServerConfig) {
- server := NewMetricsServer(cfg)
-
- go func() {
- localhostOnly := cfg.Interface == "127.0.0.1" || cfg.Interface == "localhost"
- cfg.Logger.Info("starting prometheus metrics server",
- slog.String("address", server.Addr),
- slog.Bool("localhost_only", localhostOnly),
- )
-
- if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
- cfg.Logger.Error("prometheus server failed",
- slog.String("error", err.Error()),
- )
- }
- }()
-}
diff --git a/go/deploy/pkg/telemetry/telemetry.go b/go/deploy/pkg/telemetry/telemetry.go
deleted file mode 100644
index e2b8e8ac2a..0000000000
--- a/go/deploy/pkg/telemetry/telemetry.go
+++ /dev/null
@@ -1,253 +0,0 @@
-// Package telemetry provides OpenTelemetry instrumentation for distributed tracing and metrics collection.
-//
-// This package initializes and manages OpenTelemetry providers for both tracing and metrics,
-// supporting OTLP export and Prometheus metrics exposure. It handles resource creation,
-// provider lifecycle management, and HTTP handler instrumentation.
-//
-// Basic usage:
-//
-// cfg := &telemetry.Config{
-// Enabled: true,
-// ServiceName: "my-service",
-// ServiceVersion: "1.0.0",
-// TracingSamplingRate: 1.0,
-// OTLPEndpoint: "http://localhost:4318",
-// PrometheusEnabled: true,
-// PrometheusPort: "9090",
-// }
-//
-// provider, err := telemetry.Initialize(ctx, cfg, logger)
-// if err != nil {
-// log.Fatal(err)
-// }
-// defer provider.Shutdown(ctx)
-//
-// // Wrap HTTP handlers for automatic instrumentation
-// handler := provider.WrapHandler(myHandler, "operation-name")
-package telemetry
-
-import (
- "context"
- "fmt"
- "log/slog"
- "net/http"
- "os"
- "sync"
-
- "github.com/prometheus/client_golang/prometheus/promhttp"
- "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
- "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
- "go.opentelemetry.io/otel/exporters/prometheus"
- "go.opentelemetry.io/otel/propagation"
- sdkmetric "go.opentelemetry.io/otel/sdk/metric"
- "go.opentelemetry.io/otel/sdk/resource"
- sdktrace "go.opentelemetry.io/otel/sdk/trace"
- semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
-)
-
-// Config holds telemetry configuration for OpenTelemetry providers.
-//
-// All configuration options are optional and have sensible defaults.
-// TracingSamplingRate should be between 0.0 and 1.0, where 1.0 means
-// all traces are sampled.
-type Config struct {
- // Enabled controls whether telemetry is active. When false, Initialize returns a no-op provider.
- Enabled bool
- // ServiceName identifies the service in telemetry data and should be consistent across instances.
- ServiceName string
- // ServiceVersion is included in telemetry resource attributes for version tracking.
- ServiceVersion string
- // TracingSamplingRate controls the fraction of traces to sample (0.0 to 1.0).
- TracingSamplingRate float64
- // OTLPEndpoint is the HTTP endpoint for OTLP trace export.
- OTLPEndpoint string
- // PrometheusEnabled controls whether metrics are exposed via Prometheus HTTP endpoint.
- PrometheusEnabled bool
- // PrometheusPort specifies the port for the Prometheus metrics server.
- PrometheusPort string
- // PrometheusInterface specifies the network interface for the Prometheus metrics server.
- PrometheusInterface string
- // HighCardinalityLabelsEnabled allows high-cardinality metric labels when true.
- HighCardinalityLabelsEnabled bool
-}
-
-// Provider holds initialized OpenTelemetry providers and manages their lifecycle.
-//
-// Provider is safe for concurrent use and handles graceful shutdown of all
-// telemetry components. When telemetry is disabled, Provider methods are safe
-// to call but perform no operations.
-type Provider struct {
- // TracerProvider provides distributed tracing capabilities via OpenTelemetry.
- TracerProvider *sdktrace.TracerProvider
- // MeterProvider provides metrics collection capabilities via OpenTelemetry.
- MeterProvider *sdkmetric.MeterProvider
- // PrometheusHTTP serves Prometheus metrics when metrics are enabled.
- PrometheusHTTP http.Handler
- // Resource contains service identification attributes used by all providers.
- Resource *resource.Resource
- promExporter *prometheus.Exporter
- shutdownFuncs []func(context.Context) error
- mu sync.Mutex
-}
-
-// Initialize sets up OpenTelemetry providers for tracing and metrics collection.
-//
-// When cfg.Enabled is false, Initialize returns a no-op Provider that is safe to use
-// but performs no telemetry operations. The returned Provider must be shut down
-// via Shutdown to ensure proper cleanup of resources.
-//
-// Initialize returns an error if provider creation fails, OTLP endpoint is unreachable,
-// or resource initialization encounters issues.
-func Initialize(ctx context.Context, cfg *Config, logger *slog.Logger) (*Provider, error) {
- if !cfg.Enabled {
- logger.Info("OpenTelemetry disabled")
- return &Provider{}, nil
- }
-
- // Create resource with service information
- // AIDEV-NOTE: Use resource.New() instead of resource.Merge() to avoid schema conflicts
- res, err := resource.New(ctx,
- resource.WithAttributes(
- semconv.ServiceName(cfg.ServiceName),
- semconv.ServiceVersion(cfg.ServiceVersion),
- semconv.ServiceInstanceID(getInstanceID()),
- ),
- resource.WithOS(),
- resource.WithContainer(),
- resource.WithHost(),
- )
- if err != nil {
- return nil, fmt.Errorf("failed to create resource: %w", err)
- }
-
- provider := &Provider{
- Resource: res,
- shutdownFuncs: make([]func(context.Context) error, 0),
- }
-
- // Initialize tracing
- if err := provider.initTracing(ctx, cfg, logger); err != nil {
- return nil, fmt.Errorf("failed to initialize tracing: %w", err)
- }
-
- // Initialize metrics
- if err := provider.initMetrics(ctx, cfg, logger); err != nil {
- return nil, fmt.Errorf("failed to initialize metrics: %w", err)
- }
-
- // Set global propagator
- otel.SetTextMapPropagator(propagation.TraceContext{})
-
- logger.Info("OpenTelemetry initialized",
- slog.String("service_name", cfg.ServiceName),
- slog.String("service_version", cfg.ServiceVersion),
- slog.String("endpoint", cfg.OTLPEndpoint),
- slog.Bool("prometheus_enabled", cfg.PrometheusEnabled),
- )
-
- return provider, nil
-}
-
-func (p *Provider) initTracing(ctx context.Context, cfg *Config, logger *slog.Logger) error {
- exporter, err := otlptrace.New(ctx,
- otlptracehttp.NewClient(
- otlptracehttp.WithEndpoint(cfg.OTLPEndpoint),
- otlptracehttp.WithInsecure(),
- ),
- )
- if err != nil {
- return fmt.Errorf("failed to create trace exporter: %w", err)
- }
-
- tp := sdktrace.NewTracerProvider(
- sdktrace.WithBatcher(exporter),
- sdktrace.WithResource(p.Resource),
- sdktrace.WithSampler(sdktrace.TraceIDRatioBased(cfg.TracingSamplingRate)),
- )
-
- otel.SetTracerProvider(tp)
- p.TracerProvider = tp
- p.addShutdownFunc(tp.Shutdown)
-
- return nil
-}
-
-func (p *Provider) initMetrics(ctx context.Context, cfg *Config, logger *slog.Logger) error {
- var readers []sdkmetric.Reader
-
- // Add Prometheus exporter if enabled
- if cfg.PrometheusEnabled {
- promExporter, err := prometheus.New()
- if err != nil {
- return fmt.Errorf("failed to create prometheus exporter: %w", err)
- }
- readers = append(readers, promExporter)
- }
-
- // Create meter provider with readers
- opts := []sdkmetric.Option{
- sdkmetric.WithResource(p.Resource),
- }
- for _, reader := range readers {
- opts = append(opts, sdkmetric.WithReader(reader))
- }
- mp := sdkmetric.NewMeterProvider(opts...)
-
- otel.SetMeterProvider(mp)
- p.MeterProvider = mp
- p.addShutdownFunc(mp.Shutdown)
-
- // Set up Prometheus HTTP handler if enabled
- if cfg.PrometheusEnabled {
- // The prometheus exporter automatically registers collectors with the default registry
- p.PrometheusHTTP = promhttp.Handler()
- }
-
- return nil
-}
-
-// Shutdown gracefully shuts down all telemetry providers and exporters.
-//
-// Shutdown should be called when the application terminates to ensure proper
-// cleanup and flushing of any pending telemetry data. It returns the first
-// error encountered during shutdown, but continues attempting to shut down
-// all providers even if some fail.
-func (p *Provider) Shutdown(ctx context.Context) error {
- p.mu.Lock()
- defer p.mu.Unlock()
-
- var firstErr error
- for _, fn := range p.shutdownFuncs {
- if err := fn(ctx); err != nil && firstErr == nil {
- firstErr = err
- }
- }
- return firstErr
-}
-
-// WrapHandler wraps an HTTP handler with OpenTelemetry tracing instrumentation.
-//
-// The operation parameter is used as the span name for requests handled by this handler.
-// When tracing is disabled, WrapHandler returns the original handler unchanged.
-func (p *Provider) WrapHandler(handler http.Handler, operation string) http.Handler {
- if p.TracerProvider == nil {
- return handler
- }
- return otelhttp.NewHandler(handler, operation)
-}
-
-func (p *Provider) addShutdownFunc(fn func(context.Context) error) {
- p.mu.Lock()
- defer p.mu.Unlock()
- p.shutdownFuncs = append(p.shutdownFuncs, fn)
-}
-
-func getInstanceID() string {
- hostname, err := os.Hostname()
- if err != nil {
- return "unknown"
- }
- return hostname
-}
diff --git a/go/deploy/pkg/tls/go.mod b/go/deploy/pkg/tls/go.mod
deleted file mode 100644
index c4910206bb..0000000000
--- a/go/deploy/pkg/tls/go.mod
+++ /dev/null
@@ -1,22 +0,0 @@
-module github.com/unkeyed/unkey/go/deploy/pkg/tls
-
-go 1.25
-
-toolchain go1.25.1
-
-require github.com/unkeyed/unkey/go/deploy/pkg/spiffe v0.0.0-00010101000000-000000000000
-
-replace github.com/unkeyed/unkey/go/deploy/pkg/spiffe => ../spiffe
-
-require (
- github.com/Microsoft/go-winio v0.6.2 // indirect
- github.com/go-jose/go-jose/v4 v4.1.2 // indirect
- github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
- golang.org/x/crypto v0.39.0 // indirect
- golang.org/x/net v0.41.0 // indirect
- golang.org/x/sys v0.33.0 // indirect
- golang.org/x/text v0.26.0 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
- google.golang.org/grpc v1.75.0 // indirect
- google.golang.org/protobuf v1.36.7 // indirect
-)
diff --git a/go/deploy/pkg/tls/go.sum b/go/deploy/pkg/tls/go.sum
deleted file mode 100644
index df79effe25..0000000000
--- a/go/deploy/pkg/tls/go.sum
+++ /dev/null
@@ -1,52 +0,0 @@
-github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
-github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
-github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
-github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
-github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
-github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
-github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
-github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
-github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
-github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
-github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
-github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
-github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
-github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
-github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
-github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
-go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
-go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
-go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
-go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
-go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
-go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
-go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
-go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
-go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
-go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
-golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
-golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
-golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
-golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
-golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
-golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
-golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
-golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
-gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
-gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
-google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
-google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
-google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
-google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/go/deploy/pkg/tracing/go.mod b/go/deploy/pkg/tracing/go.mod
deleted file mode 100644
index cffe6c25a0..0000000000
--- a/go/deploy/pkg/tracing/go.mod
+++ /dev/null
@@ -1,5 +0,0 @@
-module github.com/unkeyed/unkey/go/deploy/pkg/tracing
-
-go 1.25
-
-toolchain go1.25.1
diff --git a/go/deploy/pkg/tracing/naming.go b/go/deploy/pkg/tracing/naming.go
deleted file mode 100644
index 4a556c582f..0000000000
--- a/go/deploy/pkg/tracing/naming.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// Package tracing provides utilities for parsing and formatting distributed tracing span names
-// from Connect RPC procedure paths.
-//
-// This package standardizes span naming conventions across Unkey services by extracting
-// service and method information from RPC procedure paths and formatting them into
-// consistent span names for observability.
-//
-// Example usage:
-//
-// procedure := "/metald.v1.VmService/CreateVm"
-// method := tracing.ExtractMethodName(procedure) // "CreateVm"
-// service := tracing.ExtractServiceName(procedure) // "metald.v1.VmService"
-// span := tracing.FormatSpanName("metald", method) // "metald.CreateVm"
-package tracing
-
-import "strings"
-
-// ExtractMethodName extracts the method name from a Connect RPC procedure path.
-// It returns the last path segment after the final slash, or the entire procedure
-// string if no slash is found.
-//
-// Example:
-//
-// ExtractMethodName("/metald.v1.VmService/CreateVm") // returns "CreateVm"
-// ExtractMethodName("CreateVm") // returns "CreateVm"
-func ExtractMethodName(procedure string) string {
- parts := strings.Split(procedure, "/")
- if len(parts) > 0 {
- return parts[len(parts)-1]
- }
- return procedure
-}
-
-// ExtractServiceName extracts the service name from a Connect RPC procedure path.
-// It returns the first path segment after the leading slash, or an empty string
-// if the procedure path has fewer than two segments.
-//
-// Example:
-//
-// ExtractServiceName("/metald.v1.VmService/CreateVm") // returns "metald.v1.VmService"
-// ExtractServiceName("/CreateVm") // returns ""
-// ExtractServiceName("invalid") // returns ""
-func ExtractServiceName(procedure string) string {
- parts := strings.Split(procedure, "/")
- if len(parts) >= 2 {
- return parts[1]
- }
- return ""
-}
-
-// FormatSpanName creates a standardized span name by combining a service name
-// and method name with a dot separator. This provides consistent span naming
-// across all Unkey services for distributed tracing.
-//
-// Example:
-//
-// FormatSpanName("metald", "CreateVm") // returns "metald.CreateVm"
-// FormatSpanName("billaged", "GetUsage") // returns "billaged.GetUsage"
-func FormatSpanName(serviceName, methodName string) string {
- return serviceName + "." + methodName
-}
diff --git a/go/deploy/random.go b/go/deploy/random.go
deleted file mode 100644
index 012515e603..0000000000
--- a/go/deploy/random.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package main
-
-import (
- "fmt"
- "hash/fnv"
- "math"
-)
-
-// Original function with issues
-func CalculateNetworkMapIDOriginal(workspaceID string) uint32 {
- hash := fnv.New32a()
- hash.Write([]byte(workspaceID))
- return uint32(int(hash.Sum32()) % int(math.Pow(2, (28-12))))
-}
-
-// Improved version - returns 0 to 65535
-func CalculateNetworkMapID(workspaceID string) uint32 {
- hash := fnv.New32a()
- hash.Write([]byte(workspaceID))
- return hash.Sum32() % 65536 // or % (1 << 16)
-}
-
-// If you need 1-65536 instead of 0-65535
-func CalculateNetworkMapIDOneBased(workspaceID string) uint32 {
- hash := fnv.New32a()
- hash.Write([]byte(workspaceID))
- return (hash.Sum32() % 65536) + 1
-}
-
-// Even more efficient using bitwise operations
-func CalculateNetworkMapIDFast(workspaceID string) uint32 {
- hash := fnv.New32a()
- hash.Write([]byte(workspaceID))
- return hash.Sum32() & 0xFFFF // Masks to keep only lower 16 bits (0-65535)
-}
-
-// If you want to directly map to a subnet index (0-based)
-func GetSubnetIndex(workspaceID string) uint32 {
- hash := fnv.New32a()
- hash.Write([]byte(workspaceID))
- return hash.Sum32() & 0xFFFF // 0-65535
-}
-
-// Get the actual subnet CIDR for a workspace
-func GetSubnetForWorkspace(workspaceID string) string {
- index := GetSubnetIndex(workspaceID)
-
- // Each /28 subnet has 16 IPs
- // Calculate which subnet this index maps to
- subnetNumber := index * 16
-
- // Calculate octets for 172.16.0.0/12 base
- // We have 20 bits to work with (32-12)
- // The first 4 bits determine the second octet (16-31)
- // The next 8 bits determine the third octet (0-255)
- // The last 8 bits determine the fourth octet (0-255)
-
- totalOffset := subnetNumber
- octet4 := totalOffset % 256
- octet3 := (totalOffset / 256) % 256
- octet2 := 16 + (totalOffset / 65536)
-
- return fmt.Sprintf("172.%d.%d.%d/28", octet2, octet3, octet4)
-}
-
-func main() {
- testIDs := []string{
- "workspace-123",
- "workspace-456",
- "workspace-789",
- "test",
- "production",
- }
-
- fmt.Println("Workspace ID -> Subnet Mapping")
- fmt.Println("--------------------------------")
-
- for _, id := range testIDs {
- index := GetSubnetIndex(id)
- subnet := GetSubnetForWorkspace(id)
- fmt.Printf("%-15s -> Index: %5d, Subnet: %s\n", id, index, subnet)
- }
-
- // Test distribution
- fmt.Println("\n--- Distribution Test ---")
- testDistribution()
-}
-
-// Test the distribution quality of the hash function
-func testDistribution() {
- buckets := make(map[uint32]int)
- numTests := 100000
-
- for i := 0; i < numTests; i++ {
- workspaceID := fmt.Sprintf("workspace-%d", i)
- index := GetSubnetIndex(workspaceID)
- bucketIndex := index / 6554 // Divide into 10 buckets
- buckets[bucketIndex]++
- }
-
- fmt.Printf("Distribution across %d samples:\n", numTests)
- for i := uint32(0); i < 10; i++ {
- percentage := float64(buckets[i]) / float64(numTests) * 100
- fmt.Printf("Bucket %d: %.2f%%\n", i, percentage)
- }
-}
diff --git a/go/deploy/register-with-spire.sh b/go/deploy/register-with-spire.sh
deleted file mode 100755
index 1703a325ef..0000000000
--- a/go/deploy/register-with-spire.sh
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/bin/bash
-# register-with-spire.sh - Shared SPIRE registration script
-
-set -e
-
-if [ $# -lt 1 ]; then
- echo "Usage: $0 [dev]"
- echo "Example: $0 test-metald-client"
- echo "Example: $0 test-metald-client dev"
- exit 1
-fi
-
-SERVICE_NAME="$1"
-MODE="${2:-prod}"
-
-SPIFFE_ID="spiffe://development.unkey.app/client/${SERVICE_NAME}"
-PARENT_ID="spiffe://development.unkey.app/agent/$(hostname)"
-X509_TTL=3600 # 1 hour
-
-# Get current user and binary path
-USERNAME=$(whoami)
-BINARY_PATH=$(realpath "./build/${SERVICE_NAME}")
-
-if [ "$MODE" == "dev" ]; then
- echo "Registering ${SERVICE_NAME} in development mode (UID selector only)..."
- sudo /opt/spire/bin/spire-server entry create \
- -socketPath /var/lib/spire/server/server.sock \
- -spiffeID "${SPIFFE_ID}" \
- -parentID "${PARENT_ID}" \
- -selector "unix:uid:$(id -u)" \
- -x509SVIDTTL ${X509_TTL} \
- -admin
-else
- echo "Registering ${SERVICE_NAME} in production mode (user + path selectors)..."
- sudo /opt/spire/bin/spire-server entry create \
- -socketPath /var/lib/spire/server/server.sock \
- -spiffeID "${SPIFFE_ID}" \
- -parentID "${PARENT_ID}" \
- -selector "unix:user:${USERNAME}" \
- -selector "unix:path:${BINARY_PATH}" \
- -x509SVIDTTL ${X509_TTL} \
- -admin
-fi
-
-echo "Successfully registered ${SERVICE_NAME} with SPIRE"
-echo "SPIFFE ID: ${SPIFFE_ID}"
\ No newline at end of file
diff --git a/go/deploy/scripts/buf-lint.sh b/go/deploy/scripts/buf-lint.sh
deleted file mode 100755
index 94370a3040..0000000000
--- a/go/deploy/scripts/buf-lint.sh
+++ /dev/null
@@ -1,97 +0,0 @@
-#!/bin/bash
-set -euo pipefail
-
-# Enhanced Proto file linting and formatting script
-# Usage: ./scripts/lint-proto.sh
-
-file_path="${1:-}"
-
-if [[ -z "${file_path}" ]]; then
- echo "Usage: $0 "
- exit 1
-fi
-
-if [[ ! -f "${file_path}" ]]; then
- echo "Error: File '${file_path}' does not exist"
- exit 1
-fi
-
-if [[ "${file_path}" != *.proto ]]; then
- echo "Error: File '${file_path}' is not a proto file"
- exit 1
-fi
-
-echo "Processing proto file: ${file_path}"
-
-# Check if buf is available
-if ! command -v buf >/dev/null 2>&1; then
- echo "Error: buf command not found. Please install buf."
- echo "Install with: go install github.com/bufbuild/buf/cmd/buf@latest"
- exit 1
-fi
-
-# Step 1: Run buf format
-echo "Running buf format..."
-if buf format --write "${file_path}" 2>/dev/null; then
- echo "✓ buf format completed"
-else
- echo "ℹ buf format not available or failed (continuing)"
-fi
-
-# Step 2: Run buf lint (this is the critical check)
-echo "Running buf lint..."
-if buf lint "${file_path}"; then
- echo "✓ buf lint passed"
-else
- echo "✗ buf lint failed"
- exit 2 # Exit code 2 for linting failures
-fi
-
-# Step 3: Run buf breaking change detection
-echo "Running buf breaking change detection..."
-
-# Check if we're in a git repository
-if ! git rev-parse --git-dir >/dev/null 2>&1; then
- echo "ℹ not in a git repository, skipping breaking change detection"
-elif ! git rev-parse HEAD >/dev/null 2>&1; then
- echo "ℹ no commits found, skipping breaking change detection"
-elif ! git rev-parse HEAD~1 >/dev/null 2>&1; then
- echo "ℹ only one commit found, skipping breaking change detection"
-else
- # Try different breaking change detection strategies
- if buf breaking --against .git#branch=HEAD~1 "${file_path}" 2>/dev/null; then
- echo "✓ no breaking changes detected against HEAD~1"
- elif buf breaking --against .git#branch=main "${file_path}" 2>/dev/null; then
- echo "✓ no breaking changes detected against main branch"
- elif buf breaking --against .git#branch=master "${file_path}" 2>/dev/null; then
- echo "✓ no breaking changes detected against master branch"
- else
- echo "⚠ potential breaking changes detected (not blocking)"
- echo "ℹ run 'buf breaking --against .git#branch=main ${file_path}' manually for details"
- fi
-fi
-
-echo "Running buf breaking change detection..."
-
-# Check if we're in a git repository
-if ! git rev-parse --git-dir >/dev/null 2>&1; then
- echo "ℹ not in a git repository, skipping breaking change detection"
-elif ! git rev-parse HEAD >/dev/null 2>&1; then
- echo "ℹ no commits found, skipping breaking change detection"
-elif ! git rev-parse HEAD~1 >/dev/null 2>&1; then
- echo "ℹ only one commit found, skipping breaking change detection"
-else
- # Try different breaking change detection strategies
- if buf breaking --against .git#branch=HEAD~1 "${file_path}" 2>/dev/null; then
- echo "✓ no breaking changes detected against HEAD~1"
- elif buf breaking --against .git#branch=main "${file_path}" 2>/dev/null; then
- echo "✓ no breaking changes detected against main branch"
- elif buf breaking --against .git#branch=master "${file_path}" 2>/dev/null; then
- echo "✓ no breaking changes detected against master branch"
- else
- echo "⚠ potential breaking changes detected (not blocking)"
- echo "ℹ run 'buf breaking --against .git#branch=main ${file_path}' manually for details"
- fi
-fi
-
-echo "✓ Proto file processing completed: ${file_path}"
diff --git a/go/deploy/scripts/format-go.sh b/go/deploy/scripts/format-go.sh
deleted file mode 100755
index 7b36318b6b..0000000000
--- a/go/deploy/scripts/format-go.sh
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/bin/bash
-set -euo pipefail
-
-# Enhanced Go file formatting and linting script
-# Usage: ./scripts/format-go.sh
-
-file_path="${1:-}"
-
-if [[ -z "${file_path}" ]]; then
- echo "Usage: $0 "
- exit 1
-fi
-
-if [[ ! -f "${file_path}" ]]; then
- echo "Error: File '${file_path}' does not exist"
- exit 1
-fi
-
-if [[ "${file_path}" != *.go ]]; then
- echo "Error: File '${file_path}' is not a Go file"
- exit 1
-fi
-
-echo "Processing Go file: ${file_path}"
-
-# Step 1: Format with gofmt
-echo "Running gofmt..."
-if gofumpt -w "${file_path}"; then
- echo "✓ gofmt completed"
-else
- echo "✗ gofmt failed"
- exit 1
-fi
-
-# Step 2: Run goimports if available
-if command -v goimports >/dev/null 2>&1; then
- echo "Running goimports..."
- if goimports -w "${file_path}"; then
- echo "✓ goimports completed"
- else
- echo "✗ goimports failed"
- exit 1
- fi
-else
- echo "ℹ goimports not installed, skipping import formatting"
-fi
-
-# Step 3: Run go vet on the package
-if command -v go >/dev/null 2>&1; then
- dir=$(dirname "${file_path}")
- echo "Running go vet on package in $dir..."
- if go vet "$dir" 2>/dev/null; then
- echo "✓ go vet passed"
- else
- echo "⚠ go vet found issues (not blocking)"
- # Don't exit on vet issues, just warn
- fi
-else
- echo "ℹ go command not available, skipping vet"
-fi
-
-# Step 4: Optional: Run golangci-lint if available and configured
-if command -v golangci-lint >/dev/null 2>&1 && [[ -f .golangci.yml || -f .golangci.yaml ]]; then
- echo "Running golangci-lint..."
- if golangci-lint run "${file_path}" 2>/dev/null; then
- echo "✓ golangci-lint passed"
- else
- echo "⚠ golangci-lint found issues (not blocking)"
- fi
-fi
-
-echo "✓ Go file processing completed: ${file_path}"
diff --git a/go/deploy/scripts/install-buf.sh b/go/deploy/scripts/install-buf.sh
deleted file mode 100755
index b09b1b66c7..0000000000
--- a/go/deploy/scripts/install-buf.sh
+++ /dev/null
@@ -1,100 +0,0 @@
-#!/bin/bash
-# Install buf for protobuf generation
-# AIDEV-NOTE: Installs the buf CLI tool required for building services with protobuf
-
-set -euo pipefail
-
-# Configuration
-BUF_VERSION="${BUF_VERSION:-v1.57.0}"
-ARCH="${ARCH:-$(uname -m)}"
-OS="${OS:-$(uname -s)}"
-INSTALL_DIR="/usr/local/bin"
-
-# Color codes
-GREEN='\033[0;32m'
-RED='\033[0;31m'
-NC='\033[0m'
-
-# Map architecture names
-case "$ARCH" in
- x86_64|amd64)
- ARCH="x86_64"
- ;;
- aarch64|arm64)
- ARCH="aarch64"
- ;;
- *)
- echo -e "${RED}Error: Unsupported architecture: $ARCH${NC}"
- exit 1
- ;;
-esac
-
-# Map OS names
-case "$OS" in
- Linux|linux)
- OS="Linux"
- ;;
- Darwin|darwin)
- OS="Darwin"
- ;;
- *)
- echo -e "${RED}Error: Unsupported OS: $OS${NC}"
- exit 1
- ;;
-esac
-
-# Check for uninstall flag
-if [ "${1:-}" = "--uninstall" ]; then
- echo "Uninstalling buf..."
- if [ -f "$INSTALL_DIR/buf" ]; then
- sudo rm -f "$INSTALL_DIR/buf"
- echo -e "${GREEN}✓${NC} Removed buf"
- else
- echo "buf is not installed"
- fi
- exit 0
-fi
-
-echo "Installing buf ${BUF_VERSION} for ${OS}-${ARCH}..."
-
-# Download URL
-DOWNLOAD_URL="https://github.com/bufbuild/buf/releases/download/${BUF_VERSION}/buf-${OS}-${ARCH}"
-
-# Create temporary directory
-TEMP_DIR=$(mktemp -d)
-trap "rm -rf $TEMP_DIR" EXIT
-
-# Download buf
-echo "Downloading buf..."
-if ! curl -sL "$DOWNLOAD_URL" -o "$TEMP_DIR/buf"; then
- echo -e "${RED}Error: Failed to download buf from $DOWNLOAD_URL${NC}"
- exit 1
-fi
-
-# Make executable
-chmod +x "$TEMP_DIR/buf"
-
-# Verify download
-if ! "$TEMP_DIR/buf" --version >/dev/null 2>&1; then
- echo -e "${RED}Error: Downloaded binary is not valid${NC}"
- exit 1
-fi
-
-# Install
-echo "Installing buf to $INSTALL_DIR..."
-if [ "$EUID" -ne 0 ] && ! sudo -n true 2>/dev/null; then
- echo -e "${RED}Error: Installation requires root privileges${NC}"
- echo "Please run with sudo: sudo $0"
- exit 1
-fi
-
-sudo install -m 755 "$TEMP_DIR/buf" "$INSTALL_DIR/buf"
-
-# Verify installation
-if buf --version >/dev/null 2>&1; then
- echo -e "${GREEN}✓ buf installed successfully!${NC}"
- echo "Version: $(buf --version)"
-else
- echo -e "${RED}Error: buf installation verification failed${NC}"
- exit 1
-fi
diff --git a/go/deploy/scripts/install-firecracker.sh b/go/deploy/scripts/install-firecracker.sh
deleted file mode 100755
index 3da2282b27..0000000000
--- a/go/deploy/scripts/install-firecracker.sh
+++ /dev/null
@@ -1,200 +0,0 @@
-#!/bin/bash
-# Install or uninstall Firecracker and Jailer from GitHub releases
-
-set -euo pipefail
-
-# Configuration
-FIRECRACKER_VERSION="${FIRECRACKER_VERSION:-v1.13.0}"
-ARCH="${ARCH:-x86_64}"
-INSTALL_DIR="/usr/local/bin"
-
-# Color codes
-GREEN='\033[0;32m'
-RED='\033[0;31m'
-YELLOW='\033[1;33m'
-NC='\033[0m'
-
-# Check for uninstall flag
-if [ "${1:-}" = "--uninstall" ]; then
- echo "Uninstalling Firecracker..."
- if [ "$EUID" -ne 0 ] && ! sudo -n true 2>/dev/null; then
- echo -e "${RED}Error: Uninstall requires root privileges${NC}"
- echo "Please run with sudo: sudo $0 --uninstall"
- exit 1
- fi
-
- removed=0
- if [ -f "$INSTALL_DIR/firecracker" ]; then
- sudo rm -f "$INSTALL_DIR/firecracker"
- echo -e "${GREEN}✓${NC} Removed firecracker"
- removed=1
- fi
-
- # Ask about removing user and directories
- if [ $removed -eq 1 ]; then
- echo ""
- read -p "Remove firecracker user and directories? [y/N] " -n 1 -r
- echo
- if [[ $REPLY =~ ^[Yy]$ ]]; then
- if id -u firecracker >/dev/null 2>&1; then
- sudo userdel firecracker
- echo -e "${GREEN}✓${NC} Removed firecracker user"
- fi
-
- # Legacy directory - no longer used with assetmanagerd
- if [ -d "/var/lib/firecracker" ]; then
- sudo rm -rf /var/lib/firecracker
- echo -e "${GREEN}✓${NC} Removed /var/lib/firecracker (legacy)"
- fi
-
- if [ -d "/srv/jailer" ]; then
- sudo rm -rf /srv/jailer
- echo -e "${GREEN}✓${NC} Removed /srv/jailer"
- fi
-
- if [ -d "/sys/fs/cgroup/firecracker" ]; then
- sudo rmdir /sys/fs/cgroup/firecracker 2>/dev/null || true
- echo -e "${GREEN}✓${NC} Removed firecracker cgroup"
- fi
- fi
- fi
-
- if [ $removed -eq 0 ]; then
- echo "Firecracker was not installed"
- else
- echo -e "${GREEN}✓ Firecracker uninstalled successfully${NC}"
- fi
- exit 0
-fi
-
-echo "==================================="
-echo "Firecracker Installation"
-echo "==================================="
-echo "Version: $FIRECRACKER_VERSION"
-echo "Architecture: $ARCH"
-echo ""
-
-# Check if running as root or with sudo
-if [ "$EUID" -ne 0 ] && ! sudo -n true 2>/dev/null; then
- echo -e "${RED}Error: This script requires root privileges${NC}"
- echo "Please run with sudo: sudo $0"
- exit 1
-fi
-
-# Create temporary directory
-TEMP_DIR=$(mktemp -d)
-trap "rm -rf $TEMP_DIR" EXIT
-
-echo "Downloading Firecracker release..."
-RELEASE_URL="https://github.com/firecracker-microvm/firecracker/releases/download/${FIRECRACKER_VERSION}/firecracker-${FIRECRACKER_VERSION}-${ARCH}.tgz"
-
-# Download the release
-if ! curl -sL "$RELEASE_URL" -o "$TEMP_DIR/firecracker.tgz"; then
- echo -e "${RED}Error: Failed to download Firecracker from $RELEASE_URL${NC}"
- echo "Please check the version and try again."
- exit 1
-fi
-
-# Extract the tarball
-echo "Extracting Firecracker..."
-cd "$TEMP_DIR"
-if ! tar -xzf firecracker.tgz; then
- echo -e "${RED}Error: Failed to extract Firecracker archive${NC}"
- exit 1
-fi
-
-# Find the release directory
-RELEASE_DIR=$(find . -type d -name "release-${FIRECRACKER_VERSION}-${ARCH}" | head -1)
-if [ -z "$RELEASE_DIR" ]; then
- echo -e "${RED}Error: Could not find release directory${NC}"
- echo "Archive contents:"
- tar -tzf firecracker.tgz
- exit 1
-fi
-
-# Install firecracker binary
-echo "Installing firecracker binary..."
-if [ -f "$RELEASE_DIR/firecracker-${FIRECRACKER_VERSION}-${ARCH}" ]; then
- sudo install -m 755 "$RELEASE_DIR/firecracker-${FIRECRACKER_VERSION}-${ARCH}" "$INSTALL_DIR/firecracker"
- echo -e "${GREEN}✓${NC} Installed firecracker to $INSTALL_DIR/firecracker"
-else
- echo -e "${RED}Error: firecracker binary not found in release${NC}"
- exit 1
-fi
-
-# Verify installation
-echo ""
-echo "Verifying installation..."
-if firecracker --version >/dev/null 2>&1; then
- echo -e "${GREEN}✓${NC} firecracker: $(firecracker --version)"
-else
- echo -e "${RED}✗${NC} firecracker verification failed"
-fi
-
-# Check KVM access
-echo ""
-echo "Checking KVM access..."
-if [ -e /dev/kvm ]; then
- if [ -r /dev/kvm ] && [ -w /dev/kvm ]; then
- echo -e "${GREEN}✓${NC} KVM is accessible"
- else
- echo -e "${YELLOW}⚠${NC} KVM exists but may not be accessible to current user"
- echo " You may need to add your user to the kvm group:"
- echo " sudo usermod -aG kvm $USER"
- fi
-else
- echo -e "${RED}✗${NC} /dev/kvm not found - virtualization may not be enabled"
-fi
-
-# Set up jailer requirements for production
-echo ""
-echo "Setting up jailer requirements..."
-
-# Create jailer directory structure
-echo -n "Creating jailer directories... "
-sudo mkdir -p /srv/jailer
-# Note: VM assets are now managed by assetmanagerd in /opt/vm-assets
-echo -e "${GREEN}✓${NC}"
-
-# Configure cgroup v2 if needed
-echo ""
-echo "Checking cgroup configuration..."
-# Check if cgroup v2 is active by looking for the controllers file
-if [ -f /sys/fs/cgroup/cgroup.controllers ]; then
- echo -e "${GREEN}✓${NC} cgroup v2 detected"
-
- # Show available controllers
- controllers=$(cat /sys/fs/cgroup/cgroup.controllers)
- echo "Available controllers: $controllers"
-
- # Create a cgroup for firecracker if it doesn't exist
- if [ ! -d /sys/fs/cgroup/firecracker ]; then
- echo -n "Creating firecracker cgroup... "
- sudo mkdir -p /sys/fs/cgroup/firecracker
- echo -e "${GREEN}✓${NC}"
- fi
-else
- echo -e "${YELLOW}⚠${NC} cgroup v1 detected. Firecracker will work but cgroup v2 is recommended"
-
- # Check if the system can support cgroup v2
- if grep -q cgroup2 /proc/filesystems; then
- echo ""
- echo "Your system supports cgroup v2. To enable it (optional):"
- echo ""
- echo "For systemd-based systems (Fedora/Ubuntu):"
- echo " 1. Add kernel parameter:"
- echo " sudo grubby --update-kernel=ALL --args='systemd.unified_cgroup_hierarchy=1'"
- echo " 2. Reboot your system"
- echo ""
- echo "Note: Firecracker works fine with cgroup v1, this is just a recommendation."
- echo "For Fedora 31+ and Ubuntu 21.10+, cgroup v2 is usually the default."
- fi
-fi
-
-echo ""
-echo "==================================="
-echo -e "${GREEN}✓ Firecracker installation and setup complete!${NC}"
-echo "==================================="
-echo ""
-echo "Installed components:"
-echo " - firecracker: $INSTALL_DIR/firecracker"
diff --git a/go/deploy/spire/Makefile b/go/deploy/spire/Makefile
deleted file mode 100644
index bbde5b34b9..0000000000
--- a/go/deploy/spire/Makefile
+++ /dev/null
@@ -1,254 +0,0 @@
-# SPIRE Installation Makefile
-# Installs SPIRE server and agent as systemd services
-
-# Variables
-SPIRE_VERSION ?= 1.13.0
-SPIRE_ARCH ?= linux-amd64-musl
-SPIRE_URL = https://github.com/spiffe/spire/releases/download/v$(SPIRE_VERSION)/spire-$(SPIRE_VERSION)-$(SPIRE_ARCH).tar.gz
-SPIRE_INSTALL_DIR = /opt/spire
-SPIRE_DATA_DIR = /var/lib/spire
-SPIRE_CONFIG_DIR = /etc/spire
-
-# Environment selection (default to development)
-SPIRE_ENVIRONMENT ?= development
-
-# Validate environment
-ifeq ($(filter $(SPIRE_ENVIRONMENT),development production),)
-$(error Invalid SPIRE_ENVIRONMENT: $(SPIRE_ENVIRONMENT). Must be one of: development, production)
-endif
-
-# Trust domain mapping
-ifeq ($(SPIRE_ENVIRONMENT),development)
-TRUST_DOMAIN = development.unkey.cloud
-else ifeq ($(SPIRE_ENVIRONMENT),production)
-TRUST_DOMAIN = production.unkey.cliud
-endif
-
-# Targets (alphabetically ordered)
-
-.PHONY: bootstrap-agent
-bootstrap-agent: ## Bootstrap agent with server bundle
- @echo "Bootstrapping agent with server bundle..."
- @if ! sudo systemctl is-active --quiet spire-server; then \
- echo "Error: SPIRE server is not running. Start it first with 'make service-start-server'"; \
- exit 1; \
- fi
- @echo "Waiting for SPIRE server socket to be ready..."
- @for i in 1 2 3 4 5 6 7 8 9 10; do \
- if [ -S /var/lib/spire/server/server.sock ]; then \
- echo "Server socket is ready"; \
- break; \
- fi; \
- echo "Waiting for server socket... ($$i/10)"; \
- sleep 2; \
- done
- @if [ ! -S /var/lib/spire/server/server.sock ]; then \
- echo "Error: Server socket not available after 20 seconds"; \
- exit 1; \
- fi
- @sudo bash -c '$(SPIRE_INSTALL_DIR)/bin/spire-server bundle show \
- -socketPath /var/lib/spire/server/server.sock \
- -format pem > /tmp/bootstrap.crt' || \
- (echo "Error: Failed to get bundle from server. Check server logs with 'make service-logs-server'" && exit 1)
- @sudo mv /tmp/bootstrap.crt $(SPIRE_CONFIG_DIR)/agent/bootstrap.crt
- @sudo chmod 600 $(SPIRE_CONFIG_DIR)/agent/bootstrap.crt
- @echo "Agent bootstrap bundle created."
-
-.PHONY: clean
-clean: ## Clean up temporary files
- @rm -rf /tmp/spire-install
-
-.PHONY: clean-all
-clean-all: ## Completely remove SPIRE (data, configs)
- @echo "Stopping and removing SPIRE services..."
- @sudo systemctl stop spire-server spire-agent 2>/dev/null || true
- @sudo systemctl disable spire-server spire-agent 2>/dev/null || true
- @sudo rm -f /etc/systemd/system/spire-server.service
- @sudo rm -f /etc/systemd/system/spire-agent.service
- @sudo rm -rf /etc/systemd/system/spire-server.service.d
- @sudo rm -rf /etc/systemd/system/spire-agent.service.d
- @sudo systemctl daemon-reload
- @echo "Removing SPIRE binaries..."
- @sudo rm -rf $(SPIRE_INSTALL_DIR)
- @echo "Removing SPIRE data and configs..."
- @sudo rm -rf $(SPIRE_DATA_DIR)
- @sudo rm -rf $(SPIRE_CONFIG_DIR)
- @echo "Removing SPIRE users..."
- @sudo userdel -r spire-server 2>/dev/null || true
- @sudo userdel -r spire-agent 2>/dev/null || true
- @echo "SPIRE completely removed."
-
-.PHONY: create-join-token
-create-join-token: ## Create join token for agent
- @sudo $(SPIRE_INSTALL_DIR)/bin/spire-server token generate \
- -socketPath /var/lib/spire/server/server.sock \
- -spiffeID spiffe://$(TRUST_DOMAIN)/agent/node1
-
-.PHONY: deregister-services
-deregister-services: ## Deregister all Unkey services from SPIRE
- @TRUST_DOMAIN=$(TRUST_DOMAIN) ./scripts/deregister-services.sh
-
-.PHONY: download-spire
-download-spire: ## Download SPIRE binaries
- @if [ ! -f /tmp/spire-install/spire-$(SPIRE_VERSION)/bin/spire-agent ]; then \
- rm -rf /tmp/spire-install/spire-$(SPIRE_VERSION); \
- mkdir -p /tmp/spire-install; \
- curl -sL $(SPIRE_URL) | tar xz -C /tmp/spire-install; \
- fi
-
-.PHONY: help
-help: ## Show this help message
- @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
- @echo ""
- @echo "Available environments: development, production"
-
-.PHONY: install
-install: install-server install-agent ## Install both SPIRE server and agent
- @echo "Next steps: 'make service-start-server', then 'make register-agent', then 'make register-services'"
-
-.PHONY: install-agent
-install-agent: download-spire setup-directories ## Install SPIRE agent
- @sudo systemctl stop spire-agent 2>/dev/null || true
- @sudo rm -f $(SPIRE_INSTALL_DIR)/bin/spire-agent
- @sudo cp contrib/systemd/spire-agent.service /etc/systemd/system/spire-agent.service
- @sudo cp /tmp/spire-install/spire-$(SPIRE_VERSION)/bin/spire-agent $(SPIRE_INSTALL_DIR)/bin/
- @sudo chmod +x $(SPIRE_INSTALL_DIR)/bin/spire-agent
- @sudo cp contrib/bin/spire-agent-wrapper.sh $(SPIRE_INSTALL_DIR)/bin/
- @sudo chmod +x $(SPIRE_INSTALL_DIR)/bin/spire-agent-wrapper.sh
- @sudo cp environments/$(SPIRE_ENVIRONMENT)/agent.conf $(SPIRE_CONFIG_DIR)/agent/agent.conf
- @sudo chmod 700 $(SPIRE_DATA_DIR)/agent/keys
- @sudo mkdir -p /etc/systemd/system/spire-agent.service.d
- @sudo cp contrib/systemd/spire-agent.service.d/auto-join.conf /etc/systemd/system/spire-agent.service.d/
- @sudo systemctl daemon-reload
- @sudo systemctl enable spire-agent >/dev/null 2>&1
- @echo "✓ SPIRE agent installed ($(SPIRE_ENVIRONMENT): $(TRUST_DOMAIN))"
- @echo "Next: Start server with 'make service-start-server', then 'make register-agent'"
-
-.PHONY: install-server
-install-server: download-spire create-users setup-directories ## Install SPIRE server
- @sudo systemctl stop spire-server 2>/dev/null || true
- @sudo rm -f $(SPIRE_INSTALL_DIR)/bin/spire-server
- @sudo cp /tmp/spire-install/spire-$(SPIRE_VERSION)/bin/spire-server $(SPIRE_INSTALL_DIR)/bin/
- @sudo chmod +x $(SPIRE_INSTALL_DIR)/bin/spire-server
- @sudo cp environments/$(SPIRE_ENVIRONMENT)/server.conf $(SPIRE_CONFIG_DIR)/server/server.conf
- @sudo cp contrib/systemd/spire-server.service /etc/systemd/system/
- @sudo systemctl daemon-reload
- @sudo systemctl enable spire-server >/dev/null 2>&1
- @echo "✓ SPIRE server installed ($(SPIRE_ENVIRONMENT): $(TRUST_DOMAIN))"
-
-.PHONY: list-entries
-list-entries: ## List all registered entries
- @sudo $(SPIRE_INSTALL_DIR)/bin/spire-server entry show \
- -socketPath /var/lib/spire/server/server.sock
-
-.PHONY: register-agent
-register-agent: ## Register agent with join token (one-time setup)
- @./scripts/register-agent.sh
-
-.PHONY: register-services
-register-services: ## Register all Unkey services with SPIRE
- @TRUST_DOMAIN=$(TRUST_DOMAIN) ./scripts/register-services.sh
-
-.PHONY: service-logs
-service-logs: ## Follow logs for both services
- @sudo journalctl -u spire-server -u spire-agent -f
-
-.PHONY: service-logs-agent
-service-logs-agent: ## Follow SPIRE agent logs
- @sudo journalctl -u spire-agent -f
-
-.PHONY: service-logs-server
-service-logs-server: ## Follow SPIRE server logs
- @sudo journalctl -u spire-server -f
-
-.PHONY: service-restart
-service-restart: service-restart-server service-restart-agent ## Restart both
-
-.PHONY: service-restart-agent
-service-restart-agent: ## Restart SPIRE agent
- @sudo systemctl restart spire-agent
- @echo "✓ SPIRE agent restarted"
-
-.PHONY: service-restart-server
-service-restart-server: ## Restart SPIRE server
- @sudo systemctl restart spire-server
- @echo "✓ SPIRE server restarted"
-
-.PHONY: service-start
-service-start: service-start-server service-start-agent ## Start both server and agent
-
-.PHONY: service-start-agent
-service-start-agent: ## Start SPIRE agent
- @sudo systemctl start spire-agent
- @echo "✓ SPIRE agent started"
-
-.PHONY: service-start-server
-service-start-server: ## Start SPIRE server
- @sudo systemctl start spire-server
- @echo "✓ SPIRE server started"
-
-.PHONY: service-status
-service-status: ## Check status of both services
- @echo "=== SPIRE Server ==="
- @sudo systemctl status spire-server --no-pager || true
- @echo ""
- @echo "=== SPIRE Agent ==="
- @sudo systemctl status spire-agent --no-pager || true
-
-.PHONY: service-status-agent
-service-status-agent: ## Check SPIRE agent status
- @sudo systemctl status spire-agent
-
-.PHONY: service-status-server
-service-status-server: ## Check SPIRE server status
- @sudo systemctl status spire-server
-
-.PHONY: service-stop
-service-stop: service-stop-agent service-stop-server ## Stop both agent and server
-
-.PHONY: service-stop-agent
-service-stop-agent: ## Stop SPIRE agent
- @sudo systemctl stop spire-agent
- @echo "✓ SPIRE agent stopped"
-
-.PHONY: service-stop-server
-service-stop-server: ## Stop SPIRE server
- @sudo systemctl stop spire-server
- @echo "✓ SPIRE server stopped"
-
-.PHONY: setup-agent
-setup-agent: ## Setup agent with join token
- @./scripts/register-agent.sh
-
-.PHONY: setup-directories
-setup-directories: ## Create SPIRE directories
- @sudo mkdir -p $(SPIRE_INSTALL_DIR)/bin
- @sudo mkdir -p $(SPIRE_INSTALL_DIR)/scripts
- @sudo mkdir -p $(SPIRE_CONFIG_DIR)/server
- @sudo mkdir -p $(SPIRE_CONFIG_DIR)/agent
- @sudo mkdir -p $(SPIRE_DATA_DIR)/server
- @sudo mkdir -p $(SPIRE_DATA_DIR)/agent
- @sudo mkdir -p $(SPIRE_DATA_DIR)/agent/keys
-
-.PHONY: uninstall
-uninstall: uninstall-server uninstall-agent ## Uninstall both SPIRE server and agent
-
-.PHONY: uninstall-agent
-uninstall-agent: ## Uninstall SPIRE agent
- @sudo systemctl stop spire-agent 2>/dev/null || true
- @sudo systemctl disable spire-agent 2>/dev/null || true
- @sudo rm -f /etc/systemd/system/spire-agent.service
- @sudo rm -rf /etc/systemd/system/spire-agent.service.d
- @sudo rm -f $(SPIRE_INSTALL_DIR)/bin/spire-agent
- @sudo rm -f $(SPIRE_INSTALL_DIR)/bin/spire-agent-wrapper.sh
- @sudo systemctl daemon-reload
- @echo "✓ SPIRE agent uninstalled (data preserved)"
-
-.PHONY: uninstall-server
-uninstall-server: ## Uninstall SPIRE server
- @sudo systemctl stop spire-server 2>/dev/null || true
- @sudo systemctl disable spire-server 2>/dev/null || true
- @sudo rm -f /etc/systemd/system/spire-server.service
- @sudo rm -f $(SPIRE_INSTALL_DIR)/bin/spire-server
- @sudo systemctl daemon-reload
- @echo "✓ SPIRE server uninstalled (data preserved)"
diff --git a/go/deploy/spire/agent/spire-agent.conf b/go/deploy/spire/agent/spire-agent.conf
deleted file mode 100644
index 8594602298..0000000000
--- a/go/deploy/spire/agent/spire-agent.conf
+++ /dev/null
@@ -1,87 +0,0 @@
-# SPIRE Agent Configuration
-# Communicates with SPIRE server to obtain and rotate certificates
-
-agent {
- data_dir = "/var/lib/spire/agent/data"
- log_level = "${UNKEY_SPIRE_LOG_LEVEL:-INFO}"
- log_format = "json"
-
- # Agent connects to server via HTTPS
- server_address = "${UNKEY_SPIRE_SERVER_URL:-https://localhost:8085}"
- socket_path = "/var/lib/spire/agent/agent.sock"
-
- trust_domain = "${UNKEY_SPIRE_TRUST_DOMAIN:-development.unkey.cloud}"
-
- # This file must be distributed securely to agents
- trust_bundle_path = "/etc/spire/agent/bundle.crt"
-
- # Allow workloads to request SVIDs without authentication
- # Security comes from workload attestation
- authorized_delegates = []
-
- # Enable only if needed for debugging
- # admin_socket_path = "/run/spire/agent-admin.sock"
-
- sync_interval = "30s"
-
- max_concurrent_attestations = 10
-}
-
-plugins {
- # Production should use platform-specific attestors (aws_iid, gcp_iit, etc.)
- NodeAttestor "join_token" {
- plugin_data {
- # Token provided via UNKEY_SPIRE_JOIN_TOKEN environment variable
- # Enables automatic joining on startup
- }
- }
-
- # Keys are automatically rotated by SPIRE
- KeyManager "disk" {
- plugin_data {
- directory = "/var/lib/spire/agent/keys"
- }
- }
-
- # Essential for identifying workloads by binary path and user
- WorkloadAttestor "unix" {
- plugin_data {
- discover_workload_path = true
- discover_workload_user = true
- discover_workload_group = true
- }
- }
-
- # Critical for identifying systemd-managed services
- WorkloadAttestor "systemd" {
- plugin_data {
- # Enable PID tracking for accurate service identification
- pid_path = "/run/spire/systemd-pids"
- }
- }
-
- # Only workloads from same trust domain can connect
- SVIDStore "aws_secretsmanager" {
- plugin_data {
- # Optional: Store SVIDs in AWS Secrets Manager for backup
- # region = "${AWS_REGION}"
- # secret_prefix = "spire/svids/"
- }
- }
-}
-
-health_checks {
- listener_enabled = true
- bind_address = "127.0.0.1"
- live_path = "/live"
- ready_path = "/ready"
-}
-
-telemetry {
- Prometheus {
- host = "127.0.0.1"
- port = 9989
- }
-
- # metric_labels = [{service = "spire-agent"}]
-}
diff --git a/go/deploy/spire/contrib/bin/spire-agent-wrapper.sh b/go/deploy/spire/contrib/bin/spire-agent-wrapper.sh
deleted file mode 100644
index 491835afb8..0000000000
--- a/go/deploy/spire/contrib/bin/spire-agent-wrapper.sh
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/bin/bash
-# This wrapper handles automatic joining with server using join tokens
-# Environment variables are passed from systemd service
-
-set -euo pipefail
-
-# Configuration from environment variables (set by systemd)
-TRUST_DOMAIN=${UNKEY_SPIRE_TRUST_DOMAIN:-development.unkey.cloud}
-LOG_LEVEL=${UNKEY_SPIRE_LOG_LEVEL:-INFO}
-SERVER_URL=${UNKEY_SPIRE_SERVER_URL:-https://localhost:8085}
-JOIN_TOKEN=${UNKEY_SPIRE_JOIN_TOKEN:-}
-
-SPIRE_AGENT="/opt/spire/bin/spire-agent"
-CONFIG_FILE="/etc/spire/agent/agent.conf"
-LOG_FILE="/var/log/spire-agent.log"
-
-# Ensure log directory exists
-mkdir -p "$(dirname "$LOG_FILE")"
-
-log() {
- echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
-}
-
-log "Starting SPIRE Agent wrapper"
-log "Trust Domain: $TRUST_DOMAIN"
-log "Log Level: $LOG_LEVEL"
-log "Server URL: $SERVER_URL"
-
-# Check if spire-agent binary exists
-if [ ! -x "$SPIRE_AGENT" ]; then
- log "ERROR: SPIRE agent binary not found at $SPIRE_AGENT"
- exit 1
-fi
-
-# Check if config file exists
-if [ ! -f "$CONFIG_FILE" ]; then
- log "ERROR: SPIRE agent config not found at $CONFIG_FILE"
- exit 1
-fi
-
-# Build agent command
-AGENT_CMD=("$SPIRE_AGENT" "run" "-config" "$CONFIG_FILE")
-
-# Add join token if provided
-if [ -n "$JOIN_TOKEN" ]; then
- log "Using join token for authentication"
- AGENT_CMD+=("-joinToken" "$JOIN_TOKEN")
-else
- log "No join token provided - using bootstrap bundle"
-fi
-
-log "Starting SPIRE agent: ${AGENT_CMD[*]}"
-
-# Execute the agent
-exec "${AGENT_CMD[@]}"
diff --git a/go/deploy/spire/contrib/systemd/spire-agent.service b/go/deploy/spire/contrib/systemd/spire-agent.service
deleted file mode 100644
index 9b4d18af3f..0000000000
--- a/go/deploy/spire/contrib/systemd/spire-agent.service
+++ /dev/null
@@ -1,61 +0,0 @@
-[Unit]
-Description=SPIRE Agent
-Documentation=https://spiffe.io/docs/latest/
-After=network.target
-Wants=network-online.target
-# Server connection will retry if not available
-
-[Service]
-Type=simple
-User=root
-Group=root
-
-# StateDirectory creates persistent directories under /var/lib
-StateDirectory=spire/agent spire/agent/data spire/agent/keys
-StateDirectoryMode=0755
-
-ExecStartPre=/bin/bash -c 'chmod 700 /var/lib/spire/agent/keys'
-ExecStart=/opt/spire/bin/spire-agent-wrapper.sh
-
-ExecStop=/bin/kill -SIGTERM $MAINPID
-TimeoutStopSec=30
-
-Restart=on-failure
-RestartSec=5
-TimeoutStartSec=30s
-StandardOutput=journal
-StandardError=journal
-SyslogIdentifier=spire-agent
-
-NoNewPrivileges=true
-PrivateTmp=no
-ProtectSystem=no
-ProtectHome=no
-ReadWritePaths=/var/lib/spire/agent
-ReadOnlyPaths=/usr/bin /usr/local/bin /opt
-# Keep some basic protections
-RestrictRealtime=true
-LockPersonality=true
-RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
-ProtectKernelTunables=true
-ProtectKernelModules=true
-ProtectControlGroups=true
-RestrictRealtime=true
-RestrictSUIDSGID=true
-LockPersonality=true
-MemoryDenyWriteExecute=true
-RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
-SystemCallArchitectures=native
-SystemCallFilter=@system-service
-
-# SupplementaryGroups would be added here if services run under separate users
-
-Environment="UNKEY_SPIRE_TRUST_DOMAIN=development.unkey.cloud"
-Environment="UNKEY_SPIRE_LOG_LEVEL=INFO"
-Environment="UNKEY_SPIRE_SERVER_URL=https://localhost:8085"
-# Set this in environment-specific drop-ins for security
-# Environment="UNKEY_SPIRE_JOIN_TOKEN=your-long-lived-token-here"
-# e.g., /etc/systemd/system/spire-agent.service.d/environment.conf
-
-[Install]
-WantedBy=multi-user.target
diff --git a/go/deploy/spire/contrib/systemd/spire-agent.service.d/auto-join.conf b/go/deploy/spire/contrib/systemd/spire-agent.service.d/auto-join.conf
deleted file mode 100644
index 8d733a5c4e..0000000000
--- a/go/deploy/spire/contrib/systemd/spire-agent.service.d/auto-join.conf
+++ /dev/null
@@ -1,4 +0,0 @@
-[Service]
-# This file provides the join token for automatic agent registration
-Environment="UNKEY_SPIRE_JOIN_TOKEN="
-Environment="UNKEY_SPIRE_TRUST_DOMAIN=development.unkey.cloud"
diff --git a/go/deploy/spire/contrib/systemd/spire-server.service b/go/deploy/spire/contrib/systemd/spire-server.service
deleted file mode 100644
index fe1c2f4776..0000000000
--- a/go/deploy/spire/contrib/systemd/spire-server.service
+++ /dev/null
@@ -1,56 +0,0 @@
-[Unit]
-Description=SPIRE Server
-Documentation=https://spiffe.io/docs/latest/
-After=network.target
-Wants=network-online.target
-
-[Service]
-Type=simple
-User=root
-Group=root
-
-# RuntimeDirectory creates /run/spire (cleared on reboot) - keeping for backward compatibility
-RuntimeDirectory=spire
-RuntimeDirectoryMode=0755
-RuntimeDirectoryPreserve=yes
-# StateDirectory creates /var/lib/spire (persistent)
-StateDirectory=spire/server spire/server/data spire/server/keys
-StateDirectoryMode=0755
-# ConfigurationDirectory would create /etc/spire but we manage this separately
-# ConfigurationDirectory=spire/server
-
-ExecStartPre=/bin/bash -c 'chmod 700 /var/lib/spire/server/keys || true'
-
-ExecStart=/opt/spire/bin/spire-server run -config /etc/spire/server/server.conf
-
-ExecStop=/bin/kill -SIGTERM $MAINPID
-TimeoutStopSec=30
-
-Restart=on-failure
-RestartSec=5
-TimeoutStartSec=30s
-StandardOutput=journal
-StandardError=journal
-SyslogIdentifier=spire-server
-
-# Most security features disabled to prevent namespace isolation
-NoNewPrivileges=true
-PrivateTmp=no
-ProtectSystem=no
-ProtectHome=no
-ReadWritePaths=/var/lib/spire/server /run/spire
-# Keep some basic protections
-RestrictRealtime=true
-LockPersonality=true
-RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
-
-# Trust domain must be set per environment
-Environment="UNKEY_SPIRE_TRUST_DOMAIN=development.unkey.cloud"
-Environment="UNKEY_SPIRE_LOG_LEVEL=INFO"
-Environment="UNKEY_SPIRE_DB_TYPE=sqlite3"
-Environment="UNKEY_SPIRE_DB_CONNECTION=/var/lib/spire/server/data/datastore.sqlite3"
-# e.g., /etc/systemd/system/spire-server.service.d/environment.conf
-# Optional: Set UNKEY_SPIRE_TRUST_BUNDLE if using external trust bundle
-
-[Install]
-WantedBy=multi-user.target
diff --git a/go/deploy/spire/docs/README.md b/go/deploy/spire/docs/README.md
deleted file mode 100644
index b8c7aaf05f..0000000000
--- a/go/deploy/spire/docs/README.md
+++ /dev/null
@@ -1,203 +0,0 @@
-# SPIRE: Secure Service Identity for Unkey Deploy
-
-## What is SPIRE?
-
-SPIRE (SPIFFE Runtime Environment) is the production-ready implementation of SPIFFE that provides automatic, cryptographically-verifiable service identities. It eliminates the need for manual certificate management in our microservices architecture.
-
-## The Problem SPIRE Solves
-
-### Traditional Certificate Management Pain Points
-- **Manual certificate distribution**: Copying cert files to each service
-- **Certificate rotation nightmares**: Expired certs breaking production at 3am
-- **Security risks**: Long-lived certificates sitting in files
-- **Operational overhead**: Complex automation scripts for cert lifecycle
-- **Trust boundaries**: Hard to verify which service is really calling you
-
-### How SPIRE Eliminates These Problems
-- **Zero certificate files**: Everything happens in memory via APIs
-- **Automatic rotation**: Certificates refresh every hour automatically
-- **Strong workload identity**: Services proven by process attestation, not just file possession
-- **Dynamic trust**: Policy-based access control with runtime verification
-- **Simplified operations**: Deploy once, identities work forever
-
-## Architecture Overview
-
-```
-┌─────────────────────┐
-│ SPIRE Server │ ← Central trust authority
-│ (Trust Root CA) │ ← Issues short-lived certificates
-└──────────┬──────────┘ ← Manages service registrations
- │
- ┌──────┴──────┐
- │ │
-┌───▼───┐ ┌───▼───┐
-│ Host 1 │ │ Host 2 │
-├────────┤ ├────────┤
-│ Agent │ │ Agent │ ← Verifies workload identity
-├────────┤ ├────────┤ ← Delivers certificates to services
-│metald │ │builderd│ ← Services get automatic mTLS
-│billaged│ │assetmgr│ ← No certificate files needed
-└────────┘ └────────┘
-```
-
-## How It Fits Into Our Architecture
-
-### Service Communication Flow
-1. **Service Startup**: Each service (metald, billaged, etc.) contacts local SPIRE agent
-2. **Identity Verification**: Agent verifies service identity using process attestation
-3. **Certificate Delivery**: Agent provides short-lived X.509 certificate with SPIFFE ID
-4. **Secure Communication**: Services use certificates for automatic mTLS
-5. **Automatic Renewal**: Certificates refresh every hour without service restart
-
-### Integration with Unkey Services
-
-#### Core Services Using SPIRE
-- **metald**: VM management service with identity `spiffe://production.unkey.cloud/service/metald`
-- **billaged**: Billing aggregation service with identity `spiffe://production.unkey.cloud/service/billaged`
-- **builderd**: Container build service with identity `spiffe://production.unkey.cloud/service/builderd`
-- **assetmanagerd**: Asset management service with identity `spiffe://production.unkey.cloud/service/assetmanagerd`
-
-#### Multi-Tenant Identity Patterns
-```
-# Service-level identity (most common)
-spiffe://production.unkey.cloud/service/metald
-
-# Customer-scoped identity (for VM processes)
-spiffe://production.unkey.cloud/service/metald/customer/cust-123
-
-# Tenant-scoped identity (for build isolation)
-spiffe://production.unkey.cloud/service/builderd/tenant/acme-corp
-```
-
-## Environment Isolation Strategy
-
-### Separate Trust Domains
-Each environment has its own trust domain for complete cryptographic isolation:
-
-- **Development**: `spiffe://development.unkey.cloud` - Fast iteration, verbose logging
-- **Canary**: `spiffe://canary.unkey.cloud` - Production-like testing environment
-- **Production**: `spiffe://production.unkey.cloud` - Hardened configuration, HA deployment
-
-### Why Separate Trust Domains?
-1. **Security**: Services in different environments cannot communicate even if misconfigured
-2. **Clarity**: Easy to identify which environment a certificate belongs to
-3. **Compliance**: Clear security boundaries for audit purposes
-4. **Simplicity**: No complex ACL rules - trust domain provides natural isolation
-
-## Key Benefits for Developers
-
-### Before SPIRE: Manual Certificate Hell
-```go
-// Old way - manual certificate management
-cert, err := tls.LoadX509KeyPair("/etc/service/cert.pem", "/etc/service/key.pem")
-if err != nil {
- log.Fatal("Certificate file missing or expired!")
-}
-
-client := &http.Client{
- Transport: &http.Transport{
- TLSClientConfig: &tls.Config{
- Certificates: []tls.Certificate{cert},
- RootCAs: loadCA("/etc/service/ca.pem"),
- },
- },
-}
-```
-
-### After SPIRE: Automatic Everything
-```go
-// New way - automatic identity and mTLS
-spiffeClient, err := spiffe.New(ctx)
-if err != nil {
- log.Fatal("SPIRE agent not available")
-}
-
-// HTTP client with automatic mTLS - no certificates!
-client := spiffeClient.HTTPClient()
-resp, err := client.Get("https://billaged:8081/api/usage")
-```
-
-### Operational Benefits
-- **No cert files to manage**: Everything handled in memory
-- **No rotation scripts**: Certificates refresh automatically every hour
-- **No midnight pages**: Expired certificates can't break production
-- **Strong security**: Process-based attestation ensures service authenticity
-- **Better debugging**: Full audit trail of all service communications
-
-## Security Model
-
-### Workload Attestation
-SPIRE verifies service identity using multiple factors:
-- **Process path**: `/usr/bin/metald`
-- **User/group**: `unkey-metald:unkey-metald`
-- **Systemd unit**: `metald.service`
-- **Cgroup hierarchy**: Systemd-managed processes
-
-### Certificate Lifecycle
-- **TTL**: 1 hour (production), 5 minutes (development)
-- **Rotation**: Automatic renewal at 50% of TTL
-- **Revocation**: Immediate when workload stops or registration changes
-- **Validation**: Continuous verification of workload identity
-
-### Trust Bundle Management
-- **Root CA**: Managed by SPIRE server with 1-year TTL
-- **Intermediate CAs**: Automatic rotation for zero-downtime updates
-- **Cross-environment isolation**: Separate CAs per trust domain
-
-## Production Deployment Considerations
-
-### High Availability
-- **SPIRE Server**: Deploy in HA mode with shared database
-- **Database**: PostgreSQL with replication for registration data
-- **Key Management**: AWS KMS for hardware-backed key storage
-- **Monitoring**: Prometheus metrics for certificate issuance and rotation
-
-### Scaling
-- **SPIRE Agents**: One per host/node, lightweight resource usage
-- **Certificate caching**: Agent caches certificates locally
-- **Registration entries**: Centrally managed via SPIRE server API
-- **Backup/Recovery**: Database backups include all registration state
-
-## Quick Reference
-
-### Common Commands
-```bash
-# Check service SVID
-sudo -u unkey-metald spire-agent api fetch x509 -socketPath /run/spire/sockets/agent.sock
-
-# Register new service
-spire-server entry create \
- -spiffeID spiffe://production.unkey.cloud/service/newservice \
- -parentID spiffe://production.unkey.cloud/agent/server \
- -selector unix:path:/usr/bin/newservice \
- -selector unix:user:unkey-newservice
-
-# View all registrations
-spire-server entry show
-```
-
-### Directory Structure
-```
-spire/
-├── environments/ # Per-environment configurations
-│ ├── dev/ # Development settings
-│ ├── canary/ # Canary environment
-│ └── prod/ # Production configuration
-├── agent/ # Agent configuration templates
-├── contrib/ # Systemd units and helpers
-├── scripts/ # Automation and setup scripts
-└── docs/ # This documentation
-```
-
-## Related Documentation
-
-- [Understanding SPIFFE](./UNDERSTANDING_SPIFFE.md) - Developer guide to SPIFFE concepts
-- [Architecture Details](./architecture.md) - Technical implementation details
-- [Environment Configurations](../environments/README.md) - Per-environment setup guide
-
-## Further Reading
-
-- **SPIFFE Specification**: https://spiffe.io/docs/latest/spiffe/
-- **SPIRE Documentation**: https://spiffe.io/docs/latest/spire/
-- **Production Deployment Guide**: https://spiffe.io/docs/latest/planning/production/
-- **Community Support**: https://spiffe.slack.com
diff --git a/go/deploy/spire/docs/UNDERSTANDING_SPIFFE.md b/go/deploy/spire/docs/UNDERSTANDING_SPIFFE.md
deleted file mode 100644
index e83a4e1a08..0000000000
--- a/go/deploy/spire/docs/UNDERSTANDING_SPIFFE.md
+++ /dev/null
@@ -1,222 +0,0 @@
-# Understanding SPIFFE/SPIRE: A Developer's Guide
-
-## The Problem with Traditional PKI
-
-Traditional certificate management is like managing physical keys:
-- You create keys (generate certificates)
-- You copy keys (distribute cert files)
-- You worry about lost keys (compromised certs)
-- You change locks periodically (rotate certificates)
-- Someone forgets to change locks (cert expires in production)
-
-## Enter SPIFFE: Identity for Services
-
-SPIFFE (Secure Production Identity Framework For Everyone) reimagines service identity:
-- Services have identities, not just certificates
-- Identities are verified continuously, not just at creation
-- Credentials rotate automatically, like changing passwords every hour
-- No files to manage, everything happens in memory
-
-## Core Concepts Explained
-
-### SPIFFE ID: Your Service's Name Tag
-```
-spiffe://unkey.prod/service/metald
- │ │ │ │
- │ │ │ └── Specific service
- │ │ └────────── Category
- │ └────────────────────── Your trust domain
- └────────────────────────────── Always starts with spiffe://
-```
-
-Think of it like an email address for services:
-- `john@company.com` → `spiffe://company.com/user/john`
-- `metald@unkey` → `spiffe://unkey.prod/service/metald`
-
-### SVID: Your Service's ID Card
-
-SVID (SPIFFE Verifiable Identity Document) is like an employee ID card:
-- Contains the service's SPIFFE ID
-- Cryptographically signed by company (SPIRE)
-- Expires quickly (1 hour) for security
-- Automatically renewed before expiry
-
-### Workload: Any Running Process
-
-In SPIFFE terms, a "workload" is just your running service:
-- `metald` process = workload
-- Docker container = workload
-- Kubernetes pod = workload
-- Lambda function = workload
-
-### Attestation: Proving Who You Are
-
-Attestation is how SPIRE verifies a service's identity:
-
-**Like a bouncer checking IDs:**
-- "What's your process path?" → `/usr/bin/metald` ✓
-- "What systemd unit?" → `metald.service` ✓
-- "What user are you?" → `unkey-metald` ✓
-- "Here's your SVID!" → 🎫
-
-## How It Works: The Airport Security Analogy
-
-1. **Check-In (Registration)**
- - You register services with SPIRE like checking in for a flight
- - "metald service runs at /usr/bin/metald as user unkey-metald"
-
-2. **Security Check (Attestation)**
- - Service connects to SPIRE agent
- - Agent verifies the service matches registration
- - Like TSA checking your boarding pass matches your ID
-
-3. **Boarding Pass (SVID)**
- - Service receives time-limited credential
- - Like a boarding pass that expires in 1 hour
- - Automatically renewed if you're still at the gate
-
-4. **Flight (Service Communication)**
- - Services show their SVIDs to each other
- - Both verify the other's identity
- - Encrypted communication established
-
-## Real-World Example: metald → billaged
-
-### Traditional Way
-```go
-// metald code - OLD WAY
-cert, _ := tls.LoadX509KeyPair("/etc/metald/cert.pem", "/etc/metald/key.pem")
-client := &http.Client{
- Transport: &http.Transport{
- TLSClientConfig: &tls.Config{
- Certificates: []tls.Certificate{cert},
- RootCAs: loadCA("/etc/metald/ca.pem"),
- },
- },
-}
-resp, _ := client.Get("https://billaged:8081/api/usage")
-```
-
-**Problems:**
-- Where do cert files come from?
-- Who rotates them?
-- How does billaged know it's really metald?
-
-### SPIFFE Way
-```go
-// metald code - NEW WAY
-spiffeClient, _ := spiffe.New(ctx)
-client := spiffeClient.HTTPClient()
-resp, _ := client.Get("https://billaged:8081/api/usage")
-```
-
-**Benefits:**
-- No certificate files
-- Automatic rotation
-- Strong identity verification
-
-## The Magic: What Happens Behind the Scenes
-
-```mermaid
-sequenceDiagram
- participant metald
- participant Agent as SPIRE Agent
- participant Server as SPIRE Server
- participant billaged
-
- Note over metald: Service starts up
- metald->>Agent: Hi, I need an identity
- Agent->>Agent: Check: process path? user? systemd unit?
- Agent->>Server: metald matches these selectors
- Server->>Server: Lookup registration entry
- Server->>Agent: Here's SVID for spiffe://unkey.prod/service/metald
- Agent->>metald: Your SVID (expires in 1hr)
-
- Note over metald,billaged: Making API call
- metald->>billaged: HTTPS request with SVID
- billaged->>billaged: Verify SVID signature
- billaged->>billaged: Check: Is this metald? Allowed?
- billaged->>metald: Here's your response
-
- Note over metald,Agent: 50 minutes later...
- Agent->>metald: Here's your renewed SVID
- metald->>metald: Seamlessly use new credential
-```
-
-## Why This Matters for Developers
-
-### No More Certificate Nightmares
-- ❌ "The cert expired and broke production"
-- ❌ "Where do I put the cert files?"
-- ❌ "How do I rotate certificates?"
-- ✅ It just works™
-
-### Better Security By Default
-- Short-lived credentials (1 hour vs 1 year)
-- Automatic rotation (no human errors)
-- Strong workload identity (not just having a file)
-
-### Simplified Operations
-```bash
-# Old way
-1. Generate CA
-2. Generate service certs
-3. Copy certs to servers
-4. Configure services
-5. Setup rotation scripts
-6. Monitor expiry
-7. Panic at 3am
-
-# SPIFFE way
-1. Deploy SPIRE
-2. Register services
-3. Done
-```
-
-## Common Questions
-
-**Q: What if SPIRE server goes down?**
-A: Services keep their current SVIDs until expiry (1hr). Deploy SPIRE in HA mode for production.
-
-**Q: Can I still use regular TLS?**
-A: Yes! Services can accept both SPIFFE and traditional certs during migration.
-
-**Q: How is this different from service mesh?**
-A: SPIFFE provides identity. Service meshes (Istio, Linkerd) often use SPIFFE underneath.
-
-**Q: Do I need Kubernetes?**
-A: No! SPIFFE works great with systemd services, containers, VMs, or bare metal.
-
-## Debugging Tips
-
-### Check if service has SVID
-```bash
-# As the service user
-sudo -u unkey-metald spire-agent api fetch x509 \
- -socketPath /run/spire/sockets/agent.sock
-```
-
-### View SVID details
-```bash
-# See the actual certificate
-sudo -u unkey-metald spire-agent api fetch x509 \
- -socketPath /run/spire/sockets/agent.sock \
- -write /tmp/svid.pem
-
-openssl x509 -in /tmp/svid.pem -text -noout
-```
-
-### Monitor SVID rotation
-```bash
-# Watch SVIDs refresh
-watch -n 10 'spire-agent api fetch x509 | grep "SPIFFE ID"'
-```
-
-## Further Learning
-
-1. **Interactive Tutorial**: https://play.instruqt.com/spiffe
-2. **Concepts Deep Dive**: https://spiffe.io/book/
-3. **Production Guide**: https://spiffe.io/docs/latest/planning/production/
-4. **Community Slack**: https://spiffe.slack.com
-
-Remember: SPIFFE/SPIRE is just solving certificate management automatically. Your services still speak normal TLS - they just don't manage certificates anymore!
\ No newline at end of file
diff --git a/go/deploy/spire/docs/architecture.md b/go/deploy/spire/docs/architecture.md
deleted file mode 100644
index 5b13d6e31c..0000000000
--- a/go/deploy/spire/docs/architecture.md
+++ /dev/null
@@ -1,93 +0,0 @@
-# SPIFFE/SPIRE Architecture for Unkey Services
-
-## Overview
-
-SPIFFE/SPIRE provides automatic, cryptographically-verifiable service identities without manual certificate management.
-
-## Components
-
-### SPIRE Server
-- Central trust root
-- Issues SVIDs (SPIFFE Verifiable Identity Documents)
-- Manages registration entries
-- Runs on dedicated host or container
-
-### SPIRE Agents
-- One per host/node
-- Attests workload identity
-- Delivers SVIDs to workloads
-- Handles automatic rotation
-
-### Workloads (Your Services)
-- metald, billaged, builderd, assetmanagerd
-- Use Workload API to get SVIDs
-- Automatic mTLS with no certificate files
-
-## Deployment Topology
-
-```
-┌─────────────────────┐
-│ SPIRE Server │
-│ (Trust Authority) │
-└──────────┬──────────┘
- │
- ┌──────┴──────┐
- │ │
-┌───▼───┐ ┌───▼───┐
-│ Host 1 │ │ Host 2 │
-├────────┤ ├────────┤
-│ Agent │ │ Agent │
-├────────┤ ├────────┤
-│metald │ │builderd│
-│billaged│ │assetmgr│
-└────────┘ └────────┘
-```
-
-## Identity Scheme
-
-### Service Identities
-- Path: `/service/{name}`
-- Example: `spiffe://unkey.prod/service/metald`
-
-### Customer-Scoped Identities
-- Path: `/service/{name}/customer/{id}`
-- Example: `spiffe://unkey.prod/service/metald/customer/cust-123`
-- Used for VM-specific processes
-
-### Tenant-Scoped Identities
-- Path: `/service/{name}/tenant/{id}`
-- Example: `spiffe://unkey.prod/service/builderd/tenant/acme-corp`
-- Used for multi-tenant isolation
-
-## Workload Attestation
-
-### Linux Process Attestation
-- Binary path: `/usr/bin/metald`
-- User/Group: `unkey-metald:unkey-metald`
-- Systemd cgroup matching
-
-### Kubernetes Attestation (Future)
-- Namespace + Service Account
-- Pod labels/annotations
-
-## Benefits Over Traditional PKI
-
-1. **Zero Certificate Management**
- - No files to distribute
- - No manual rotation
- - No passphrase management
-
-2. **Dynamic Authorization**
- - Policy-based access control
- - Runtime identity verification
- - Automatic revocation
-
-3. **Observability**
- - Full audit trail
- - Metrics on all mTLS connections
- - Identity-based tracing
-
-4. **Security**
- - Short-lived credentials (1 hour)
- - Hardware-backed attestation
- - No long-lived secrets
\ No newline at end of file
diff --git a/go/deploy/spire/environments/README.md b/go/deploy/spire/environments/README.md
deleted file mode 100644
index adc0ee015e..0000000000
--- a/go/deploy/spire/environments/README.md
+++ /dev/null
@@ -1,67 +0,0 @@
-# SPIRE Environment Configurations
-
-This directory contains SPIRE configurations for each environment, implementing our trust domain isolation strategy.
-
-## Trust Domain Strategy
-
-Each environment has its own trust domain to ensure complete cryptographic isolation:
-
-- **Development**: `spiffe://development.unkey.cloud`
-- **Canary**: `spiffe://canary.unkey.cloud`
-- **Production**: `spiffe://production.unkey.cloud`
-
-## Why Separate Trust Domains?
-
-1. **Security**: Services in different environments cannot communicate, even if misconfigured
-2. **Clarity**: Easy to identify which environment a certificate belongs to
-3. **Compliance**: Clear security boundaries for audit purposes
-4. **Simplicity**: No complex ACL rules needed - trust domain provides isolation
-
-## Directory Structure
-
-```
-environments/
-├── dev/
-│ ├── server.conf # SPIRE server config for dev
-│ ├── agent.conf # SPIRE agent config for dev
-│ └── registrations/ # Workload registrations for dev
-├── canary/
-│ ├── server.conf # SPIRE server config for canary
-│ ├── agent.conf # SPIRE agent config for canary
-│ └── registrations/ # Workload registrations for canary
-└── prod/
- ├── server.conf # SPIRE server config for production
- ├── agent.conf # SPIRE agent config for production
- └── registrations/ # Workload registrations for production
-```
-
-## Deployment
-
-Each environment should have its own SPIRE deployment:
-
-```bash
-# Development
-kubectl apply -f environments/dev/
-
-# Canary
-kubectl apply -f environments/canary/
-
-# Production
-kubectl apply -f environments/prod/
-```
-
-## Service Names
-
-Services keep the same logical names across environments:
-- `metald`
-- `billaged`
-- `builderd`
-- `assetmanagerd`
-
-The full SPIFFE ID includes the environment via trust domain:
-- Dev: `spiffe://development.unkey.cloud/service/metald`
-- Prod: `spiffe://production.unkey.cloud/service/metald`
-
-## Note on DNS
-
-The trust domains (dev.unkey.cloud, production.unkey.cloud) do **NOT** need to be real DNS names. They are just identifiers used within SPIFFE/SPIRE.
diff --git a/go/deploy/spire/environments/development/agent.conf b/go/deploy/spire/environments/development/agent.conf
deleted file mode 100644
index 3e262a9413..0000000000
--- a/go/deploy/spire/environments/development/agent.conf
+++ /dev/null
@@ -1,70 +0,0 @@
-# SPIRE Agent Configuration - Development Environment
-
-agent {
- data_dir = "/var/lib/spire/agent/data"
- log_level = "DEBUG"
- log_format = "json"
-
- # Server connection configuration
- server_address = "127.0.0.1"
- server_port = "8085"
- socket_path = "/var/lib/spire/agent/agent.sock"
-
- # Trust domain from environment
- trust_domain = "development.unkey.cloud"
-
- # Using join token with insecure bootstrap for development
- insecure_bootstrap = true
-
- # Workload API configuration
- authorized_delegates = []
-}
-
-plugins {
- # Join token attestation for development
- NodeAttestor "join_token" {
- plugin_data {
- # Long-lived token for development auto-join
- # Token provided via UNKEY_SPIRE_JOIN_TOKEN environment variable
- }
- }
-
- # Disk-based key storage
- KeyManager "disk" {
- plugin_data {
- directory = "/var/lib/spire/agent/keys"
- }
- }
-
- # Unix workload attestor for process-based identity
- WorkloadAttestor "unix" {
- plugin_data {
- discover_workload_path = true
- discover_workload_user = true
- discover_workload_group = true
- }
- }
-
- # Docker workload attestor for container-based identity
- WorkloadAttestor "docker" {
- plugin_data {
- # Docker socket for container inspection
- docker_socket_path = "unix:///var/run/docker.sock"
- }
- }
-}
-
-health_checks {
- listener_enabled = true
- bind_address = "127.0.0.1"
- live_path = "/live"
- ready_path = "/ready"
-}
-
-# Development telemetry with verbose metrics
-telemetry {
- Prometheus {
- host = "127.0.0.1"
- port = 9989
- }
-}
diff --git a/go/deploy/spire/environments/development/server.conf b/go/deploy/spire/environments/development/server.conf
deleted file mode 100644
index 3127443ac8..0000000000
--- a/go/deploy/spire/environments/development/server.conf
+++ /dev/null
@@ -1,56 +0,0 @@
-# SPIRE Server Configuration - Development Environment
-
-server {
- bind_address = "127.0.0.1"
- socket_path = "/var/lib/spire/server/server.sock"
- trust_domain = "development.unkey.cloud"
- data_dir = "/var/lib/spire/server/data"
- log_level = "DEBUG"
- log_format = "text" # Human-readable for development
-
- default_x509_svid_ttl = "5m"
- default_jwt_svid_ttl = "5m"
-
- ca_ttl = "8760h"
- ca_key_type = "ec-p256"
- ca_subject = {
- country = ["US"],
- organization = ["Unkey"],
- common_name = "Unkey Development CA",
- }
-
- audit_log_enabled = true
-}
-
-plugins {
- DataStore "sql" {
- plugin_data {
- database_type = "sqlite3"
- connection_string = "/var/lib/spire/server/data/datastore.sqlite3"
- }
- }
-
- NodeAttestor "join_token" {
- plugin_data {}
- }
-
- KeyManager "disk" {
- plugin_data {
- keys_path = "/var/lib/spire/server/keys/keys.json"
- }
- }
-}
-
-health_checks {
- listener_enabled = true
- bind_address = "127.0.0.1"
- live_path = "/live"
- ready_path = "/ready"
-}
-
-telemetry {
- Prometheus {
- host = "127.0.0.1"
- port = 9988
- }
-}
diff --git a/go/deploy/spire/environments/production/agent.conf b/go/deploy/spire/environments/production/agent.conf
deleted file mode 100644
index 8ba56fa8c0..0000000000
--- a/go/deploy/spire/environments/production/agent.conf
+++ /dev/null
@@ -1,80 +0,0 @@
-# SPIRE Agent Configuration - Production Environment
-
-agent {
- data_dir = "/var/lib/spire/agent/data"
- log_level = "WARN"
- log_format = "json"
-
- # Server connection configuration
- server_address = "127.0.0.1"
- server_port = "8085"
- socket_path = "/var/lib/spire/agent/agent.sock"
-
- # Trust domain from environment
- trust_domain = "production.unkey.cloud"
-
- # Bootstrap bundle for initial trust
- trust_bundle_path = "/etc/spire/agent/bootstrap.crt"
-
- # Workload API configuration
- authorized_delegates = []
-}
-
-plugins {
- # Production node attestation - choose based on platform
-
- # Option 1: AWS Instance Identity (recommended for AWS EC2)
- # NodeAttestor "aws_iid" {
- # plugin_data {
- # account_id = "${AWS_ACCOUNT_ID}"
- # instance_profile_arn = "${SPIRE_AGENT_INSTANCE_PROFILE_ARN}"
- # }
- # }
-
- # Option 2: GCP Instance Identity (for GCP)
- # NodeAttestor "gcp_iit" {
- # plugin_data {
- # project_id = "${GCP_PROJECT_ID}"
- # service_account = "${SPIRE_AGENT_SERVICE_ACCOUNT}"
- # }
- # }
-
- # Option 3: Join token fallback (less secure, but works everywhere)
- NodeAttestor "join_token" {
- plugin_data {
- # Token provided via UNKEY_SPIRE_JOIN_TOKEN environment variable
- # Consider enabling node attestation above for better security
- }
- }
-
- # Disk-based key storage
- KeyManager "disk" {
- plugin_data {
- directory = "/var/lib/spire/agent/keys"
- }
- }
-
- # Unix workload attestor for process-based identity
- WorkloadAttestor "unix" {
- plugin_data {
- discover_workload_path = true
- discover_workload_user = true
- discover_workload_group = true
- }
- }
-}
-
-health_checks {
- listener_enabled = true
- bind_address = "127.0.0.1"
- live_path = "/live"
- ready_path = "/ready"
-}
-
-# Production telemetry
-telemetry {
- Prometheus {
- host = "127.0.0.1"
- port = 9989
- }
-}
diff --git a/go/deploy/spire/environments/production/server.conf b/go/deploy/spire/environments/production/server.conf
deleted file mode 100644
index eb16aff1a1..0000000000
--- a/go/deploy/spire/environments/production/server.conf
+++ /dev/null
@@ -1,99 +0,0 @@
-# SPIRE Server Configuration - Production Environment
-
-server {
- # Consider using a load balancer or service mesh for external access
- bind_address = "127.0.0.1"
- socket_path = "/var/lib/spire/server/server.sock"
- trust_domain = "production.unkey.cloud"
- data_dir = "/var/lib/spire/server/data"
- log_level = "INFO"
- log_format = "json" # Structured logging for production
-
- default_x509_svid_ttl = "1h"
- default_jwt_svid_ttl = "5m"
-
- ca_ttl = "8760h"
- ca_key_type = "ec-p256"
- ca_subject = {
- country = ["US"],
- organization = ["Unkey"],
- common_name = "Unkey Production CA",
- }
-
- audit_log_enabled = true
-
- # federation {
- # bundle_endpoint {
- # address = "https://spire-bundle.unkey.cloud"
- # port = 443
- # }
- # }
-}
-
-plugins {
- DataStore "sql" {
- plugin_data {
- database_type = "postgres"
- connection_string = "${UNKEY_SPIRE_DB_CONNECTION}"
-
- # Connection pool configuration
- max_open_conns = 20
- max_idle_conns = 10
- conn_max_lifetime = "300s"
- }
- }
-
- NodeAttestor "aws_iid" {
- plugin_data {
- # AWS SDK will use instance profile automatically
- # trust_domain inherited from server config
-
- account_allowlist = ["${UNKEY_AWS_ACCOUNT_ID}"]
-
- # instance_allowlist = ["i-*"]
- }
- }
-
- NodeAttestor "join_token" {
- plugin_data {}
- }
-
- KeyManager "aws_kms" {
- plugin_data {
- region = "${AWS_REGION:-us-east-1}"
- key_metadata_file = "/etc/spire/server/kms-keys.json"
- # Uses IAM role, no explicit credentials needed
- }
- }
-
- # UpstreamAuthority "aws_pca" {
- # plugin_data {
- # region = "${AWS_REGION}"
- # certificate_authority_arn = "${UNKEY_PCA_ARN}"
- # }
- # }
-}
-
-health_checks {
- listener_enabled = true
- bind_address = "127.0.0.1"
- live_path = "/live"
- ready_path = "/ready"
-}
-
-telemetry {
- Prometheus {
- host = "127.0.0.1"
- port = 9988
- }
-
- metric_labels = [
- {env = "prod"},
- {service = "spire-server"},
- {region = "${AWS_REGION:-us-east-1}"}
- ]
-
- # DogStatsd = [{
- # address = "127.0.0.1:8125"
- # }]
-}
diff --git a/go/deploy/spire/scripts/deregister-services.sh b/go/deploy/spire/scripts/deregister-services.sh
deleted file mode 100755
index 366d6e350e..0000000000
--- a/go/deploy/spire/scripts/deregister-services.sh
+++ /dev/null
@@ -1,82 +0,0 @@
-#!/bin/bash
-# Deregisters all Unkey services
-
-set -euo pipefail
-
-# Get trust domain from environment or use default
-TRUST_DOMAIN=${TRUST_DOMAIN:-development.unkey.cloud}
-SPIRE_DIR="/opt/spire"
-SOCKET_PATH="/var/lib/spire/server/server.sock"
-
-# Colors for output
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-RED='\033[0;31m'
-BLUE='\033[0;34m'
-NC='\033[0m' # No Color
-
-echo -e "${GREEN}=== SPIRE Service Deregistration ===${NC}"
-echo -e "Trust Domain: ${YELLOW}${TRUST_DOMAIN}${NC}"
-
-# Check if server is running
-#if ! systemctl is-active --quiet spire-server; then
-# echo -e "${RED}Error: SPIRE server is not running${NC}"
-# echo "Start it with: sudo systemctl start spire-server"
-# exit 1
-#fi
-
-# Wait for server socket
-if [ ! -S "$SOCKET_PATH" ]; then
- echo -e "${RED}Error: Server socket not available${NC}"
- exit 1
-fi
-
-# Function to deregister a service
-deregister_service() {
- local service_name=$1
- local spiffe_id="spiffe://${TRUST_DOMAIN}/service/${service_name}"
-
- echo -e "\n${BLUE}Deregistering ${service_name}...${NC}"
- echo -e "\n${BLUE}spiffeid: ${spiffe_id}${NC}"
- # Find entry ID for the service
- local entry_id=$(sudo ${SPIRE_DIR}/bin/spire-server entry show \
- -socketPath "$SOCKET_PATH" \
- -spiffeID "$spiffe_id" 2>/dev/null | grep "Entry ID" | awk '{print $NF}')
-
- if [ -z "$entry_id" ]; then
- echo -e "${YELLOW}✓ ${service_name} not registered${NC}"
- return 0
- fi
- echo -e "${GREEN}✓ ${service_name} registered${NC}"
-
- # Delete the entry
- sudo ${SPIRE_DIR}/bin/spire-server entry delete \
- -socketPath "$SOCKET_PATH" \
- -entryID "$entry_id" \
- || {
- echo -e "${RED}✗ Failed to deregister ${service_name}${NC}"
- return 1
- }
-
- echo -e "${GREEN}✓ ${service_name} deregistered${NC}"
-}
-
-# Deregister all services
-deregister_service "metald"
-deregister_service "billaged"
-deregister_service "builderd"
-deregister_service "assetmanagerd"
-deregister_service "metald-cli"
-deregister_service "assetmanagerd-cli"
-deregister_service "billaged-cli"
-deregister_service "builderd-cli"
-deregister_service "metald-client"
-
-# List remaining registered entries
-echo -e "\n${YELLOW}=== Remaining Registered Services ===${NC}"
-sudo ${SPIRE_DIR}/bin/spire-server entry show \
- -socketPath "$SOCKET_PATH" \
- -parentID "spiffe://${TRUST_DOMAIN}/agent/node1" \
- | grep -E "(Entry ID|SPIFFE ID|Selector)" || echo -e "${GREEN}No services remaining${NC}"
-
-echo -e "\n${GREEN}✓ Service deregistration complete!${NC}"
diff --git a/go/deploy/spire/scripts/register-agent.sh b/go/deploy/spire/scripts/register-agent.sh
deleted file mode 100755
index c1990ca3e4..0000000000
--- a/go/deploy/spire/scripts/register-agent.sh
+++ /dev/null
@@ -1,144 +0,0 @@
-#!/bin/bash
-# For development: creates a long-lived token that enables auto-joining on startup
-# For production: creates shorter-lived tokens with node attestation
-
-set -euo pipefail
-
-# Get trust domain from environment or use default
-TRUST_DOMAIN=${UNKEY_SPIRE_TRUST_DOMAIN:-development.unkey.cloud}
-ENVIRONMENT=${SPIRE_ENVIRONMENT:-development}
-SPIRE_DIR="/opt/spire"
-AGENT_SERVICE_DIR="/etc/systemd/system/spire-agent.service.d"
-
-# Colors for output
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-RED='\033[0;31m'
-NC='\033[0m' # No Color
-
-echo -e "${GREEN}=== SPIRE Agent Auto-Join Setup ===${NC}"
-echo -e "Environment: ${YELLOW}${ENVIRONMENT}${NC}"
-echo -e "Trust Domain: ${YELLOW}${TRUST_DOMAIN}${NC}"
-
-# Check if server is running
-if ! systemctl is-active --quiet spire-server; then
- echo -e "${RED}Error: SPIRE server is not running${NC}"
- echo "Start it with: sudo systemctl start spire-server"
- exit 1
-fi
-
-# Wait for server socket
-echo "Waiting for SPIRE server socket..."
-for i in {1..10}; do
- if [ -S /var/lib/spire/server/server.sock ]; then
- echo -e "${GREEN}Server socket ready${NC}"
- break
- fi
- echo "Waiting... ($i/10)"
- sleep 2
-done
-
-if [ ! -S /var/lib/spire/server/server.sock ]; then
- echo -e "${RED}Error: Server socket not available after 20 seconds${NC}"
- exit 1
-fi
-
-# Check if auto-join is already configured
-if [ -f "$AGENT_SERVICE_DIR/auto-join.conf" ]; then
- echo -e "${YELLOW}Auto-join already configured${NC}"
- echo "Checking if agent is running..."
-
- if systemctl is-active --quiet spire-agent; then
- echo -e "${GREEN}✓ Agent is running with auto-join${NC}"
- exit 0
- fi
-fi
-
-# Generate join token
-echo "Generating join token..."
-
-# For development, create very long-lived token (1 year)
-# For production, shorter-lived tokens are recommended
-if [ "$ENVIRONMENT" = "development" ]; then
- TTL="31536000" # 1 year in seconds
- echo -e "${YELLOW}Creating long-lived token for development (1 year)${NC}"
-else
- TTL="3600" # 1 hour in seconds
- echo -e "${YELLOW}Creating token for ${ENVIRONMENT} (1 hour)${NC}"
-fi
-
-JOIN_TOKEN=$(sudo ${SPIRE_DIR}/bin/spire-server token generate \
- -socketPath /var/lib/spire/server/server.sock \
- -spiffeID spiffe://${TRUST_DOMAIN}/agent/node1 \
- -ttl ${TTL} \
- | grep "Token:" | cut -d' ' -f2)
-
-if [ -z "$JOIN_TOKEN" ]; then
- echo -e "${RED}Error: Failed to generate join token${NC}"
- exit 1
-fi
-
-echo -e "${GREEN}Join token generated${NC}"
-
-# Configure systemd for auto-join
-echo "Setting up auto-join configuration..."
-sudo mkdir -p "$AGENT_SERVICE_DIR"
-
-# Update auto-join environment configuration with the token
-cat < /dev/null
-[Service]
-# This file provides the join token for automatic agent registration
-Environment="UNKEY_SPIRE_JOIN_TOKEN=${JOIN_TOKEN}"
-Environment="UNKEY_SPIRE_TRUST_DOMAIN=${TRUST_DOMAIN}"
-EOF
-
-# Reload systemd and start agent
-sudo systemctl daemon-reload
-
-# Enable auto-start
-sudo systemctl enable spire-agent
-
-# Start agent
-echo "Starting SPIRE agent with auto-join..."
-sudo systemctl restart spire-agent
-
-# Wait for agent to start
-echo "Waiting for agent to initialize..."
-for i in {1..15}; do
- if systemctl is-active --quiet spire-agent && \
- [ -S /var/lib/spire/agent/agent.sock ]; then
- echo -e "${GREEN}✓ Agent started and socket ready${NC}"
- break
- fi
- echo "Waiting... ($i/15)"
- sleep 2
-done
-
-# Verify agent is working
-if systemctl is-active --quiet spire-agent; then
- echo -e "${GREEN}✓ SPIRE agent auto-join configured successfully${NC}"
-
- # Test agent health
- echo -e "\n${YELLOW}Agent Health Check:${NC}"
- curl -sv http://localhost:9990/live && echo || echo "Health check endpoint not ready yet"
-
- # Show token expiry warning for non-development environments
- if [ "$ENVIRONMENT" != "development" ]; then
- echo -e "\n${YELLOW}⚠ Token expires in ${TTL}${NC}"
- echo -e "For production, consider using node attestation instead"
- fi
-
- echo -e "\n${YELLOW}Auto-join configured! Agent will now:${NC}"
- echo "✓ Start automatically on boot"
- echo "✓ Join the SPIRE server automatically"
- echo "✓ Re-join after restarts (until token expires)"
-
- echo -e "\n${YELLOW}Next steps:${NC}"
- echo "1. Register services: make register-services"
- echo "2. View agent logs: sudo journalctl -u spire-agent -f"
- echo "3. Test agent: sudo journalctl -u spire-agent -n 20"
-else
- echo -e "${RED}✗ Failed to start SPIRE agent${NC}"
- echo "Check logs with: sudo journalctl -u spire-agent -n 50"
- exit 1
-fi
diff --git a/go/deploy/spire/scripts/register-services.sh b/go/deploy/spire/scripts/register-services.sh
deleted file mode 100755
index e116f714ee..0000000000
--- a/go/deploy/spire/scripts/register-services.sh
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/bin/bash
-# Registers all Unkey services with proper selectors
-
-set -euo pipefail
-
-# Get trust domain from environment or use default
-TRUST_DOMAIN=${TRUST_DOMAIN:-development.unkey.cloud}
-SPIRE_DIR="/opt/spire"
-SOCKET_PATH="/var/lib/spire/server/server.sock"
-
-# Colors for output
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-RED='\033[0;31m'
-BLUE='\033[0;34m'
-NC='\033[0m' # No Color
-
-echo -e "${GREEN}=== SPIRE Service Registration ===${NC}"
-echo -e "Trust Domain: ${YELLOW}${TRUST_DOMAIN}${NC}"
-
-# Check if server is running
-#if ! systemctl is-active --quiet spire-server; then
-# echo -e "${RED}Error: SPIRE server is not running${NC}"
-# echo "Start it with: sudo systemctl start spire-server"
-# exit 1
-#fi
-
-# Wait for server socket
-if [ ! -S "$SOCKET_PATH" ]; then
- echo -e "${RED}Error: Server socket not available${NC}"
- exit 1
-fi
-
-# Function to register a service
-register_service() {
- local service_name=$1
- local service_path=$2
- local service_user=$3
- local parent_id="spiffe://${TRUST_DOMAIN}/agent/node1"
- local spiffe_id="spiffe://${TRUST_DOMAIN}/service/${service_name}"
-
- echo -e "\n${BLUE}Registering ${service_name}...${NC}"
-
- # Check if entry already exists
- if sudo ${SPIRE_DIR}/bin/spire-server entry show \
- -socketPath "$SOCKET_PATH" \
- -spiffeID "$spiffe_id" 2>/dev/null | grep -q "SPIFFE ID"; then
- echo -e "${YELLOW}✓ ${service_name} already registered${NC}"
- return 0
- fi
-
- # Create registration entry
- sudo ${SPIRE_DIR}/bin/spire-server entry create \
- -socketPath "$SOCKET_PATH" \
- -parentID "$parent_id" \
- -spiffeID "$spiffe_id" \
- -selector "unix:path:${service_path}" \
- -selector "unix:user:${service_user}" \
- -x509SVIDTTL 3600 \
- || {
- echo -e "${RED}✗ Failed to register ${service_name}${NC}"
- return 1
- }
-
- echo -e "${GREEN}✓ ${service_name} registered${NC}"
-}
-
-# Register all services
-# All services run as their own dedicated user
-register_service "metald" "/usr/local/bin/metald" "root"
-register_service "billaged" "/usr/local/bin/billaged" "billaged"
-register_service "builderd" "/usr/local/bin/builderd" "root"
-register_service "assetmanagerd" "/usr/local/bin/assetmanagerd" "root"
-
-# Register the CLI tools for testing
-register_service "metald-cli" "/usr/local/bin/metald-cli" "$USER"
-register_service "assetmanagerd-cli" "/usr/local/bin/assetmanagerd-cli" "$USER"
-register_service "billaged-cli" "/usr/local/bin/billaged-cli" "$USER"
-register_service "builderd-cli" "/usr/local/bin/builderd-cli" "$USER"
-
-# List all registered entries
-echo -e "\n${YELLOW}=== Registered Services ===${NC}"
-sudo ${SPIRE_DIR}/bin/spire-server entry show \
- -socketPath "$SOCKET_PATH" \
- -parentID "spiffe://${TRUST_DOMAIN}/agent/node1" \
- | grep -E "(Entry ID|SPIFFE ID|Selector)" || true
-
-echo -e "\n${GREEN}✓ Service registration complete!${NC}"
-echo -e "\n${YELLOW}Next steps:${NC}"
-echo "1. Start services with SPIFFE support enabled"
-echo "2. Services will automatically receive SVIDs from SPIRE"
-echo "3. Monitor logs: sudo journalctl -u spire-agent -f"
diff --git a/go/deploy/version-hook.sh b/go/deploy/version-hook.sh
deleted file mode 100644
index 6f9cf0317f..0000000000
--- a/go/deploy/version-hook.sh
+++ /dev/null
@@ -1,326 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
-# Version Hook Script
-# Automatically bumps patch version and updates changelog for pillar services
-# and their clients when their Go code changes
-
-# Define pillar services
-PILLAR_SERVICES=("assetmanagerd" "billaged" "builderd" "metald")
-
-# Colors for output
-RED='\033[0;31m'
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-BLUE='\033[0;34m'
-NC='\033[0m' # No Color
-
-# Log function
-log() {
- echo -e "${BLUE}[VERSION-HOOK]${NC} $1"
-}
-
-warn() {
- echo -e "${YELLOW}[VERSION-HOOK WARNING]${NC} $1"
-}
-
-error() {
- echo -e "${RED}[VERSION-HOOK ERROR]${NC} $1"
-}
-
-success() {
- echo -e "${GREEN}[VERSION-HOOK SUCCESS]${NC} $1"
-}
-
-# Function to get current version from Makefile
-get_current_version() {
- local service=$1
- local type=${2:-"service"} # "service" or "client"
- local makefile
-
- if [[ "$type" == "client" ]]; then
- makefile="${service}/client/Makefile"
- else
- makefile="${service}/Makefile"
- fi
-
- if [[ ! -f "$makefile" ]]; then
- error "Makefile not found: $makefile"
- return 1
- fi
-
- # Extract version using sed/grep
- grep -E '^VERSION \?= ' "$makefile" | sed -E 's/VERSION \?= ([0-9]+\.[0-9]+\.[0-9]+).*/\1/'
-}
-
-# Function to bump patch version
-bump_patch_version() {
- local version=$1
- # Split version into major.minor.patch
- local major=$(echo "$version" | cut -d. -f1)
- local minor=$(echo "$version" | cut -d. -f2)
- local patch=$(echo "$version" | cut -d. -f3)
-
- # Increment patch
- patch=$((patch + 1))
-
- echo "${major}.${minor}.${patch}"
-}
-
-# Function to update version in Makefile only
-update_version_in_makefile() {
- local service=$1
- local new_version=$2
- local type=${3:-"service"} # "service" or "client"
- local makefile
-
- if [[ "$type" == "client" ]]; then
- makefile="${service}/client/Makefile"
- else
- makefile="${service}/Makefile"
- fi
-
- # Update Makefile version
- if [[ -f "$makefile" ]]; then
- sed -i.bak "s/^VERSION ?= [0-9][^[:space:]]*/VERSION ?= ${new_version}/" "$makefile"
- rm "${makefile}.bak" 2>/dev/null || true
- log "Updated ${service} ${type} Makefile version to ${new_version}"
- else
- warn "Makefile not found: $makefile"
- fi
-}
-
-# Function to update changelog
-update_changelog() {
- local service=$1
- local new_version=$2
- local summary=$3
- local type=${4:-"service"} # "service" or "client"
- local changelog_file
-
- if [[ "$type" == "client" ]]; then
- changelog_file="${service}/client/CHANGELOG.md"
- else
- changelog_file="${service}/CHANGELOG.md"
- fi
-
- # Get current date
- local date=$(date '+%Y-%m-%d')
-
- # Create changelog if it doesn't exist
- if [[ ! -f "$changelog_file" ]]; then
- log "Creating new changelog for ${service} ${type}"
- cat > "$changelog_file" << EOF
-# Changelog
-
-All notable changes to ${service} ${type} will be documented in this file.
-
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-
-## [${new_version}] - ${date}
-
-### Changed
-- ${summary}
-
-EOF
- else
- # Insert new version at the top (after the header)
- local temp_file=$(mktemp)
- {
- # Copy header (everything before first version entry)
- awk '/^## \[/{exit} {print}' "$changelog_file"
-
- # Add new version entry
- echo "## [${new_version}] - ${date}"
- echo ""
- echo "### Changed"
- echo "- ${summary}"
- echo ""
-
- # Copy rest of the file (starting from first version entry)
- awk '/^## \[/{found=1} found{print}' "$changelog_file"
- } > "$temp_file"
-
- mv "$temp_file" "$changelog_file"
- fi
-
- success "Updated changelog for ${service} ${type} v${new_version}"
-}
-
-# Function to detect changes in a service (excluding client)
-detect_service_changes() {
- local service=$1
-
- # Check if there are any *.go files that have been modified in the service
- # but exclude the client directory
- local go_files_changed=$(git diff --cached --name-only | grep -E "(^|/)${service}/.*\.go$" | grep -v "${service}/client/" || true)
-
- if [[ -n "$go_files_changed" ]]; then
- log "Detected Go code changes in ${service} service:"
- echo "$go_files_changed" | sed 's/^/ - /'
- return 0
- else
- return 1
- fi
-}
-
-# Function to detect changes in a client
-detect_client_changes() {
- local service=$1
-
- # Check if there are any *.go files that have been modified in the client directory
- local go_files_changed=$(git diff --cached --name-only | grep -E "(^|/)${service}/client/.*\.go$" || true)
-
- if [[ -n "$go_files_changed" ]]; then
- log "Detected Go code changes in ${service} client:"
- echo "$go_files_changed" | sed 's/^/ - /'
- return 0
- else
- return 1
- fi
-}
-
-# Function to generate change summary
-generate_change_summary() {
- local service=$1
- local type=${2:-"service"} # "service" or "client"
- local pattern
-
- if [[ "$type" == "client" ]]; then
- pattern="(^|/)${service}/client/.*\.go$"
- else
- pattern="(^|/)${service}/.*\.go$"
- # Exclude client directory for service changes
- if [[ "$type" == "service" ]]; then
- pattern="${pattern}|grep -v ${service}/client/"
- fi
- fi
-
- # Get list of changed files
- local changed_files
- if [[ "$type" == "service" ]]; then
- changed_files=$(git diff --cached --name-only | grep -E "(^|/)${service}/.*\.go$" | grep -v "${service}/client/" | head -5)
- else
- changed_files=$(git diff --cached --name-only | grep -E "(^|/)${service}/client/.*\.go$" | head -5)
- fi
-
- local file_count=$(echo "$changed_files" | wc -l)
-
- if [[ -z "$changed_files" ]]; then
- echo "Update ${type} code"
- elif [[ $file_count -eq 1 ]]; then
- local filename=$(basename "$changed_files")
- echo "Update ${filename} in ${type}"
- elif [[ $file_count -le 3 ]]; then
- local filenames=$(echo "$changed_files" | xargs -I {} basename {} | tr '\n' ', ' | sed 's/, $//')
- echo "Update ${filenames} in ${type}"
- else
- echo "Update ${file_count} Go files in ${type}"
- fi
-}
-
-# Main function
-main() {
- log "Starting version hook..."
-
- # Check if we're in a git repository
- if ! git rev-parse --git-dir > /dev/null 2>&1; then
- error "Not in a git repository"
- exit 1
- fi
-
- # Check if we're in the correct directory
- if [[ ! -f "CLAUDE.md" ]]; then
- error "Not in the deploy directory (CLAUDE.md not found)"
- exit 1
- fi
-
- local changes_made=false
-
- # Process each pillar service
- for service in "${PILLAR_SERVICES[@]}"; do
- if [[ ! -d "$service" ]]; then
- warn "Service directory not found: $service"
- continue
- fi
-
- # Check for service changes (excluding client)
- if detect_service_changes "$service"; then
- # Get current version
- local current_version
- if ! current_version=$(get_current_version "$service" "service"); then
- error "Failed to get current version for $service service"
- continue
- fi
-
- # Bump patch version
- local new_version
- new_version=$(bump_patch_version "$current_version")
-
- # Generate change summary
- local summary
- summary=$(generate_change_summary "$service" "service")
-
- log "Processing ${service} service: ${current_version} -> ${new_version}"
-
- # Update version in Makefile
- update_version_in_makefile "$service" "$new_version" "service"
-
- # Update changelog
- update_changelog "$service" "$new_version" "$summary" "service"
-
- # Stage the changes
- git add "${service}/Makefile"
- git add "${service}/CHANGELOG.md"
-
- success "Processed ${service} service v${new_version}"
- changes_made=true
- fi
-
- # Check for client changes
- if [[ -d "${service}/client" ]] && detect_client_changes "$service"; then
- # Get current client version
- local current_client_version
- if ! current_client_version=$(get_current_version "$service" "client"); then
- error "Failed to get current version for $service client"
- continue
- fi
-
- # Bump patch version
- local new_client_version
- new_client_version=$(bump_patch_version "$current_client_version")
-
- # Generate change summary
- local client_summary
- client_summary=$(generate_change_summary "$service" "client")
-
- log "Processing ${service} client: ${current_client_version} -> ${new_client_version}"
-
- # Update version in client Makefile
- update_version_in_makefile "$service" "$new_client_version" "client"
-
- # Update client changelog
- update_changelog "$service" "$new_client_version" "$client_summary" "client"
-
- # Stage the client changes
- git add "${service}/client/Makefile"
- git add "${service}/client/CHANGELOG.md"
-
- success "Processed ${service} client v${new_client_version}"
- changes_made=true
- fi
- done
-
- if [[ "$changes_made" == true ]]; then
- log "Version updates complete. Changes have been staged."
- log "You can now commit with: git commit"
- else
- log "No pillar service changes detected. No version bumps needed."
- fi
-}
-
-# Check if script is being sourced or executed
-if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
- main "$@"
-fi
\ No newline at end of file
diff --git a/go/gen/proto/assetmanagerd/v1/asset.pb.go b/go/gen/proto/assetmanagerd/v1/asset.pb.go
deleted file mode 100644
index acf87b2891..0000000000
--- a/go/gen/proto/assetmanagerd/v1/asset.pb.go
+++ /dev/null
@@ -1,2138 +0,0 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// protoc-gen-go v1.36.8
-// protoc (unknown)
-// source: assetmanagerd/v1/asset.proto
-
-package assetmanagerdv1
-
-import (
- protoreflect "google.golang.org/protobuf/reflect/protoreflect"
- protoimpl "google.golang.org/protobuf/runtime/protoimpl"
- reflect "reflect"
- sync "sync"
- unsafe "unsafe"
-)
-
-const (
- // Verify that this generated code is sufficiently up-to-date.
- _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
- // Verify that runtime/protoimpl is sufficiently up-to-date.
- _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
-)
-
-type AssetType int32
-
-const (
- AssetType_ASSET_TYPE_UNSPECIFIED AssetType = 0
- AssetType_ASSET_TYPE_KERNEL AssetType = 1
- AssetType_ASSET_TYPE_ROOTFS AssetType = 2
- AssetType_ASSET_TYPE_INITRD AssetType = 3
- AssetType_ASSET_TYPE_DISK_IMAGE AssetType = 4
-)
-
-// Enum value maps for AssetType.
-var (
- AssetType_name = map[int32]string{
- 0: "ASSET_TYPE_UNSPECIFIED",
- 1: "ASSET_TYPE_KERNEL",
- 2: "ASSET_TYPE_ROOTFS",
- 3: "ASSET_TYPE_INITRD",
- 4: "ASSET_TYPE_DISK_IMAGE",
- }
- AssetType_value = map[string]int32{
- "ASSET_TYPE_UNSPECIFIED": 0,
- "ASSET_TYPE_KERNEL": 1,
- "ASSET_TYPE_ROOTFS": 2,
- "ASSET_TYPE_INITRD": 3,
- "ASSET_TYPE_DISK_IMAGE": 4,
- }
-)
-
-func (x AssetType) Enum() *AssetType {
- p := new(AssetType)
- *p = x
- return p
-}
-
-func (x AssetType) String() string {
- return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
-}
-
-func (AssetType) Descriptor() protoreflect.EnumDescriptor {
- return file_assetmanagerd_v1_asset_proto_enumTypes[0].Descriptor()
-}
-
-func (AssetType) Type() protoreflect.EnumType {
- return &file_assetmanagerd_v1_asset_proto_enumTypes[0]
-}
-
-func (x AssetType) Number() protoreflect.EnumNumber {
- return protoreflect.EnumNumber(x)
-}
-
-// Deprecated: Use AssetType.Descriptor instead.
-func (AssetType) EnumDescriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{0}
-}
-
-type AssetStatus int32
-
-const (
- AssetStatus_ASSET_STATUS_UNSPECIFIED AssetStatus = 0
- AssetStatus_ASSET_STATUS_UPLOADING AssetStatus = 1
- AssetStatus_ASSET_STATUS_AVAILABLE AssetStatus = 2
- AssetStatus_ASSET_STATUS_DELETING AssetStatus = 3
- AssetStatus_ASSET_STATUS_ERROR AssetStatus = 4
-)
-
-// Enum value maps for AssetStatus.
-var (
- AssetStatus_name = map[int32]string{
- 0: "ASSET_STATUS_UNSPECIFIED",
- 1: "ASSET_STATUS_UPLOADING",
- 2: "ASSET_STATUS_AVAILABLE",
- 3: "ASSET_STATUS_DELETING",
- 4: "ASSET_STATUS_ERROR",
- }
- AssetStatus_value = map[string]int32{
- "ASSET_STATUS_UNSPECIFIED": 0,
- "ASSET_STATUS_UPLOADING": 1,
- "ASSET_STATUS_AVAILABLE": 2,
- "ASSET_STATUS_DELETING": 3,
- "ASSET_STATUS_ERROR": 4,
- }
-)
-
-func (x AssetStatus) Enum() *AssetStatus {
- p := new(AssetStatus)
- *p = x
- return p
-}
-
-func (x AssetStatus) String() string {
- return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
-}
-
-func (AssetStatus) Descriptor() protoreflect.EnumDescriptor {
- return file_assetmanagerd_v1_asset_proto_enumTypes[1].Descriptor()
-}
-
-func (AssetStatus) Type() protoreflect.EnumType {
- return &file_assetmanagerd_v1_asset_proto_enumTypes[1]
-}
-
-func (x AssetStatus) Number() protoreflect.EnumNumber {
- return protoreflect.EnumNumber(x)
-}
-
-// Deprecated: Use AssetStatus.Descriptor instead.
-func (AssetStatus) EnumDescriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{1}
-}
-
-type StorageBackend int32
-
-const (
- StorageBackend_STORAGE_BACKEND_UNSPECIFIED StorageBackend = 0
- StorageBackend_STORAGE_BACKEND_LOCAL StorageBackend = 1
- StorageBackend_STORAGE_BACKEND_S3 StorageBackend = 2
- StorageBackend_STORAGE_BACKEND_HTTP StorageBackend = 3
- StorageBackend_STORAGE_BACKEND_NFS StorageBackend = 4
-)
-
-// Enum value maps for StorageBackend.
-var (
- StorageBackend_name = map[int32]string{
- 0: "STORAGE_BACKEND_UNSPECIFIED",
- 1: "STORAGE_BACKEND_LOCAL",
- 2: "STORAGE_BACKEND_S3",
- 3: "STORAGE_BACKEND_HTTP",
- 4: "STORAGE_BACKEND_NFS",
- }
- StorageBackend_value = map[string]int32{
- "STORAGE_BACKEND_UNSPECIFIED": 0,
- "STORAGE_BACKEND_LOCAL": 1,
- "STORAGE_BACKEND_S3": 2,
- "STORAGE_BACKEND_HTTP": 3,
- "STORAGE_BACKEND_NFS": 4,
- }
-)
-
-func (x StorageBackend) Enum() *StorageBackend {
- p := new(StorageBackend)
- *p = x
- return p
-}
-
-func (x StorageBackend) String() string {
- return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
-}
-
-func (StorageBackend) Descriptor() protoreflect.EnumDescriptor {
- return file_assetmanagerd_v1_asset_proto_enumTypes[2].Descriptor()
-}
-
-func (StorageBackend) Type() protoreflect.EnumType {
- return &file_assetmanagerd_v1_asset_proto_enumTypes[2]
-}
-
-func (x StorageBackend) Number() protoreflect.EnumNumber {
- return protoreflect.EnumNumber(x)
-}
-
-// Deprecated: Use StorageBackend.Descriptor instead.
-func (StorageBackend) EnumDescriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{2}
-}
-
-type Asset struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
- Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
- Type AssetType `protobuf:"varint,3,opt,name=type,proto3,enum=assetmanagerd.v1.AssetType" json:"type,omitempty"`
- Status AssetStatus `protobuf:"varint,4,opt,name=status,proto3,enum=assetmanagerd.v1.AssetStatus" json:"status,omitempty"`
- // Storage information
- Backend StorageBackend `protobuf:"varint,5,opt,name=backend,proto3,enum=assetmanagerd.v1.StorageBackend" json:"backend,omitempty"`
- Location string `protobuf:"bytes,6,opt,name=location,proto3" json:"location,omitempty"` // Path or URL depending on backend
- SizeBytes int64 `protobuf:"varint,7,opt,name=size_bytes,json=sizeBytes,proto3" json:"size_bytes,omitempty"`
- Checksum string `protobuf:"bytes,8,opt,name=checksum,proto3" json:"checksum,omitempty"` // SHA256
- // Metadata
- Labels map[string]string `protobuf:"bytes,9,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- CreatedBy string `protobuf:"bytes,10,opt,name=created_by,json=createdBy,proto3" json:"created_by,omitempty"` // e.g., "builderd", "manual"
- CreatedAt int64 `protobuf:"varint,11,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` // Unix timestamp
- LastAccessedAt int64 `protobuf:"varint,12,opt,name=last_accessed_at,json=lastAccessedAt,proto3" json:"last_accessed_at,omitempty"`
- // Reference counting for GC
- ReferenceCount int32 `protobuf:"varint,13,opt,name=reference_count,json=referenceCount,proto3" json:"reference_count,omitempty"`
- // Build information (if created by builderd)
- BuildId string `protobuf:"bytes,14,opt,name=build_id,json=buildId,proto3" json:"build_id,omitempty"`
- SourceImage string `protobuf:"bytes,15,opt,name=source_image,json=sourceImage,proto3" json:"source_image,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *Asset) Reset() {
- *x = Asset{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[0]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *Asset) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*Asset) ProtoMessage() {}
-
-func (x *Asset) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[0]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use Asset.ProtoReflect.Descriptor instead.
-func (*Asset) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{0}
-}
-
-func (x *Asset) GetId() string {
- if x != nil {
- return x.Id
- }
- return ""
-}
-
-func (x *Asset) GetName() string {
- if x != nil {
- return x.Name
- }
- return ""
-}
-
-func (x *Asset) GetType() AssetType {
- if x != nil {
- return x.Type
- }
- return AssetType_ASSET_TYPE_UNSPECIFIED
-}
-
-func (x *Asset) GetStatus() AssetStatus {
- if x != nil {
- return x.Status
- }
- return AssetStatus_ASSET_STATUS_UNSPECIFIED
-}
-
-func (x *Asset) GetBackend() StorageBackend {
- if x != nil {
- return x.Backend
- }
- return StorageBackend_STORAGE_BACKEND_UNSPECIFIED
-}
-
-func (x *Asset) GetLocation() string {
- if x != nil {
- return x.Location
- }
- return ""
-}
-
-func (x *Asset) GetSizeBytes() int64 {
- if x != nil {
- return x.SizeBytes
- }
- return 0
-}
-
-func (x *Asset) GetChecksum() string {
- if x != nil {
- return x.Checksum
- }
- return ""
-}
-
-func (x *Asset) GetLabels() map[string]string {
- if x != nil {
- return x.Labels
- }
- return nil
-}
-
-func (x *Asset) GetCreatedBy() string {
- if x != nil {
- return x.CreatedBy
- }
- return ""
-}
-
-func (x *Asset) GetCreatedAt() int64 {
- if x != nil {
- return x.CreatedAt
- }
- return 0
-}
-
-func (x *Asset) GetLastAccessedAt() int64 {
- if x != nil {
- return x.LastAccessedAt
- }
- return 0
-}
-
-func (x *Asset) GetReferenceCount() int32 {
- if x != nil {
- return x.ReferenceCount
- }
- return 0
-}
-
-func (x *Asset) GetBuildId() string {
- if x != nil {
- return x.BuildId
- }
- return ""
-}
-
-func (x *Asset) GetSourceImage() string {
- if x != nil {
- return x.SourceImage
- }
- return ""
-}
-
-type UploadAssetRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // Types that are valid to be assigned to Data:
- //
- // *UploadAssetRequest_Metadata
- // *UploadAssetRequest_Chunk
- Data isUploadAssetRequest_Data `protobuf_oneof:"data"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *UploadAssetRequest) Reset() {
- *x = UploadAssetRequest{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[1]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *UploadAssetRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*UploadAssetRequest) ProtoMessage() {}
-
-func (x *UploadAssetRequest) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[1]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use UploadAssetRequest.ProtoReflect.Descriptor instead.
-func (*UploadAssetRequest) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{1}
-}
-
-func (x *UploadAssetRequest) GetData() isUploadAssetRequest_Data {
- if x != nil {
- return x.Data
- }
- return nil
-}
-
-func (x *UploadAssetRequest) GetMetadata() *UploadAssetMetadata {
- if x != nil {
- if x, ok := x.Data.(*UploadAssetRequest_Metadata); ok {
- return x.Metadata
- }
- }
- return nil
-}
-
-func (x *UploadAssetRequest) GetChunk() []byte {
- if x != nil {
- if x, ok := x.Data.(*UploadAssetRequest_Chunk); ok {
- return x.Chunk
- }
- }
- return nil
-}
-
-type isUploadAssetRequest_Data interface {
- isUploadAssetRequest_Data()
-}
-
-type UploadAssetRequest_Metadata struct {
- Metadata *UploadAssetMetadata `protobuf:"bytes,1,opt,name=metadata,proto3,oneof"`
-}
-
-type UploadAssetRequest_Chunk struct {
- Chunk []byte `protobuf:"bytes,2,opt,name=chunk,proto3,oneof"`
-}
-
-func (*UploadAssetRequest_Metadata) isUploadAssetRequest_Data() {}
-
-func (*UploadAssetRequest_Chunk) isUploadAssetRequest_Data() {}
-
-type UploadAssetMetadata struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
- Type AssetType `protobuf:"varint,2,opt,name=type,proto3,enum=assetmanagerd.v1.AssetType" json:"type,omitempty"`
- SizeBytes int64 `protobuf:"varint,3,opt,name=size_bytes,json=sizeBytes,proto3" json:"size_bytes,omitempty"`
- Labels map[string]string `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- CreatedBy string `protobuf:"bytes,5,opt,name=created_by,json=createdBy,proto3" json:"created_by,omitempty"`
- BuildId string `protobuf:"bytes,6,opt,name=build_id,json=buildId,proto3" json:"build_id,omitempty"`
- SourceImage string `protobuf:"bytes,7,opt,name=source_image,json=sourceImage,proto3" json:"source_image,omitempty"`
- Id string `protobuf:"bytes,8,opt,name=id,proto3" json:"id,omitempty"` // Optional: specific asset ID to use
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *UploadAssetMetadata) Reset() {
- *x = UploadAssetMetadata{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[2]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *UploadAssetMetadata) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*UploadAssetMetadata) ProtoMessage() {}
-
-func (x *UploadAssetMetadata) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[2]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use UploadAssetMetadata.ProtoReflect.Descriptor instead.
-func (*UploadAssetMetadata) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{2}
-}
-
-func (x *UploadAssetMetadata) GetName() string {
- if x != nil {
- return x.Name
- }
- return ""
-}
-
-func (x *UploadAssetMetadata) GetType() AssetType {
- if x != nil {
- return x.Type
- }
- return AssetType_ASSET_TYPE_UNSPECIFIED
-}
-
-func (x *UploadAssetMetadata) GetSizeBytes() int64 {
- if x != nil {
- return x.SizeBytes
- }
- return 0
-}
-
-func (x *UploadAssetMetadata) GetLabels() map[string]string {
- if x != nil {
- return x.Labels
- }
- return nil
-}
-
-func (x *UploadAssetMetadata) GetCreatedBy() string {
- if x != nil {
- return x.CreatedBy
- }
- return ""
-}
-
-func (x *UploadAssetMetadata) GetBuildId() string {
- if x != nil {
- return x.BuildId
- }
- return ""
-}
-
-func (x *UploadAssetMetadata) GetSourceImage() string {
- if x != nil {
- return x.SourceImage
- }
- return ""
-}
-
-func (x *UploadAssetMetadata) GetId() string {
- if x != nil {
- return x.Id
- }
- return ""
-}
-
-type UploadAssetResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Asset *Asset `protobuf:"bytes,1,opt,name=asset,proto3" json:"asset,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *UploadAssetResponse) Reset() {
- *x = UploadAssetResponse{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[3]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *UploadAssetResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*UploadAssetResponse) ProtoMessage() {}
-
-func (x *UploadAssetResponse) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[3]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use UploadAssetResponse.ProtoReflect.Descriptor instead.
-func (*UploadAssetResponse) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{3}
-}
-
-func (x *UploadAssetResponse) GetAsset() *Asset {
- if x != nil {
- return x.Asset
- }
- return nil
-}
-
-type RegisterAssetRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
- Type AssetType `protobuf:"varint,2,opt,name=type,proto3,enum=assetmanagerd.v1.AssetType" json:"type,omitempty"`
- Backend StorageBackend `protobuf:"varint,3,opt,name=backend,proto3,enum=assetmanagerd.v1.StorageBackend" json:"backend,omitempty"`
- Location string `protobuf:"bytes,4,opt,name=location,proto3" json:"location,omitempty"`
- SizeBytes int64 `protobuf:"varint,5,opt,name=size_bytes,json=sizeBytes,proto3" json:"size_bytes,omitempty"`
- Checksum string `protobuf:"bytes,6,opt,name=checksum,proto3" json:"checksum,omitempty"`
- Labels map[string]string `protobuf:"bytes,7,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- CreatedBy string `protobuf:"bytes,8,opt,name=created_by,json=createdBy,proto3" json:"created_by,omitempty"`
- // Optional build information
- BuildId string `protobuf:"bytes,9,opt,name=build_id,json=buildId,proto3" json:"build_id,omitempty"`
- SourceImage string `protobuf:"bytes,10,opt,name=source_image,json=sourceImage,proto3" json:"source_image,omitempty"`
- // Optional: specific asset ID to use (if not provided, one will be generated)
- Id string `protobuf:"bytes,11,opt,name=id,proto3" json:"id,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *RegisterAssetRequest) Reset() {
- *x = RegisterAssetRequest{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[4]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *RegisterAssetRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*RegisterAssetRequest) ProtoMessage() {}
-
-func (x *RegisterAssetRequest) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[4]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use RegisterAssetRequest.ProtoReflect.Descriptor instead.
-func (*RegisterAssetRequest) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{4}
-}
-
-func (x *RegisterAssetRequest) GetName() string {
- if x != nil {
- return x.Name
- }
- return ""
-}
-
-func (x *RegisterAssetRequest) GetType() AssetType {
- if x != nil {
- return x.Type
- }
- return AssetType_ASSET_TYPE_UNSPECIFIED
-}
-
-func (x *RegisterAssetRequest) GetBackend() StorageBackend {
- if x != nil {
- return x.Backend
- }
- return StorageBackend_STORAGE_BACKEND_UNSPECIFIED
-}
-
-func (x *RegisterAssetRequest) GetLocation() string {
- if x != nil {
- return x.Location
- }
- return ""
-}
-
-func (x *RegisterAssetRequest) GetSizeBytes() int64 {
- if x != nil {
- return x.SizeBytes
- }
- return 0
-}
-
-func (x *RegisterAssetRequest) GetChecksum() string {
- if x != nil {
- return x.Checksum
- }
- return ""
-}
-
-func (x *RegisterAssetRequest) GetLabels() map[string]string {
- if x != nil {
- return x.Labels
- }
- return nil
-}
-
-func (x *RegisterAssetRequest) GetCreatedBy() string {
- if x != nil {
- return x.CreatedBy
- }
- return ""
-}
-
-func (x *RegisterAssetRequest) GetBuildId() string {
- if x != nil {
- return x.BuildId
- }
- return ""
-}
-
-func (x *RegisterAssetRequest) GetSourceImage() string {
- if x != nil {
- return x.SourceImage
- }
- return ""
-}
-
-func (x *RegisterAssetRequest) GetId() string {
- if x != nil {
- return x.Id
- }
- return ""
-}
-
-type RegisterAssetResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Asset *Asset `protobuf:"bytes,1,opt,name=asset,proto3" json:"asset,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *RegisterAssetResponse) Reset() {
- *x = RegisterAssetResponse{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[5]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *RegisterAssetResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*RegisterAssetResponse) ProtoMessage() {}
-
-func (x *RegisterAssetResponse) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[5]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use RegisterAssetResponse.ProtoReflect.Descriptor instead.
-func (*RegisterAssetResponse) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{5}
-}
-
-func (x *RegisterAssetResponse) GetAsset() *Asset {
- if x != nil {
- return x.Asset
- }
- return nil
-}
-
-type GetAssetRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
- // If true, ensures asset is available locally (downloads if needed)
- EnsureLocal bool `protobuf:"varint,2,opt,name=ensure_local,json=ensureLocal,proto3" json:"ensure_local,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *GetAssetRequest) Reset() {
- *x = GetAssetRequest{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[6]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *GetAssetRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*GetAssetRequest) ProtoMessage() {}
-
-func (x *GetAssetRequest) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[6]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use GetAssetRequest.ProtoReflect.Descriptor instead.
-func (*GetAssetRequest) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{6}
-}
-
-func (x *GetAssetRequest) GetId() string {
- if x != nil {
- return x.Id
- }
- return ""
-}
-
-func (x *GetAssetRequest) GetEnsureLocal() bool {
- if x != nil {
- return x.EnsureLocal
- }
- return false
-}
-
-type GetAssetResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Asset *Asset `protobuf:"bytes,1,opt,name=asset,proto3" json:"asset,omitempty"`
- // Local path if ensure_local was true
- LocalPath string `protobuf:"bytes,2,opt,name=local_path,json=localPath,proto3" json:"local_path,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *GetAssetResponse) Reset() {
- *x = GetAssetResponse{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[7]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *GetAssetResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*GetAssetResponse) ProtoMessage() {}
-
-func (x *GetAssetResponse) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[7]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use GetAssetResponse.ProtoReflect.Descriptor instead.
-func (*GetAssetResponse) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{7}
-}
-
-func (x *GetAssetResponse) GetAsset() *Asset {
- if x != nil {
- return x.Asset
- }
- return nil
-}
-
-func (x *GetAssetResponse) GetLocalPath() string {
- if x != nil {
- return x.LocalPath
- }
- return ""
-}
-
-type ListAssetsRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // Filter by type
- Type AssetType `protobuf:"varint,1,opt,name=type,proto3,enum=assetmanagerd.v1.AssetType" json:"type,omitempty"`
- // Filter by status
- Status AssetStatus `protobuf:"varint,2,opt,name=status,proto3,enum=assetmanagerd.v1.AssetStatus" json:"status,omitempty"`
- // Filter by labels (all must match)
- LabelSelector map[string]string `protobuf:"bytes,3,rep,name=label_selector,json=labelSelector,proto3" json:"label_selector,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- // Pagination
- PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
- PageToken string `protobuf:"bytes,5,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *ListAssetsRequest) Reset() {
- *x = ListAssetsRequest{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[8]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *ListAssetsRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*ListAssetsRequest) ProtoMessage() {}
-
-func (x *ListAssetsRequest) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[8]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use ListAssetsRequest.ProtoReflect.Descriptor instead.
-func (*ListAssetsRequest) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{8}
-}
-
-func (x *ListAssetsRequest) GetType() AssetType {
- if x != nil {
- return x.Type
- }
- return AssetType_ASSET_TYPE_UNSPECIFIED
-}
-
-func (x *ListAssetsRequest) GetStatus() AssetStatus {
- if x != nil {
- return x.Status
- }
- return AssetStatus_ASSET_STATUS_UNSPECIFIED
-}
-
-func (x *ListAssetsRequest) GetLabelSelector() map[string]string {
- if x != nil {
- return x.LabelSelector
- }
- return nil
-}
-
-func (x *ListAssetsRequest) GetPageSize() int32 {
- if x != nil {
- return x.PageSize
- }
- return 0
-}
-
-func (x *ListAssetsRequest) GetPageToken() string {
- if x != nil {
- return x.PageToken
- }
- return ""
-}
-
-type ListAssetsResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Assets []*Asset `protobuf:"bytes,1,rep,name=assets,proto3" json:"assets,omitempty"`
- NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *ListAssetsResponse) Reset() {
- *x = ListAssetsResponse{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[9]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *ListAssetsResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*ListAssetsResponse) ProtoMessage() {}
-
-func (x *ListAssetsResponse) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[9]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use ListAssetsResponse.ProtoReflect.Descriptor instead.
-func (*ListAssetsResponse) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{9}
-}
-
-func (x *ListAssetsResponse) GetAssets() []*Asset {
- if x != nil {
- return x.Assets
- }
- return nil
-}
-
-func (x *ListAssetsResponse) GetNextPageToken() string {
- if x != nil {
- return x.NextPageToken
- }
- return ""
-}
-
-type AcquireAssetRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- AssetId string `protobuf:"bytes,1,opt,name=asset_id,json=assetId,proto3" json:"asset_id,omitempty"`
- AcquiredBy string `protobuf:"bytes,2,opt,name=acquired_by,json=acquiredBy,proto3" json:"acquired_by,omitempty"` // e.g., "vm-123"
- TtlSeconds int64 `protobuf:"varint,3,opt,name=ttl_seconds,json=ttlSeconds,proto3" json:"ttl_seconds,omitempty"` // Optional auto-release after TTL
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *AcquireAssetRequest) Reset() {
- *x = AcquireAssetRequest{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[10]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *AcquireAssetRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*AcquireAssetRequest) ProtoMessage() {}
-
-func (x *AcquireAssetRequest) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[10]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use AcquireAssetRequest.ProtoReflect.Descriptor instead.
-func (*AcquireAssetRequest) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{10}
-}
-
-func (x *AcquireAssetRequest) GetAssetId() string {
- if x != nil {
- return x.AssetId
- }
- return ""
-}
-
-func (x *AcquireAssetRequest) GetAcquiredBy() string {
- if x != nil {
- return x.AcquiredBy
- }
- return ""
-}
-
-func (x *AcquireAssetRequest) GetTtlSeconds() int64 {
- if x != nil {
- return x.TtlSeconds
- }
- return 0
-}
-
-type AcquireAssetResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Asset *Asset `protobuf:"bytes,1,opt,name=asset,proto3" json:"asset,omitempty"`
- LeaseId string `protobuf:"bytes,2,opt,name=lease_id,json=leaseId,proto3" json:"lease_id,omitempty"` // Use this for release
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *AcquireAssetResponse) Reset() {
- *x = AcquireAssetResponse{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[11]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *AcquireAssetResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*AcquireAssetResponse) ProtoMessage() {}
-
-func (x *AcquireAssetResponse) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[11]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use AcquireAssetResponse.ProtoReflect.Descriptor instead.
-func (*AcquireAssetResponse) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{11}
-}
-
-func (x *AcquireAssetResponse) GetAsset() *Asset {
- if x != nil {
- return x.Asset
- }
- return nil
-}
-
-func (x *AcquireAssetResponse) GetLeaseId() string {
- if x != nil {
- return x.LeaseId
- }
- return ""
-}
-
-type ReleaseAssetRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- LeaseId string `protobuf:"bytes,1,opt,name=lease_id,json=leaseId,proto3" json:"lease_id,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *ReleaseAssetRequest) Reset() {
- *x = ReleaseAssetRequest{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[12]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *ReleaseAssetRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*ReleaseAssetRequest) ProtoMessage() {}
-
-func (x *ReleaseAssetRequest) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[12]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use ReleaseAssetRequest.ProtoReflect.Descriptor instead.
-func (*ReleaseAssetRequest) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{12}
-}
-
-func (x *ReleaseAssetRequest) GetLeaseId() string {
- if x != nil {
- return x.LeaseId
- }
- return ""
-}
-
-type ReleaseAssetResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Asset *Asset `protobuf:"bytes,1,opt,name=asset,proto3" json:"asset,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *ReleaseAssetResponse) Reset() {
- *x = ReleaseAssetResponse{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[13]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *ReleaseAssetResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*ReleaseAssetResponse) ProtoMessage() {}
-
-func (x *ReleaseAssetResponse) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[13]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use ReleaseAssetResponse.ProtoReflect.Descriptor instead.
-func (*ReleaseAssetResponse) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{13}
-}
-
-func (x *ReleaseAssetResponse) GetAsset() *Asset {
- if x != nil {
- return x.Asset
- }
- return nil
-}
-
-type DeleteAssetRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
- Force bool `protobuf:"varint,2,opt,name=force,proto3" json:"force,omitempty"` // Delete even if ref count > 0
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *DeleteAssetRequest) Reset() {
- *x = DeleteAssetRequest{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[14]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *DeleteAssetRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*DeleteAssetRequest) ProtoMessage() {}
-
-func (x *DeleteAssetRequest) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[14]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use DeleteAssetRequest.ProtoReflect.Descriptor instead.
-func (*DeleteAssetRequest) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{14}
-}
-
-func (x *DeleteAssetRequest) GetId() string {
- if x != nil {
- return x.Id
- }
- return ""
-}
-
-func (x *DeleteAssetRequest) GetForce() bool {
- if x != nil {
- return x.Force
- }
- return false
-}
-
-type DeleteAssetResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"`
- Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *DeleteAssetResponse) Reset() {
- *x = DeleteAssetResponse{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[15]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *DeleteAssetResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*DeleteAssetResponse) ProtoMessage() {}
-
-func (x *DeleteAssetResponse) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[15]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use DeleteAssetResponse.ProtoReflect.Descriptor instead.
-func (*DeleteAssetResponse) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{15}
-}
-
-func (x *DeleteAssetResponse) GetDeleted() bool {
- if x != nil {
- return x.Deleted
- }
- return false
-}
-
-func (x *DeleteAssetResponse) GetMessage() string {
- if x != nil {
- return x.Message
- }
- return ""
-}
-
-type GarbageCollectRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // Delete assets not accessed in this many seconds
- MaxAgeSeconds int64 `protobuf:"varint,1,opt,name=max_age_seconds,json=maxAgeSeconds,proto3" json:"max_age_seconds,omitempty"`
- // Delete assets with 0 references
- DeleteUnreferenced bool `protobuf:"varint,2,opt,name=delete_unreferenced,json=deleteUnreferenced,proto3" json:"delete_unreferenced,omitempty"`
- // Dry run - just return what would be deleted
- DryRun bool `protobuf:"varint,3,opt,name=dry_run,json=dryRun,proto3" json:"dry_run,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *GarbageCollectRequest) Reset() {
- *x = GarbageCollectRequest{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[16]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *GarbageCollectRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*GarbageCollectRequest) ProtoMessage() {}
-
-func (x *GarbageCollectRequest) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[16]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use GarbageCollectRequest.ProtoReflect.Descriptor instead.
-func (*GarbageCollectRequest) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{16}
-}
-
-func (x *GarbageCollectRequest) GetMaxAgeSeconds() int64 {
- if x != nil {
- return x.MaxAgeSeconds
- }
- return 0
-}
-
-func (x *GarbageCollectRequest) GetDeleteUnreferenced() bool {
- if x != nil {
- return x.DeleteUnreferenced
- }
- return false
-}
-
-func (x *GarbageCollectRequest) GetDryRun() bool {
- if x != nil {
- return x.DryRun
- }
- return false
-}
-
-type GarbageCollectResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- DeletedAssets []*Asset `protobuf:"bytes,1,rep,name=deleted_assets,json=deletedAssets,proto3" json:"deleted_assets,omitempty"`
- BytesFreed int64 `protobuf:"varint,2,opt,name=bytes_freed,json=bytesFreed,proto3" json:"bytes_freed,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *GarbageCollectResponse) Reset() {
- *x = GarbageCollectResponse{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[17]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *GarbageCollectResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*GarbageCollectResponse) ProtoMessage() {}
-
-func (x *GarbageCollectResponse) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[17]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use GarbageCollectResponse.ProtoReflect.Descriptor instead.
-func (*GarbageCollectResponse) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{17}
-}
-
-func (x *GarbageCollectResponse) GetDeletedAssets() []*Asset {
- if x != nil {
- return x.DeletedAssets
- }
- return nil
-}
-
-func (x *GarbageCollectResponse) GetBytesFreed() int64 {
- if x != nil {
- return x.BytesFreed
- }
- return 0
-}
-
-type PrepareAssetsRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- AssetIds []string `protobuf:"bytes,1,rep,name=asset_ids,json=assetIds,proto3" json:"asset_ids,omitempty"`
- TargetPath string `protobuf:"bytes,2,opt,name=target_path,json=targetPath,proto3" json:"target_path,omitempty"` // e.g., jailer chroot path
- PreparedFor string `protobuf:"bytes,3,opt,name=prepared_for,json=preparedFor,proto3" json:"prepared_for,omitempty"` // e.g., "vm-123"
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *PrepareAssetsRequest) Reset() {
- *x = PrepareAssetsRequest{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[18]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *PrepareAssetsRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*PrepareAssetsRequest) ProtoMessage() {}
-
-func (x *PrepareAssetsRequest) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[18]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use PrepareAssetsRequest.ProtoReflect.Descriptor instead.
-func (*PrepareAssetsRequest) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{18}
-}
-
-func (x *PrepareAssetsRequest) GetAssetIds() []string {
- if x != nil {
- return x.AssetIds
- }
- return nil
-}
-
-func (x *PrepareAssetsRequest) GetTargetPath() string {
- if x != nil {
- return x.TargetPath
- }
- return ""
-}
-
-func (x *PrepareAssetsRequest) GetPreparedFor() string {
- if x != nil {
- return x.PreparedFor
- }
- return ""
-}
-
-type PrepareAssetsResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- AssetPaths map[string]string `protobuf:"bytes,1,rep,name=asset_paths,json=assetPaths,proto3" json:"asset_paths,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // asset_id -> local path
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *PrepareAssetsResponse) Reset() {
- *x = PrepareAssetsResponse{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[19]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *PrepareAssetsResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*PrepareAssetsResponse) ProtoMessage() {}
-
-func (x *PrepareAssetsResponse) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[19]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use PrepareAssetsResponse.ProtoReflect.Descriptor instead.
-func (*PrepareAssetsResponse) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{19}
-}
-
-func (x *PrepareAssetsResponse) GetAssetPaths() map[string]string {
- if x != nil {
- return x.AssetPaths
- }
- return nil
-}
-
-// QueryAssetsRequest is similar to ListAssetsRequest but with build options
-type QueryAssetsRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // Filter by type
- Type AssetType `protobuf:"varint,1,opt,name=type,proto3,enum=assetmanagerd.v1.AssetType" json:"type,omitempty"`
- // Filter by status
- Status AssetStatus `protobuf:"varint,2,opt,name=status,proto3,enum=assetmanagerd.v1.AssetStatus" json:"status,omitempty"`
- // Filter by labels (all must match)
- LabelSelector map[string]string `protobuf:"bytes,3,rep,name=label_selector,json=labelSelector,proto3" json:"label_selector,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- // Pagination
- PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
- PageToken string `protobuf:"bytes,5,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"`
- // Build options - if asset not found and these are set, trigger build
- BuildOptions *BuildOptions `protobuf:"bytes,6,opt,name=build_options,json=buildOptions,proto3" json:"build_options,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *QueryAssetsRequest) Reset() {
- *x = QueryAssetsRequest{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[20]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *QueryAssetsRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*QueryAssetsRequest) ProtoMessage() {}
-
-func (x *QueryAssetsRequest) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[20]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use QueryAssetsRequest.ProtoReflect.Descriptor instead.
-func (*QueryAssetsRequest) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{20}
-}
-
-func (x *QueryAssetsRequest) GetType() AssetType {
- if x != nil {
- return x.Type
- }
- return AssetType_ASSET_TYPE_UNSPECIFIED
-}
-
-func (x *QueryAssetsRequest) GetStatus() AssetStatus {
- if x != nil {
- return x.Status
- }
- return AssetStatus_ASSET_STATUS_UNSPECIFIED
-}
-
-func (x *QueryAssetsRequest) GetLabelSelector() map[string]string {
- if x != nil {
- return x.LabelSelector
- }
- return nil
-}
-
-func (x *QueryAssetsRequest) GetPageSize() int32 {
- if x != nil {
- return x.PageSize
- }
- return 0
-}
-
-func (x *QueryAssetsRequest) GetPageToken() string {
- if x != nil {
- return x.PageToken
- }
- return ""
-}
-
-func (x *QueryAssetsRequest) GetBuildOptions() *BuildOptions {
- if x != nil {
- return x.BuildOptions
- }
- return nil
-}
-
-// BuildOptions controls automatic asset creation
-type BuildOptions struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // Enable automatic building if assets don't exist
- EnableAutoBuild bool `protobuf:"varint,1,opt,name=enable_auto_build,json=enableAutoBuild,proto3" json:"enable_auto_build,omitempty"`
- // Wait for build completion before returning
- WaitForCompletion bool `protobuf:"varint,2,opt,name=wait_for_completion,json=waitForCompletion,proto3" json:"wait_for_completion,omitempty"`
- // Timeout for build operation (seconds)
- BuildTimeoutSeconds int32 `protobuf:"varint,3,opt,name=build_timeout_seconds,json=buildTimeoutSeconds,proto3" json:"build_timeout_seconds,omitempty"`
- // Additional labels to add to the built asset
- BuildLabels map[string]string `protobuf:"bytes,4,rep,name=build_labels,json=buildLabels,proto3" json:"build_labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- // Suggested asset ID to use when registering the built asset
- // This allows the caller to know the asset ID before it's built
- SuggestedAssetId string `protobuf:"bytes,5,opt,name=suggested_asset_id,json=suggestedAssetId,proto3" json:"suggested_asset_id,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *BuildOptions) Reset() {
- *x = BuildOptions{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[21]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *BuildOptions) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*BuildOptions) ProtoMessage() {}
-
-func (x *BuildOptions) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[21]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use BuildOptions.ProtoReflect.Descriptor instead.
-func (*BuildOptions) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{21}
-}
-
-func (x *BuildOptions) GetEnableAutoBuild() bool {
- if x != nil {
- return x.EnableAutoBuild
- }
- return false
-}
-
-func (x *BuildOptions) GetWaitForCompletion() bool {
- if x != nil {
- return x.WaitForCompletion
- }
- return false
-}
-
-func (x *BuildOptions) GetBuildTimeoutSeconds() int32 {
- if x != nil {
- return x.BuildTimeoutSeconds
- }
- return 0
-}
-
-func (x *BuildOptions) GetBuildLabels() map[string]string {
- if x != nil {
- return x.BuildLabels
- }
- return nil
-}
-
-func (x *BuildOptions) GetSuggestedAssetId() string {
- if x != nil {
- return x.SuggestedAssetId
- }
- return ""
-}
-
-// QueryAssetsResponse includes build information if builds were triggered
-type QueryAssetsResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Assets []*Asset `protobuf:"bytes,1,rep,name=assets,proto3" json:"assets,omitempty"`
- NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"`
- // Information about any builds that were triggered
- TriggeredBuilds []*BuildInfo `protobuf:"bytes,3,rep,name=triggered_builds,json=triggeredBuilds,proto3" json:"triggered_builds,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *QueryAssetsResponse) Reset() {
- *x = QueryAssetsResponse{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[22]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *QueryAssetsResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*QueryAssetsResponse) ProtoMessage() {}
-
-func (x *QueryAssetsResponse) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[22]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use QueryAssetsResponse.ProtoReflect.Descriptor instead.
-func (*QueryAssetsResponse) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{22}
-}
-
-func (x *QueryAssetsResponse) GetAssets() []*Asset {
- if x != nil {
- return x.Assets
- }
- return nil
-}
-
-func (x *QueryAssetsResponse) GetNextPageToken() string {
- if x != nil {
- return x.NextPageToken
- }
- return ""
-}
-
-func (x *QueryAssetsResponse) GetTriggeredBuilds() []*BuildInfo {
- if x != nil {
- return x.TriggeredBuilds
- }
- return nil
-}
-
-// BuildInfo provides information about triggered builds
-type BuildInfo struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- BuildId string `protobuf:"bytes,1,opt,name=build_id,json=buildId,proto3" json:"build_id,omitempty"`
- DockerImage string `protobuf:"bytes,2,opt,name=docker_image,json=dockerImage,proto3" json:"docker_image,omitempty"`
- Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` // "pending", "building", "completed", "failed"
- ErrorMessage string `protobuf:"bytes,4,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"`
- AssetId string `protobuf:"bytes,5,opt,name=asset_id,json=assetId,proto3" json:"asset_id,omitempty"` // Asset ID if build completed and asset was registered
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *BuildInfo) Reset() {
- *x = BuildInfo{}
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[23]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *BuildInfo) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*BuildInfo) ProtoMessage() {}
-
-func (x *BuildInfo) ProtoReflect() protoreflect.Message {
- mi := &file_assetmanagerd_v1_asset_proto_msgTypes[23]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use BuildInfo.ProtoReflect.Descriptor instead.
-func (*BuildInfo) Descriptor() ([]byte, []int) {
- return file_assetmanagerd_v1_asset_proto_rawDescGZIP(), []int{23}
-}
-
-func (x *BuildInfo) GetBuildId() string {
- if x != nil {
- return x.BuildId
- }
- return ""
-}
-
-func (x *BuildInfo) GetDockerImage() string {
- if x != nil {
- return x.DockerImage
- }
- return ""
-}
-
-func (x *BuildInfo) GetStatus() string {
- if x != nil {
- return x.Status
- }
- return ""
-}
-
-func (x *BuildInfo) GetErrorMessage() string {
- if x != nil {
- return x.ErrorMessage
- }
- return ""
-}
-
-func (x *BuildInfo) GetAssetId() string {
- if x != nil {
- return x.AssetId
- }
- return ""
-}
-
-var File_assetmanagerd_v1_asset_proto protoreflect.FileDescriptor
-
-const file_assetmanagerd_v1_asset_proto_rawDesc = "" +
- "\n" +
- "\x1cassetmanagerd/v1/asset.proto\x12\x10assetmanagerd.v1\"\xed\x04\n" +
- "\x05Asset\x12\x0e\n" +
- "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
- "\x04name\x18\x02 \x01(\tR\x04name\x12/\n" +
- "\x04type\x18\x03 \x01(\x0e2\x1b.assetmanagerd.v1.AssetTypeR\x04type\x125\n" +
- "\x06status\x18\x04 \x01(\x0e2\x1d.assetmanagerd.v1.AssetStatusR\x06status\x12:\n" +
- "\abackend\x18\x05 \x01(\x0e2 .assetmanagerd.v1.StorageBackendR\abackend\x12\x1a\n" +
- "\blocation\x18\x06 \x01(\tR\blocation\x12\x1d\n" +
- "\n" +
- "size_bytes\x18\a \x01(\x03R\tsizeBytes\x12\x1a\n" +
- "\bchecksum\x18\b \x01(\tR\bchecksum\x12;\n" +
- "\x06labels\x18\t \x03(\v2#.assetmanagerd.v1.Asset.LabelsEntryR\x06labels\x12\x1d\n" +
- "\n" +
- "created_by\x18\n" +
- " \x01(\tR\tcreatedBy\x12\x1d\n" +
- "\n" +
- "created_at\x18\v \x01(\x03R\tcreatedAt\x12(\n" +
- "\x10last_accessed_at\x18\f \x01(\x03R\x0elastAccessedAt\x12'\n" +
- "\x0freference_count\x18\r \x01(\x05R\x0ereferenceCount\x12\x19\n" +
- "\bbuild_id\x18\x0e \x01(\tR\abuildId\x12!\n" +
- "\fsource_image\x18\x0f \x01(\tR\vsourceImage\x1a9\n" +
- "\vLabelsEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"y\n" +
- "\x12UploadAssetRequest\x12C\n" +
- "\bmetadata\x18\x01 \x01(\v2%.assetmanagerd.v1.UploadAssetMetadataH\x00R\bmetadata\x12\x16\n" +
- "\x05chunk\x18\x02 \x01(\fH\x00R\x05chunkB\x06\n" +
- "\x04data\"\xec\x02\n" +
- "\x13UploadAssetMetadata\x12\x12\n" +
- "\x04name\x18\x01 \x01(\tR\x04name\x12/\n" +
- "\x04type\x18\x02 \x01(\x0e2\x1b.assetmanagerd.v1.AssetTypeR\x04type\x12\x1d\n" +
- "\n" +
- "size_bytes\x18\x03 \x01(\x03R\tsizeBytes\x12I\n" +
- "\x06labels\x18\x04 \x03(\v21.assetmanagerd.v1.UploadAssetMetadata.LabelsEntryR\x06labels\x12\x1d\n" +
- "\n" +
- "created_by\x18\x05 \x01(\tR\tcreatedBy\x12\x19\n" +
- "\bbuild_id\x18\x06 \x01(\tR\abuildId\x12!\n" +
- "\fsource_image\x18\a \x01(\tR\vsourceImage\x12\x0e\n" +
- "\x02id\x18\b \x01(\tR\x02id\x1a9\n" +
- "\vLabelsEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"D\n" +
- "\x13UploadAssetResponse\x12-\n" +
- "\x05asset\x18\x01 \x01(\v2\x17.assetmanagerd.v1.AssetR\x05asset\"\xe2\x03\n" +
- "\x14RegisterAssetRequest\x12\x12\n" +
- "\x04name\x18\x01 \x01(\tR\x04name\x12/\n" +
- "\x04type\x18\x02 \x01(\x0e2\x1b.assetmanagerd.v1.AssetTypeR\x04type\x12:\n" +
- "\abackend\x18\x03 \x01(\x0e2 .assetmanagerd.v1.StorageBackendR\abackend\x12\x1a\n" +
- "\blocation\x18\x04 \x01(\tR\blocation\x12\x1d\n" +
- "\n" +
- "size_bytes\x18\x05 \x01(\x03R\tsizeBytes\x12\x1a\n" +
- "\bchecksum\x18\x06 \x01(\tR\bchecksum\x12J\n" +
- "\x06labels\x18\a \x03(\v22.assetmanagerd.v1.RegisterAssetRequest.LabelsEntryR\x06labels\x12\x1d\n" +
- "\n" +
- "created_by\x18\b \x01(\tR\tcreatedBy\x12\x19\n" +
- "\bbuild_id\x18\t \x01(\tR\abuildId\x12!\n" +
- "\fsource_image\x18\n" +
- " \x01(\tR\vsourceImage\x12\x0e\n" +
- "\x02id\x18\v \x01(\tR\x02id\x1a9\n" +
- "\vLabelsEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"F\n" +
- "\x15RegisterAssetResponse\x12-\n" +
- "\x05asset\x18\x01 \x01(\v2\x17.assetmanagerd.v1.AssetR\x05asset\"D\n" +
- "\x0fGetAssetRequest\x12\x0e\n" +
- "\x02id\x18\x01 \x01(\tR\x02id\x12!\n" +
- "\fensure_local\x18\x02 \x01(\bR\vensureLocal\"`\n" +
- "\x10GetAssetResponse\x12-\n" +
- "\x05asset\x18\x01 \x01(\v2\x17.assetmanagerd.v1.AssetR\x05asset\x12\x1d\n" +
- "\n" +
- "local_path\x18\x02 \x01(\tR\tlocalPath\"\xd8\x02\n" +
- "\x11ListAssetsRequest\x12/\n" +
- "\x04type\x18\x01 \x01(\x0e2\x1b.assetmanagerd.v1.AssetTypeR\x04type\x125\n" +
- "\x06status\x18\x02 \x01(\x0e2\x1d.assetmanagerd.v1.AssetStatusR\x06status\x12]\n" +
- "\x0elabel_selector\x18\x03 \x03(\v26.assetmanagerd.v1.ListAssetsRequest.LabelSelectorEntryR\rlabelSelector\x12\x1b\n" +
- "\tpage_size\x18\x04 \x01(\x05R\bpageSize\x12\x1d\n" +
- "\n" +
- "page_token\x18\x05 \x01(\tR\tpageToken\x1a@\n" +
- "\x12LabelSelectorEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"m\n" +
- "\x12ListAssetsResponse\x12/\n" +
- "\x06assets\x18\x01 \x03(\v2\x17.assetmanagerd.v1.AssetR\x06assets\x12&\n" +
- "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"r\n" +
- "\x13AcquireAssetRequest\x12\x19\n" +
- "\basset_id\x18\x01 \x01(\tR\aassetId\x12\x1f\n" +
- "\vacquired_by\x18\x02 \x01(\tR\n" +
- "acquiredBy\x12\x1f\n" +
- "\vttl_seconds\x18\x03 \x01(\x03R\n" +
- "ttlSeconds\"`\n" +
- "\x14AcquireAssetResponse\x12-\n" +
- "\x05asset\x18\x01 \x01(\v2\x17.assetmanagerd.v1.AssetR\x05asset\x12\x19\n" +
- "\blease_id\x18\x02 \x01(\tR\aleaseId\"0\n" +
- "\x13ReleaseAssetRequest\x12\x19\n" +
- "\blease_id\x18\x01 \x01(\tR\aleaseId\"E\n" +
- "\x14ReleaseAssetResponse\x12-\n" +
- "\x05asset\x18\x01 \x01(\v2\x17.assetmanagerd.v1.AssetR\x05asset\":\n" +
- "\x12DeleteAssetRequest\x12\x0e\n" +
- "\x02id\x18\x01 \x01(\tR\x02id\x12\x14\n" +
- "\x05force\x18\x02 \x01(\bR\x05force\"I\n" +
- "\x13DeleteAssetResponse\x12\x18\n" +
- "\adeleted\x18\x01 \x01(\bR\adeleted\x12\x18\n" +
- "\amessage\x18\x02 \x01(\tR\amessage\"\x89\x01\n" +
- "\x15GarbageCollectRequest\x12&\n" +
- "\x0fmax_age_seconds\x18\x01 \x01(\x03R\rmaxAgeSeconds\x12/\n" +
- "\x13delete_unreferenced\x18\x02 \x01(\bR\x12deleteUnreferenced\x12\x17\n" +
- "\adry_run\x18\x03 \x01(\bR\x06dryRun\"y\n" +
- "\x16GarbageCollectResponse\x12>\n" +
- "\x0edeleted_assets\x18\x01 \x03(\v2\x17.assetmanagerd.v1.AssetR\rdeletedAssets\x12\x1f\n" +
- "\vbytes_freed\x18\x02 \x01(\x03R\n" +
- "bytesFreed\"w\n" +
- "\x14PrepareAssetsRequest\x12\x1b\n" +
- "\tasset_ids\x18\x01 \x03(\tR\bassetIds\x12\x1f\n" +
- "\vtarget_path\x18\x02 \x01(\tR\n" +
- "targetPath\x12!\n" +
- "\fprepared_for\x18\x03 \x01(\tR\vpreparedFor\"\xb0\x01\n" +
- "\x15PrepareAssetsResponse\x12X\n" +
- "\vasset_paths\x18\x01 \x03(\v27.assetmanagerd.v1.PrepareAssetsResponse.AssetPathsEntryR\n" +
- "assetPaths\x1a=\n" +
- "\x0fAssetPathsEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x9f\x03\n" +
- "\x12QueryAssetsRequest\x12/\n" +
- "\x04type\x18\x01 \x01(\x0e2\x1b.assetmanagerd.v1.AssetTypeR\x04type\x125\n" +
- "\x06status\x18\x02 \x01(\x0e2\x1d.assetmanagerd.v1.AssetStatusR\x06status\x12^\n" +
- "\x0elabel_selector\x18\x03 \x03(\v27.assetmanagerd.v1.QueryAssetsRequest.LabelSelectorEntryR\rlabelSelector\x12\x1b\n" +
- "\tpage_size\x18\x04 \x01(\x05R\bpageSize\x12\x1d\n" +
- "\n" +
- "page_token\x18\x05 \x01(\tR\tpageToken\x12C\n" +
- "\rbuild_options\x18\x06 \x01(\v2\x1e.assetmanagerd.v1.BuildOptionsR\fbuildOptions\x1a@\n" +
- "\x12LabelSelectorEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xe0\x02\n" +
- "\fBuildOptions\x12*\n" +
- "\x11enable_auto_build\x18\x01 \x01(\bR\x0fenableAutoBuild\x12.\n" +
- "\x13wait_for_completion\x18\x02 \x01(\bR\x11waitForCompletion\x122\n" +
- "\x15build_timeout_seconds\x18\x03 \x01(\x05R\x13buildTimeoutSeconds\x12R\n" +
- "\fbuild_labels\x18\x04 \x03(\v2/.assetmanagerd.v1.BuildOptions.BuildLabelsEntryR\vbuildLabels\x12,\n" +
- "\x12suggested_asset_id\x18\x05 \x01(\tR\x10suggestedAssetId\x1a>\n" +
- "\x10BuildLabelsEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xb6\x01\n" +
- "\x13QueryAssetsResponse\x12/\n" +
- "\x06assets\x18\x01 \x03(\v2\x17.assetmanagerd.v1.AssetR\x06assets\x12&\n" +
- "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\x12F\n" +
- "\x10triggered_builds\x18\x03 \x03(\v2\x1b.assetmanagerd.v1.BuildInfoR\x0ftriggeredBuilds\"\xa1\x01\n" +
- "\tBuildInfo\x12\x19\n" +
- "\bbuild_id\x18\x01 \x01(\tR\abuildId\x12!\n" +
- "\fdocker_image\x18\x02 \x01(\tR\vdockerImage\x12\x16\n" +
- "\x06status\x18\x03 \x01(\tR\x06status\x12#\n" +
- "\rerror_message\x18\x04 \x01(\tR\ferrorMessage\x12\x19\n" +
- "\basset_id\x18\x05 \x01(\tR\aassetId*\x87\x01\n" +
- "\tAssetType\x12\x1a\n" +
- "\x16ASSET_TYPE_UNSPECIFIED\x10\x00\x12\x15\n" +
- "\x11ASSET_TYPE_KERNEL\x10\x01\x12\x15\n" +
- "\x11ASSET_TYPE_ROOTFS\x10\x02\x12\x15\n" +
- "\x11ASSET_TYPE_INITRD\x10\x03\x12\x19\n" +
- "\x15ASSET_TYPE_DISK_IMAGE\x10\x04*\x96\x01\n" +
- "\vAssetStatus\x12\x1c\n" +
- "\x18ASSET_STATUS_UNSPECIFIED\x10\x00\x12\x1a\n" +
- "\x16ASSET_STATUS_UPLOADING\x10\x01\x12\x1a\n" +
- "\x16ASSET_STATUS_AVAILABLE\x10\x02\x12\x19\n" +
- "\x15ASSET_STATUS_DELETING\x10\x03\x12\x16\n" +
- "\x12ASSET_STATUS_ERROR\x10\x04*\x97\x01\n" +
- "\x0eStorageBackend\x12\x1f\n" +
- "\x1bSTORAGE_BACKEND_UNSPECIFIED\x10\x00\x12\x19\n" +
- "\x15STORAGE_BACKEND_LOCAL\x10\x01\x12\x16\n" +
- "\x12STORAGE_BACKEND_S3\x10\x02\x12\x18\n" +
- "\x14STORAGE_BACKEND_HTTP\x10\x03\x12\x17\n" +
- "\x13STORAGE_BACKEND_NFS\x10\x042\xbe\a\n" +
- "\x13AssetManagerService\x12\\\n" +
- "\vUploadAsset\x12$.assetmanagerd.v1.UploadAssetRequest\x1a%.assetmanagerd.v1.UploadAssetResponse(\x01\x12`\n" +
- "\rRegisterAsset\x12&.assetmanagerd.v1.RegisterAssetRequest\x1a'.assetmanagerd.v1.RegisterAssetResponse\x12Q\n" +
- "\bGetAsset\x12!.assetmanagerd.v1.GetAssetRequest\x1a\".assetmanagerd.v1.GetAssetResponse\x12W\n" +
- "\n" +
- "ListAssets\x12#.assetmanagerd.v1.ListAssetsRequest\x1a$.assetmanagerd.v1.ListAssetsResponse\x12]\n" +
- "\fAcquireAsset\x12%.assetmanagerd.v1.AcquireAssetRequest\x1a&.assetmanagerd.v1.AcquireAssetResponse\x12]\n" +
- "\fReleaseAsset\x12%.assetmanagerd.v1.ReleaseAssetRequest\x1a&.assetmanagerd.v1.ReleaseAssetResponse\x12Z\n" +
- "\vDeleteAsset\x12$.assetmanagerd.v1.DeleteAssetRequest\x1a%.assetmanagerd.v1.DeleteAssetResponse\x12c\n" +
- "\x0eGarbageCollect\x12'.assetmanagerd.v1.GarbageCollectRequest\x1a(.assetmanagerd.v1.GarbageCollectResponse\x12`\n" +
- "\rPrepareAssets\x12&.assetmanagerd.v1.PrepareAssetsRequest\x1a'.assetmanagerd.v1.PrepareAssetsResponse\x12Z\n" +
- "\vQueryAssets\x12$.assetmanagerd.v1.QueryAssetsRequest\x1a%.assetmanagerd.v1.QueryAssetsResponseBHZFgithub.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1;assetmanagerdv1b\x06proto3"
-
-var (
- file_assetmanagerd_v1_asset_proto_rawDescOnce sync.Once
- file_assetmanagerd_v1_asset_proto_rawDescData []byte
-)
-
-func file_assetmanagerd_v1_asset_proto_rawDescGZIP() []byte {
- file_assetmanagerd_v1_asset_proto_rawDescOnce.Do(func() {
- file_assetmanagerd_v1_asset_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_assetmanagerd_v1_asset_proto_rawDesc), len(file_assetmanagerd_v1_asset_proto_rawDesc)))
- })
- return file_assetmanagerd_v1_asset_proto_rawDescData
-}
-
-var file_assetmanagerd_v1_asset_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
-var file_assetmanagerd_v1_asset_proto_msgTypes = make([]protoimpl.MessageInfo, 31)
-var file_assetmanagerd_v1_asset_proto_goTypes = []any{
- (AssetType)(0), // 0: assetmanagerd.v1.AssetType
- (AssetStatus)(0), // 1: assetmanagerd.v1.AssetStatus
- (StorageBackend)(0), // 2: assetmanagerd.v1.StorageBackend
- (*Asset)(nil), // 3: assetmanagerd.v1.Asset
- (*UploadAssetRequest)(nil), // 4: assetmanagerd.v1.UploadAssetRequest
- (*UploadAssetMetadata)(nil), // 5: assetmanagerd.v1.UploadAssetMetadata
- (*UploadAssetResponse)(nil), // 6: assetmanagerd.v1.UploadAssetResponse
- (*RegisterAssetRequest)(nil), // 7: assetmanagerd.v1.RegisterAssetRequest
- (*RegisterAssetResponse)(nil), // 8: assetmanagerd.v1.RegisterAssetResponse
- (*GetAssetRequest)(nil), // 9: assetmanagerd.v1.GetAssetRequest
- (*GetAssetResponse)(nil), // 10: assetmanagerd.v1.GetAssetResponse
- (*ListAssetsRequest)(nil), // 11: assetmanagerd.v1.ListAssetsRequest
- (*ListAssetsResponse)(nil), // 12: assetmanagerd.v1.ListAssetsResponse
- (*AcquireAssetRequest)(nil), // 13: assetmanagerd.v1.AcquireAssetRequest
- (*AcquireAssetResponse)(nil), // 14: assetmanagerd.v1.AcquireAssetResponse
- (*ReleaseAssetRequest)(nil), // 15: assetmanagerd.v1.ReleaseAssetRequest
- (*ReleaseAssetResponse)(nil), // 16: assetmanagerd.v1.ReleaseAssetResponse
- (*DeleteAssetRequest)(nil), // 17: assetmanagerd.v1.DeleteAssetRequest
- (*DeleteAssetResponse)(nil), // 18: assetmanagerd.v1.DeleteAssetResponse
- (*GarbageCollectRequest)(nil), // 19: assetmanagerd.v1.GarbageCollectRequest
- (*GarbageCollectResponse)(nil), // 20: assetmanagerd.v1.GarbageCollectResponse
- (*PrepareAssetsRequest)(nil), // 21: assetmanagerd.v1.PrepareAssetsRequest
- (*PrepareAssetsResponse)(nil), // 22: assetmanagerd.v1.PrepareAssetsResponse
- (*QueryAssetsRequest)(nil), // 23: assetmanagerd.v1.QueryAssetsRequest
- (*BuildOptions)(nil), // 24: assetmanagerd.v1.BuildOptions
- (*QueryAssetsResponse)(nil), // 25: assetmanagerd.v1.QueryAssetsResponse
- (*BuildInfo)(nil), // 26: assetmanagerd.v1.BuildInfo
- nil, // 27: assetmanagerd.v1.Asset.LabelsEntry
- nil, // 28: assetmanagerd.v1.UploadAssetMetadata.LabelsEntry
- nil, // 29: assetmanagerd.v1.RegisterAssetRequest.LabelsEntry
- nil, // 30: assetmanagerd.v1.ListAssetsRequest.LabelSelectorEntry
- nil, // 31: assetmanagerd.v1.PrepareAssetsResponse.AssetPathsEntry
- nil, // 32: assetmanagerd.v1.QueryAssetsRequest.LabelSelectorEntry
- nil, // 33: assetmanagerd.v1.BuildOptions.BuildLabelsEntry
-}
-var file_assetmanagerd_v1_asset_proto_depIdxs = []int32{
- 0, // 0: assetmanagerd.v1.Asset.type:type_name -> assetmanagerd.v1.AssetType
- 1, // 1: assetmanagerd.v1.Asset.status:type_name -> assetmanagerd.v1.AssetStatus
- 2, // 2: assetmanagerd.v1.Asset.backend:type_name -> assetmanagerd.v1.StorageBackend
- 27, // 3: assetmanagerd.v1.Asset.labels:type_name -> assetmanagerd.v1.Asset.LabelsEntry
- 5, // 4: assetmanagerd.v1.UploadAssetRequest.metadata:type_name -> assetmanagerd.v1.UploadAssetMetadata
- 0, // 5: assetmanagerd.v1.UploadAssetMetadata.type:type_name -> assetmanagerd.v1.AssetType
- 28, // 6: assetmanagerd.v1.UploadAssetMetadata.labels:type_name -> assetmanagerd.v1.UploadAssetMetadata.LabelsEntry
- 3, // 7: assetmanagerd.v1.UploadAssetResponse.asset:type_name -> assetmanagerd.v1.Asset
- 0, // 8: assetmanagerd.v1.RegisterAssetRequest.type:type_name -> assetmanagerd.v1.AssetType
- 2, // 9: assetmanagerd.v1.RegisterAssetRequest.backend:type_name -> assetmanagerd.v1.StorageBackend
- 29, // 10: assetmanagerd.v1.RegisterAssetRequest.labels:type_name -> assetmanagerd.v1.RegisterAssetRequest.LabelsEntry
- 3, // 11: assetmanagerd.v1.RegisterAssetResponse.asset:type_name -> assetmanagerd.v1.Asset
- 3, // 12: assetmanagerd.v1.GetAssetResponse.asset:type_name -> assetmanagerd.v1.Asset
- 0, // 13: assetmanagerd.v1.ListAssetsRequest.type:type_name -> assetmanagerd.v1.AssetType
- 1, // 14: assetmanagerd.v1.ListAssetsRequest.status:type_name -> assetmanagerd.v1.AssetStatus
- 30, // 15: assetmanagerd.v1.ListAssetsRequest.label_selector:type_name -> assetmanagerd.v1.ListAssetsRequest.LabelSelectorEntry
- 3, // 16: assetmanagerd.v1.ListAssetsResponse.assets:type_name -> assetmanagerd.v1.Asset
- 3, // 17: assetmanagerd.v1.AcquireAssetResponse.asset:type_name -> assetmanagerd.v1.Asset
- 3, // 18: assetmanagerd.v1.ReleaseAssetResponse.asset:type_name -> assetmanagerd.v1.Asset
- 3, // 19: assetmanagerd.v1.GarbageCollectResponse.deleted_assets:type_name -> assetmanagerd.v1.Asset
- 31, // 20: assetmanagerd.v1.PrepareAssetsResponse.asset_paths:type_name -> assetmanagerd.v1.PrepareAssetsResponse.AssetPathsEntry
- 0, // 21: assetmanagerd.v1.QueryAssetsRequest.type:type_name -> assetmanagerd.v1.AssetType
- 1, // 22: assetmanagerd.v1.QueryAssetsRequest.status:type_name -> assetmanagerd.v1.AssetStatus
- 32, // 23: assetmanagerd.v1.QueryAssetsRequest.label_selector:type_name -> assetmanagerd.v1.QueryAssetsRequest.LabelSelectorEntry
- 24, // 24: assetmanagerd.v1.QueryAssetsRequest.build_options:type_name -> assetmanagerd.v1.BuildOptions
- 33, // 25: assetmanagerd.v1.BuildOptions.build_labels:type_name -> assetmanagerd.v1.BuildOptions.BuildLabelsEntry
- 3, // 26: assetmanagerd.v1.QueryAssetsResponse.assets:type_name -> assetmanagerd.v1.Asset
- 26, // 27: assetmanagerd.v1.QueryAssetsResponse.triggered_builds:type_name -> assetmanagerd.v1.BuildInfo
- 4, // 28: assetmanagerd.v1.AssetManagerService.UploadAsset:input_type -> assetmanagerd.v1.UploadAssetRequest
- 7, // 29: assetmanagerd.v1.AssetManagerService.RegisterAsset:input_type -> assetmanagerd.v1.RegisterAssetRequest
- 9, // 30: assetmanagerd.v1.AssetManagerService.GetAsset:input_type -> assetmanagerd.v1.GetAssetRequest
- 11, // 31: assetmanagerd.v1.AssetManagerService.ListAssets:input_type -> assetmanagerd.v1.ListAssetsRequest
- 13, // 32: assetmanagerd.v1.AssetManagerService.AcquireAsset:input_type -> assetmanagerd.v1.AcquireAssetRequest
- 15, // 33: assetmanagerd.v1.AssetManagerService.ReleaseAsset:input_type -> assetmanagerd.v1.ReleaseAssetRequest
- 17, // 34: assetmanagerd.v1.AssetManagerService.DeleteAsset:input_type -> assetmanagerd.v1.DeleteAssetRequest
- 19, // 35: assetmanagerd.v1.AssetManagerService.GarbageCollect:input_type -> assetmanagerd.v1.GarbageCollectRequest
- 21, // 36: assetmanagerd.v1.AssetManagerService.PrepareAssets:input_type -> assetmanagerd.v1.PrepareAssetsRequest
- 23, // 37: assetmanagerd.v1.AssetManagerService.QueryAssets:input_type -> assetmanagerd.v1.QueryAssetsRequest
- 6, // 38: assetmanagerd.v1.AssetManagerService.UploadAsset:output_type -> assetmanagerd.v1.UploadAssetResponse
- 8, // 39: assetmanagerd.v1.AssetManagerService.RegisterAsset:output_type -> assetmanagerd.v1.RegisterAssetResponse
- 10, // 40: assetmanagerd.v1.AssetManagerService.GetAsset:output_type -> assetmanagerd.v1.GetAssetResponse
- 12, // 41: assetmanagerd.v1.AssetManagerService.ListAssets:output_type -> assetmanagerd.v1.ListAssetsResponse
- 14, // 42: assetmanagerd.v1.AssetManagerService.AcquireAsset:output_type -> assetmanagerd.v1.AcquireAssetResponse
- 16, // 43: assetmanagerd.v1.AssetManagerService.ReleaseAsset:output_type -> assetmanagerd.v1.ReleaseAssetResponse
- 18, // 44: assetmanagerd.v1.AssetManagerService.DeleteAsset:output_type -> assetmanagerd.v1.DeleteAssetResponse
- 20, // 45: assetmanagerd.v1.AssetManagerService.GarbageCollect:output_type -> assetmanagerd.v1.GarbageCollectResponse
- 22, // 46: assetmanagerd.v1.AssetManagerService.PrepareAssets:output_type -> assetmanagerd.v1.PrepareAssetsResponse
- 25, // 47: assetmanagerd.v1.AssetManagerService.QueryAssets:output_type -> assetmanagerd.v1.QueryAssetsResponse
- 38, // [38:48] is the sub-list for method output_type
- 28, // [28:38] is the sub-list for method input_type
- 28, // [28:28] is the sub-list for extension type_name
- 28, // [28:28] is the sub-list for extension extendee
- 0, // [0:28] is the sub-list for field type_name
-}
-
-func init() { file_assetmanagerd_v1_asset_proto_init() }
-func file_assetmanagerd_v1_asset_proto_init() {
- if File_assetmanagerd_v1_asset_proto != nil {
- return
- }
- file_assetmanagerd_v1_asset_proto_msgTypes[1].OneofWrappers = []any{
- (*UploadAssetRequest_Metadata)(nil),
- (*UploadAssetRequest_Chunk)(nil),
- }
- type x struct{}
- out := protoimpl.TypeBuilder{
- File: protoimpl.DescBuilder{
- GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
- RawDescriptor: unsafe.Slice(unsafe.StringData(file_assetmanagerd_v1_asset_proto_rawDesc), len(file_assetmanagerd_v1_asset_proto_rawDesc)),
- NumEnums: 3,
- NumMessages: 31,
- NumExtensions: 0,
- NumServices: 1,
- },
- GoTypes: file_assetmanagerd_v1_asset_proto_goTypes,
- DependencyIndexes: file_assetmanagerd_v1_asset_proto_depIdxs,
- EnumInfos: file_assetmanagerd_v1_asset_proto_enumTypes,
- MessageInfos: file_assetmanagerd_v1_asset_proto_msgTypes,
- }.Build()
- File_assetmanagerd_v1_asset_proto = out.File
- file_assetmanagerd_v1_asset_proto_goTypes = nil
- file_assetmanagerd_v1_asset_proto_depIdxs = nil
-}
diff --git a/go/gen/proto/assetmanagerd/v1/assetmanagerdv1connect/asset.connect.go b/go/gen/proto/assetmanagerd/v1/assetmanagerdv1connect/asset.connect.go
deleted file mode 100644
index fe02147e24..0000000000
--- a/go/gen/proto/assetmanagerd/v1/assetmanagerdv1connect/asset.connect.go
+++ /dev/null
@@ -1,393 +0,0 @@
-// Code generated by protoc-gen-connect-go. DO NOT EDIT.
-//
-// Source: assetmanagerd/v1/asset.proto
-
-package assetmanagerdv1connect
-
-import (
- connect "connectrpc.com/connect"
- context "context"
- errors "errors"
- v1 "github.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1"
- http "net/http"
- strings "strings"
-)
-
-// This is a compile-time assertion to ensure that this generated file and the connect package are
-// compatible. If you get a compiler error that this constant is not defined, this code was
-// generated with a version of connect newer than the one compiled into your binary. You can fix the
-// problem by either regenerating this code with an older version of connect or updating the connect
-// version compiled into your binary.
-const _ = connect.IsAtLeastVersion1_13_0
-
-const (
- // AssetManagerServiceName is the fully-qualified name of the AssetManagerService service.
- AssetManagerServiceName = "assetmanagerd.v1.AssetManagerService"
-)
-
-// These constants are the fully-qualified names of the RPCs defined in this package. They're
-// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
-//
-// Note that these are different from the fully-qualified method names used by
-// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
-// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
-// period.
-const (
- // AssetManagerServiceUploadAssetProcedure is the fully-qualified name of the AssetManagerService's
- // UploadAsset RPC.
- AssetManagerServiceUploadAssetProcedure = "/assetmanagerd.v1.AssetManagerService/UploadAsset"
- // AssetManagerServiceRegisterAssetProcedure is the fully-qualified name of the
- // AssetManagerService's RegisterAsset RPC.
- AssetManagerServiceRegisterAssetProcedure = "/assetmanagerd.v1.AssetManagerService/RegisterAsset"
- // AssetManagerServiceGetAssetProcedure is the fully-qualified name of the AssetManagerService's
- // GetAsset RPC.
- AssetManagerServiceGetAssetProcedure = "/assetmanagerd.v1.AssetManagerService/GetAsset"
- // AssetManagerServiceListAssetsProcedure is the fully-qualified name of the AssetManagerService's
- // ListAssets RPC.
- AssetManagerServiceListAssetsProcedure = "/assetmanagerd.v1.AssetManagerService/ListAssets"
- // AssetManagerServiceAcquireAssetProcedure is the fully-qualified name of the AssetManagerService's
- // AcquireAsset RPC.
- AssetManagerServiceAcquireAssetProcedure = "/assetmanagerd.v1.AssetManagerService/AcquireAsset"
- // AssetManagerServiceReleaseAssetProcedure is the fully-qualified name of the AssetManagerService's
- // ReleaseAsset RPC.
- AssetManagerServiceReleaseAssetProcedure = "/assetmanagerd.v1.AssetManagerService/ReleaseAsset"
- // AssetManagerServiceDeleteAssetProcedure is the fully-qualified name of the AssetManagerService's
- // DeleteAsset RPC.
- AssetManagerServiceDeleteAssetProcedure = "/assetmanagerd.v1.AssetManagerService/DeleteAsset"
- // AssetManagerServiceGarbageCollectProcedure is the fully-qualified name of the
- // AssetManagerService's GarbageCollect RPC.
- AssetManagerServiceGarbageCollectProcedure = "/assetmanagerd.v1.AssetManagerService/GarbageCollect"
- // AssetManagerServicePrepareAssetsProcedure is the fully-qualified name of the
- // AssetManagerService's PrepareAssets RPC.
- AssetManagerServicePrepareAssetsProcedure = "/assetmanagerd.v1.AssetManagerService/PrepareAssets"
- // AssetManagerServiceQueryAssetsProcedure is the fully-qualified name of the AssetManagerService's
- // QueryAssets RPC.
- AssetManagerServiceQueryAssetsProcedure = "/assetmanagerd.v1.AssetManagerService/QueryAssets"
-)
-
-// AssetManagerServiceClient is a client for the assetmanagerd.v1.AssetManagerService service.
-type AssetManagerServiceClient interface {
- // Upload and register an asset in one operation
- UploadAsset(context.Context) *connect.ClientStreamForClient[v1.UploadAssetRequest, v1.UploadAssetResponse]
- // Register a new asset (called by builderd after creating images)
- RegisterAsset(context.Context, *connect.Request[v1.RegisterAssetRequest]) (*connect.Response[v1.RegisterAssetResponse], error)
- // Get asset location and metadata
- GetAsset(context.Context, *connect.Request[v1.GetAssetRequest]) (*connect.Response[v1.GetAssetResponse], error)
- // List available assets with filtering
- ListAssets(context.Context, *connect.Request[v1.ListAssetsRequest]) (*connect.Response[v1.ListAssetsResponse], error)
- // Mark asset as in-use (reference counting for GC)
- AcquireAsset(context.Context, *connect.Request[v1.AcquireAssetRequest]) (*connect.Response[v1.AcquireAssetResponse], error)
- // Release asset reference (decrements ref count)
- ReleaseAsset(context.Context, *connect.Request[v1.ReleaseAssetRequest]) (*connect.Response[v1.ReleaseAssetResponse], error)
- // Delete an asset (only if ref count is 0)
- DeleteAsset(context.Context, *connect.Request[v1.DeleteAssetRequest]) (*connect.Response[v1.DeleteAssetResponse], error)
- // Trigger garbage collection of unused assets
- GarbageCollect(context.Context, *connect.Request[v1.GarbageCollectRequest]) (*connect.Response[v1.GarbageCollectResponse], error)
- // Pre-stage assets for a specific host/jailer
- PrepareAssets(context.Context, *connect.Request[v1.PrepareAssetsRequest]) (*connect.Response[v1.PrepareAssetsResponse], error)
- // Query assets with automatic build triggering if not found
- // This is the enhanced version of ListAssets that supports automatic asset creation
- QueryAssets(context.Context, *connect.Request[v1.QueryAssetsRequest]) (*connect.Response[v1.QueryAssetsResponse], error)
-}
-
-// NewAssetManagerServiceClient constructs a client for the assetmanagerd.v1.AssetManagerService
-// service. By default, it uses the Connect protocol with the binary Protobuf Codec, asks for
-// gzipped responses, and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply
-// the connect.WithGRPC() or connect.WithGRPCWeb() options.
-//
-// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
-// http://api.acme.com or https://acme.com/grpc).
-func NewAssetManagerServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) AssetManagerServiceClient {
- baseURL = strings.TrimRight(baseURL, "/")
- assetManagerServiceMethods := v1.File_assetmanagerd_v1_asset_proto.Services().ByName("AssetManagerService").Methods()
- return &assetManagerServiceClient{
- uploadAsset: connect.NewClient[v1.UploadAssetRequest, v1.UploadAssetResponse](
- httpClient,
- baseURL+AssetManagerServiceUploadAssetProcedure,
- connect.WithSchema(assetManagerServiceMethods.ByName("UploadAsset")),
- connect.WithClientOptions(opts...),
- ),
- registerAsset: connect.NewClient[v1.RegisterAssetRequest, v1.RegisterAssetResponse](
- httpClient,
- baseURL+AssetManagerServiceRegisterAssetProcedure,
- connect.WithSchema(assetManagerServiceMethods.ByName("RegisterAsset")),
- connect.WithClientOptions(opts...),
- ),
- getAsset: connect.NewClient[v1.GetAssetRequest, v1.GetAssetResponse](
- httpClient,
- baseURL+AssetManagerServiceGetAssetProcedure,
- connect.WithSchema(assetManagerServiceMethods.ByName("GetAsset")),
- connect.WithClientOptions(opts...),
- ),
- listAssets: connect.NewClient[v1.ListAssetsRequest, v1.ListAssetsResponse](
- httpClient,
- baseURL+AssetManagerServiceListAssetsProcedure,
- connect.WithSchema(assetManagerServiceMethods.ByName("ListAssets")),
- connect.WithClientOptions(opts...),
- ),
- acquireAsset: connect.NewClient[v1.AcquireAssetRequest, v1.AcquireAssetResponse](
- httpClient,
- baseURL+AssetManagerServiceAcquireAssetProcedure,
- connect.WithSchema(assetManagerServiceMethods.ByName("AcquireAsset")),
- connect.WithClientOptions(opts...),
- ),
- releaseAsset: connect.NewClient[v1.ReleaseAssetRequest, v1.ReleaseAssetResponse](
- httpClient,
- baseURL+AssetManagerServiceReleaseAssetProcedure,
- connect.WithSchema(assetManagerServiceMethods.ByName("ReleaseAsset")),
- connect.WithClientOptions(opts...),
- ),
- deleteAsset: connect.NewClient[v1.DeleteAssetRequest, v1.DeleteAssetResponse](
- httpClient,
- baseURL+AssetManagerServiceDeleteAssetProcedure,
- connect.WithSchema(assetManagerServiceMethods.ByName("DeleteAsset")),
- connect.WithClientOptions(opts...),
- ),
- garbageCollect: connect.NewClient[v1.GarbageCollectRequest, v1.GarbageCollectResponse](
- httpClient,
- baseURL+AssetManagerServiceGarbageCollectProcedure,
- connect.WithSchema(assetManagerServiceMethods.ByName("GarbageCollect")),
- connect.WithClientOptions(opts...),
- ),
- prepareAssets: connect.NewClient[v1.PrepareAssetsRequest, v1.PrepareAssetsResponse](
- httpClient,
- baseURL+AssetManagerServicePrepareAssetsProcedure,
- connect.WithSchema(assetManagerServiceMethods.ByName("PrepareAssets")),
- connect.WithClientOptions(opts...),
- ),
- queryAssets: connect.NewClient[v1.QueryAssetsRequest, v1.QueryAssetsResponse](
- httpClient,
- baseURL+AssetManagerServiceQueryAssetsProcedure,
- connect.WithSchema(assetManagerServiceMethods.ByName("QueryAssets")),
- connect.WithClientOptions(opts...),
- ),
- }
-}
-
-// assetManagerServiceClient implements AssetManagerServiceClient.
-type assetManagerServiceClient struct {
- uploadAsset *connect.Client[v1.UploadAssetRequest, v1.UploadAssetResponse]
- registerAsset *connect.Client[v1.RegisterAssetRequest, v1.RegisterAssetResponse]
- getAsset *connect.Client[v1.GetAssetRequest, v1.GetAssetResponse]
- listAssets *connect.Client[v1.ListAssetsRequest, v1.ListAssetsResponse]
- acquireAsset *connect.Client[v1.AcquireAssetRequest, v1.AcquireAssetResponse]
- releaseAsset *connect.Client[v1.ReleaseAssetRequest, v1.ReleaseAssetResponse]
- deleteAsset *connect.Client[v1.DeleteAssetRequest, v1.DeleteAssetResponse]
- garbageCollect *connect.Client[v1.GarbageCollectRequest, v1.GarbageCollectResponse]
- prepareAssets *connect.Client[v1.PrepareAssetsRequest, v1.PrepareAssetsResponse]
- queryAssets *connect.Client[v1.QueryAssetsRequest, v1.QueryAssetsResponse]
-}
-
-// UploadAsset calls assetmanagerd.v1.AssetManagerService.UploadAsset.
-func (c *assetManagerServiceClient) UploadAsset(ctx context.Context) *connect.ClientStreamForClient[v1.UploadAssetRequest, v1.UploadAssetResponse] {
- return c.uploadAsset.CallClientStream(ctx)
-}
-
-// RegisterAsset calls assetmanagerd.v1.AssetManagerService.RegisterAsset.
-func (c *assetManagerServiceClient) RegisterAsset(ctx context.Context, req *connect.Request[v1.RegisterAssetRequest]) (*connect.Response[v1.RegisterAssetResponse], error) {
- return c.registerAsset.CallUnary(ctx, req)
-}
-
-// GetAsset calls assetmanagerd.v1.AssetManagerService.GetAsset.
-func (c *assetManagerServiceClient) GetAsset(ctx context.Context, req *connect.Request[v1.GetAssetRequest]) (*connect.Response[v1.GetAssetResponse], error) {
- return c.getAsset.CallUnary(ctx, req)
-}
-
-// ListAssets calls assetmanagerd.v1.AssetManagerService.ListAssets.
-func (c *assetManagerServiceClient) ListAssets(ctx context.Context, req *connect.Request[v1.ListAssetsRequest]) (*connect.Response[v1.ListAssetsResponse], error) {
- return c.listAssets.CallUnary(ctx, req)
-}
-
-// AcquireAsset calls assetmanagerd.v1.AssetManagerService.AcquireAsset.
-func (c *assetManagerServiceClient) AcquireAsset(ctx context.Context, req *connect.Request[v1.AcquireAssetRequest]) (*connect.Response[v1.AcquireAssetResponse], error) {
- return c.acquireAsset.CallUnary(ctx, req)
-}
-
-// ReleaseAsset calls assetmanagerd.v1.AssetManagerService.ReleaseAsset.
-func (c *assetManagerServiceClient) ReleaseAsset(ctx context.Context, req *connect.Request[v1.ReleaseAssetRequest]) (*connect.Response[v1.ReleaseAssetResponse], error) {
- return c.releaseAsset.CallUnary(ctx, req)
-}
-
-// DeleteAsset calls assetmanagerd.v1.AssetManagerService.DeleteAsset.
-func (c *assetManagerServiceClient) DeleteAsset(ctx context.Context, req *connect.Request[v1.DeleteAssetRequest]) (*connect.Response[v1.DeleteAssetResponse], error) {
- return c.deleteAsset.CallUnary(ctx, req)
-}
-
-// GarbageCollect calls assetmanagerd.v1.AssetManagerService.GarbageCollect.
-func (c *assetManagerServiceClient) GarbageCollect(ctx context.Context, req *connect.Request[v1.GarbageCollectRequest]) (*connect.Response[v1.GarbageCollectResponse], error) {
- return c.garbageCollect.CallUnary(ctx, req)
-}
-
-// PrepareAssets calls assetmanagerd.v1.AssetManagerService.PrepareAssets.
-func (c *assetManagerServiceClient) PrepareAssets(ctx context.Context, req *connect.Request[v1.PrepareAssetsRequest]) (*connect.Response[v1.PrepareAssetsResponse], error) {
- return c.prepareAssets.CallUnary(ctx, req)
-}
-
-// QueryAssets calls assetmanagerd.v1.AssetManagerService.QueryAssets.
-func (c *assetManagerServiceClient) QueryAssets(ctx context.Context, req *connect.Request[v1.QueryAssetsRequest]) (*connect.Response[v1.QueryAssetsResponse], error) {
- return c.queryAssets.CallUnary(ctx, req)
-}
-
-// AssetManagerServiceHandler is an implementation of the assetmanagerd.v1.AssetManagerService
-// service.
-type AssetManagerServiceHandler interface {
- // Upload and register an asset in one operation
- UploadAsset(context.Context, *connect.ClientStream[v1.UploadAssetRequest]) (*connect.Response[v1.UploadAssetResponse], error)
- // Register a new asset (called by builderd after creating images)
- RegisterAsset(context.Context, *connect.Request[v1.RegisterAssetRequest]) (*connect.Response[v1.RegisterAssetResponse], error)
- // Get asset location and metadata
- GetAsset(context.Context, *connect.Request[v1.GetAssetRequest]) (*connect.Response[v1.GetAssetResponse], error)
- // List available assets with filtering
- ListAssets(context.Context, *connect.Request[v1.ListAssetsRequest]) (*connect.Response[v1.ListAssetsResponse], error)
- // Mark asset as in-use (reference counting for GC)
- AcquireAsset(context.Context, *connect.Request[v1.AcquireAssetRequest]) (*connect.Response[v1.AcquireAssetResponse], error)
- // Release asset reference (decrements ref count)
- ReleaseAsset(context.Context, *connect.Request[v1.ReleaseAssetRequest]) (*connect.Response[v1.ReleaseAssetResponse], error)
- // Delete an asset (only if ref count is 0)
- DeleteAsset(context.Context, *connect.Request[v1.DeleteAssetRequest]) (*connect.Response[v1.DeleteAssetResponse], error)
- // Trigger garbage collection of unused assets
- GarbageCollect(context.Context, *connect.Request[v1.GarbageCollectRequest]) (*connect.Response[v1.GarbageCollectResponse], error)
- // Pre-stage assets for a specific host/jailer
- PrepareAssets(context.Context, *connect.Request[v1.PrepareAssetsRequest]) (*connect.Response[v1.PrepareAssetsResponse], error)
- // Query assets with automatic build triggering if not found
- // This is the enhanced version of ListAssets that supports automatic asset creation
- QueryAssets(context.Context, *connect.Request[v1.QueryAssetsRequest]) (*connect.Response[v1.QueryAssetsResponse], error)
-}
-
-// NewAssetManagerServiceHandler builds an HTTP handler from the service implementation. It returns
-// the path on which to mount the handler and the handler itself.
-//
-// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
-// and JSON codecs. They also support gzip compression.
-func NewAssetManagerServiceHandler(svc AssetManagerServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
- assetManagerServiceMethods := v1.File_assetmanagerd_v1_asset_proto.Services().ByName("AssetManagerService").Methods()
- assetManagerServiceUploadAssetHandler := connect.NewClientStreamHandler(
- AssetManagerServiceUploadAssetProcedure,
- svc.UploadAsset,
- connect.WithSchema(assetManagerServiceMethods.ByName("UploadAsset")),
- connect.WithHandlerOptions(opts...),
- )
- assetManagerServiceRegisterAssetHandler := connect.NewUnaryHandler(
- AssetManagerServiceRegisterAssetProcedure,
- svc.RegisterAsset,
- connect.WithSchema(assetManagerServiceMethods.ByName("RegisterAsset")),
- connect.WithHandlerOptions(opts...),
- )
- assetManagerServiceGetAssetHandler := connect.NewUnaryHandler(
- AssetManagerServiceGetAssetProcedure,
- svc.GetAsset,
- connect.WithSchema(assetManagerServiceMethods.ByName("GetAsset")),
- connect.WithHandlerOptions(opts...),
- )
- assetManagerServiceListAssetsHandler := connect.NewUnaryHandler(
- AssetManagerServiceListAssetsProcedure,
- svc.ListAssets,
- connect.WithSchema(assetManagerServiceMethods.ByName("ListAssets")),
- connect.WithHandlerOptions(opts...),
- )
- assetManagerServiceAcquireAssetHandler := connect.NewUnaryHandler(
- AssetManagerServiceAcquireAssetProcedure,
- svc.AcquireAsset,
- connect.WithSchema(assetManagerServiceMethods.ByName("AcquireAsset")),
- connect.WithHandlerOptions(opts...),
- )
- assetManagerServiceReleaseAssetHandler := connect.NewUnaryHandler(
- AssetManagerServiceReleaseAssetProcedure,
- svc.ReleaseAsset,
- connect.WithSchema(assetManagerServiceMethods.ByName("ReleaseAsset")),
- connect.WithHandlerOptions(opts...),
- )
- assetManagerServiceDeleteAssetHandler := connect.NewUnaryHandler(
- AssetManagerServiceDeleteAssetProcedure,
- svc.DeleteAsset,
- connect.WithSchema(assetManagerServiceMethods.ByName("DeleteAsset")),
- connect.WithHandlerOptions(opts...),
- )
- assetManagerServiceGarbageCollectHandler := connect.NewUnaryHandler(
- AssetManagerServiceGarbageCollectProcedure,
- svc.GarbageCollect,
- connect.WithSchema(assetManagerServiceMethods.ByName("GarbageCollect")),
- connect.WithHandlerOptions(opts...),
- )
- assetManagerServicePrepareAssetsHandler := connect.NewUnaryHandler(
- AssetManagerServicePrepareAssetsProcedure,
- svc.PrepareAssets,
- connect.WithSchema(assetManagerServiceMethods.ByName("PrepareAssets")),
- connect.WithHandlerOptions(opts...),
- )
- assetManagerServiceQueryAssetsHandler := connect.NewUnaryHandler(
- AssetManagerServiceQueryAssetsProcedure,
- svc.QueryAssets,
- connect.WithSchema(assetManagerServiceMethods.ByName("QueryAssets")),
- connect.WithHandlerOptions(opts...),
- )
- return "/assetmanagerd.v1.AssetManagerService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- switch r.URL.Path {
- case AssetManagerServiceUploadAssetProcedure:
- assetManagerServiceUploadAssetHandler.ServeHTTP(w, r)
- case AssetManagerServiceRegisterAssetProcedure:
- assetManagerServiceRegisterAssetHandler.ServeHTTP(w, r)
- case AssetManagerServiceGetAssetProcedure:
- assetManagerServiceGetAssetHandler.ServeHTTP(w, r)
- case AssetManagerServiceListAssetsProcedure:
- assetManagerServiceListAssetsHandler.ServeHTTP(w, r)
- case AssetManagerServiceAcquireAssetProcedure:
- assetManagerServiceAcquireAssetHandler.ServeHTTP(w, r)
- case AssetManagerServiceReleaseAssetProcedure:
- assetManagerServiceReleaseAssetHandler.ServeHTTP(w, r)
- case AssetManagerServiceDeleteAssetProcedure:
- assetManagerServiceDeleteAssetHandler.ServeHTTP(w, r)
- case AssetManagerServiceGarbageCollectProcedure:
- assetManagerServiceGarbageCollectHandler.ServeHTTP(w, r)
- case AssetManagerServicePrepareAssetsProcedure:
- assetManagerServicePrepareAssetsHandler.ServeHTTP(w, r)
- case AssetManagerServiceQueryAssetsProcedure:
- assetManagerServiceQueryAssetsHandler.ServeHTTP(w, r)
- default:
- http.NotFound(w, r)
- }
- })
-}
-
-// UnimplementedAssetManagerServiceHandler returns CodeUnimplemented from all methods.
-type UnimplementedAssetManagerServiceHandler struct{}
-
-func (UnimplementedAssetManagerServiceHandler) UploadAsset(context.Context, *connect.ClientStream[v1.UploadAssetRequest]) (*connect.Response[v1.UploadAssetResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("assetmanagerd.v1.AssetManagerService.UploadAsset is not implemented"))
-}
-
-func (UnimplementedAssetManagerServiceHandler) RegisterAsset(context.Context, *connect.Request[v1.RegisterAssetRequest]) (*connect.Response[v1.RegisterAssetResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("assetmanagerd.v1.AssetManagerService.RegisterAsset is not implemented"))
-}
-
-func (UnimplementedAssetManagerServiceHandler) GetAsset(context.Context, *connect.Request[v1.GetAssetRequest]) (*connect.Response[v1.GetAssetResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("assetmanagerd.v1.AssetManagerService.GetAsset is not implemented"))
-}
-
-func (UnimplementedAssetManagerServiceHandler) ListAssets(context.Context, *connect.Request[v1.ListAssetsRequest]) (*connect.Response[v1.ListAssetsResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("assetmanagerd.v1.AssetManagerService.ListAssets is not implemented"))
-}
-
-func (UnimplementedAssetManagerServiceHandler) AcquireAsset(context.Context, *connect.Request[v1.AcquireAssetRequest]) (*connect.Response[v1.AcquireAssetResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("assetmanagerd.v1.AssetManagerService.AcquireAsset is not implemented"))
-}
-
-func (UnimplementedAssetManagerServiceHandler) ReleaseAsset(context.Context, *connect.Request[v1.ReleaseAssetRequest]) (*connect.Response[v1.ReleaseAssetResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("assetmanagerd.v1.AssetManagerService.ReleaseAsset is not implemented"))
-}
-
-func (UnimplementedAssetManagerServiceHandler) DeleteAsset(context.Context, *connect.Request[v1.DeleteAssetRequest]) (*connect.Response[v1.DeleteAssetResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("assetmanagerd.v1.AssetManagerService.DeleteAsset is not implemented"))
-}
-
-func (UnimplementedAssetManagerServiceHandler) GarbageCollect(context.Context, *connect.Request[v1.GarbageCollectRequest]) (*connect.Response[v1.GarbageCollectResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("assetmanagerd.v1.AssetManagerService.GarbageCollect is not implemented"))
-}
-
-func (UnimplementedAssetManagerServiceHandler) PrepareAssets(context.Context, *connect.Request[v1.PrepareAssetsRequest]) (*connect.Response[v1.PrepareAssetsResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("assetmanagerd.v1.AssetManagerService.PrepareAssets is not implemented"))
-}
-
-func (UnimplementedAssetManagerServiceHandler) QueryAssets(context.Context, *connect.Request[v1.QueryAssetsRequest]) (*connect.Response[v1.QueryAssetsResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("assetmanagerd.v1.AssetManagerService.QueryAssets is not implemented"))
-}
diff --git a/go/gen/proto/billaged/v1/billagedv1connect/billing.connect.go b/go/gen/proto/billaged/v1/billagedv1connect/billing.connect.go
deleted file mode 100644
index 4842de603d..0000000000
--- a/go/gen/proto/billaged/v1/billagedv1connect/billing.connect.go
+++ /dev/null
@@ -1,225 +0,0 @@
-// Code generated by protoc-gen-connect-go. DO NOT EDIT.
-//
-// Source: billaged/v1/billing.proto
-
-package billagedv1connect
-
-import (
- connect "connectrpc.com/connect"
- context "context"
- errors "errors"
- v1 "github.com/unkeyed/unkey/go/gen/proto/billaged/v1"
- http "net/http"
- strings "strings"
-)
-
-// This is a compile-time assertion to ensure that this generated file and the connect package are
-// compatible. If you get a compiler error that this constant is not defined, this code was
-// generated with a version of connect newer than the one compiled into your binary. You can fix the
-// problem by either regenerating this code with an older version of connect or updating the connect
-// version compiled into your binary.
-const _ = connect.IsAtLeastVersion1_13_0
-
-const (
- // BillingServiceName is the fully-qualified name of the BillingService service.
- BillingServiceName = "billaged.v1.BillingService"
-)
-
-// These constants are the fully-qualified names of the RPCs defined in this package. They're
-// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
-//
-// Note that these are different from the fully-qualified method names used by
-// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
-// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
-// period.
-const (
- // BillingServiceSendMetricsBatchProcedure is the fully-qualified name of the BillingService's
- // SendMetricsBatch RPC.
- BillingServiceSendMetricsBatchProcedure = "/billaged.v1.BillingService/SendMetricsBatch"
- // BillingServiceSendHeartbeatProcedure is the fully-qualified name of the BillingService's
- // SendHeartbeat RPC.
- BillingServiceSendHeartbeatProcedure = "/billaged.v1.BillingService/SendHeartbeat"
- // BillingServiceNotifyVmStartedProcedure is the fully-qualified name of the BillingService's
- // NotifyVmStarted RPC.
- BillingServiceNotifyVmStartedProcedure = "/billaged.v1.BillingService/NotifyVmStarted"
- // BillingServiceNotifyVmStoppedProcedure is the fully-qualified name of the BillingService's
- // NotifyVmStopped RPC.
- BillingServiceNotifyVmStoppedProcedure = "/billaged.v1.BillingService/NotifyVmStopped"
- // BillingServiceNotifyPossibleGapProcedure is the fully-qualified name of the BillingService's
- // NotifyPossibleGap RPC.
- BillingServiceNotifyPossibleGapProcedure = "/billaged.v1.BillingService/NotifyPossibleGap"
-)
-
-// BillingServiceClient is a client for the billaged.v1.BillingService service.
-type BillingServiceClient interface {
- SendMetricsBatch(context.Context, *connect.Request[v1.SendMetricsBatchRequest]) (*connect.Response[v1.SendMetricsBatchResponse], error)
- SendHeartbeat(context.Context, *connect.Request[v1.SendHeartbeatRequest]) (*connect.Response[v1.SendHeartbeatResponse], error)
- NotifyVmStarted(context.Context, *connect.Request[v1.NotifyVmStartedRequest]) (*connect.Response[v1.NotifyVmStartedResponse], error)
- NotifyVmStopped(context.Context, *connect.Request[v1.NotifyVmStoppedRequest]) (*connect.Response[v1.NotifyVmStoppedResponse], error)
- NotifyPossibleGap(context.Context, *connect.Request[v1.NotifyPossibleGapRequest]) (*connect.Response[v1.NotifyPossibleGapResponse], error)
-}
-
-// NewBillingServiceClient constructs a client for the billaged.v1.BillingService service. By
-// default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses,
-// and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the
-// connect.WithGRPC() or connect.WithGRPCWeb() options.
-//
-// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
-// http://api.acme.com or https://acme.com/grpc).
-func NewBillingServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) BillingServiceClient {
- baseURL = strings.TrimRight(baseURL, "/")
- billingServiceMethods := v1.File_billaged_v1_billing_proto.Services().ByName("BillingService").Methods()
- return &billingServiceClient{
- sendMetricsBatch: connect.NewClient[v1.SendMetricsBatchRequest, v1.SendMetricsBatchResponse](
- httpClient,
- baseURL+BillingServiceSendMetricsBatchProcedure,
- connect.WithSchema(billingServiceMethods.ByName("SendMetricsBatch")),
- connect.WithClientOptions(opts...),
- ),
- sendHeartbeat: connect.NewClient[v1.SendHeartbeatRequest, v1.SendHeartbeatResponse](
- httpClient,
- baseURL+BillingServiceSendHeartbeatProcedure,
- connect.WithSchema(billingServiceMethods.ByName("SendHeartbeat")),
- connect.WithClientOptions(opts...),
- ),
- notifyVmStarted: connect.NewClient[v1.NotifyVmStartedRequest, v1.NotifyVmStartedResponse](
- httpClient,
- baseURL+BillingServiceNotifyVmStartedProcedure,
- connect.WithSchema(billingServiceMethods.ByName("NotifyVmStarted")),
- connect.WithClientOptions(opts...),
- ),
- notifyVmStopped: connect.NewClient[v1.NotifyVmStoppedRequest, v1.NotifyVmStoppedResponse](
- httpClient,
- baseURL+BillingServiceNotifyVmStoppedProcedure,
- connect.WithSchema(billingServiceMethods.ByName("NotifyVmStopped")),
- connect.WithClientOptions(opts...),
- ),
- notifyPossibleGap: connect.NewClient[v1.NotifyPossibleGapRequest, v1.NotifyPossibleGapResponse](
- httpClient,
- baseURL+BillingServiceNotifyPossibleGapProcedure,
- connect.WithSchema(billingServiceMethods.ByName("NotifyPossibleGap")),
- connect.WithClientOptions(opts...),
- ),
- }
-}
-
-// billingServiceClient implements BillingServiceClient.
-type billingServiceClient struct {
- sendMetricsBatch *connect.Client[v1.SendMetricsBatchRequest, v1.SendMetricsBatchResponse]
- sendHeartbeat *connect.Client[v1.SendHeartbeatRequest, v1.SendHeartbeatResponse]
- notifyVmStarted *connect.Client[v1.NotifyVmStartedRequest, v1.NotifyVmStartedResponse]
- notifyVmStopped *connect.Client[v1.NotifyVmStoppedRequest, v1.NotifyVmStoppedResponse]
- notifyPossibleGap *connect.Client[v1.NotifyPossibleGapRequest, v1.NotifyPossibleGapResponse]
-}
-
-// SendMetricsBatch calls billaged.v1.BillingService.SendMetricsBatch.
-func (c *billingServiceClient) SendMetricsBatch(ctx context.Context, req *connect.Request[v1.SendMetricsBatchRequest]) (*connect.Response[v1.SendMetricsBatchResponse], error) {
- return c.sendMetricsBatch.CallUnary(ctx, req)
-}
-
-// SendHeartbeat calls billaged.v1.BillingService.SendHeartbeat.
-func (c *billingServiceClient) SendHeartbeat(ctx context.Context, req *connect.Request[v1.SendHeartbeatRequest]) (*connect.Response[v1.SendHeartbeatResponse], error) {
- return c.sendHeartbeat.CallUnary(ctx, req)
-}
-
-// NotifyVmStarted calls billaged.v1.BillingService.NotifyVmStarted.
-func (c *billingServiceClient) NotifyVmStarted(ctx context.Context, req *connect.Request[v1.NotifyVmStartedRequest]) (*connect.Response[v1.NotifyVmStartedResponse], error) {
- return c.notifyVmStarted.CallUnary(ctx, req)
-}
-
-// NotifyVmStopped calls billaged.v1.BillingService.NotifyVmStopped.
-func (c *billingServiceClient) NotifyVmStopped(ctx context.Context, req *connect.Request[v1.NotifyVmStoppedRequest]) (*connect.Response[v1.NotifyVmStoppedResponse], error) {
- return c.notifyVmStopped.CallUnary(ctx, req)
-}
-
-// NotifyPossibleGap calls billaged.v1.BillingService.NotifyPossibleGap.
-func (c *billingServiceClient) NotifyPossibleGap(ctx context.Context, req *connect.Request[v1.NotifyPossibleGapRequest]) (*connect.Response[v1.NotifyPossibleGapResponse], error) {
- return c.notifyPossibleGap.CallUnary(ctx, req)
-}
-
-// BillingServiceHandler is an implementation of the billaged.v1.BillingService service.
-type BillingServiceHandler interface {
- SendMetricsBatch(context.Context, *connect.Request[v1.SendMetricsBatchRequest]) (*connect.Response[v1.SendMetricsBatchResponse], error)
- SendHeartbeat(context.Context, *connect.Request[v1.SendHeartbeatRequest]) (*connect.Response[v1.SendHeartbeatResponse], error)
- NotifyVmStarted(context.Context, *connect.Request[v1.NotifyVmStartedRequest]) (*connect.Response[v1.NotifyVmStartedResponse], error)
- NotifyVmStopped(context.Context, *connect.Request[v1.NotifyVmStoppedRequest]) (*connect.Response[v1.NotifyVmStoppedResponse], error)
- NotifyPossibleGap(context.Context, *connect.Request[v1.NotifyPossibleGapRequest]) (*connect.Response[v1.NotifyPossibleGapResponse], error)
-}
-
-// NewBillingServiceHandler builds an HTTP handler from the service implementation. It returns the
-// path on which to mount the handler and the handler itself.
-//
-// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
-// and JSON codecs. They also support gzip compression.
-func NewBillingServiceHandler(svc BillingServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
- billingServiceMethods := v1.File_billaged_v1_billing_proto.Services().ByName("BillingService").Methods()
- billingServiceSendMetricsBatchHandler := connect.NewUnaryHandler(
- BillingServiceSendMetricsBatchProcedure,
- svc.SendMetricsBatch,
- connect.WithSchema(billingServiceMethods.ByName("SendMetricsBatch")),
- connect.WithHandlerOptions(opts...),
- )
- billingServiceSendHeartbeatHandler := connect.NewUnaryHandler(
- BillingServiceSendHeartbeatProcedure,
- svc.SendHeartbeat,
- connect.WithSchema(billingServiceMethods.ByName("SendHeartbeat")),
- connect.WithHandlerOptions(opts...),
- )
- billingServiceNotifyVmStartedHandler := connect.NewUnaryHandler(
- BillingServiceNotifyVmStartedProcedure,
- svc.NotifyVmStarted,
- connect.WithSchema(billingServiceMethods.ByName("NotifyVmStarted")),
- connect.WithHandlerOptions(opts...),
- )
- billingServiceNotifyVmStoppedHandler := connect.NewUnaryHandler(
- BillingServiceNotifyVmStoppedProcedure,
- svc.NotifyVmStopped,
- connect.WithSchema(billingServiceMethods.ByName("NotifyVmStopped")),
- connect.WithHandlerOptions(opts...),
- )
- billingServiceNotifyPossibleGapHandler := connect.NewUnaryHandler(
- BillingServiceNotifyPossibleGapProcedure,
- svc.NotifyPossibleGap,
- connect.WithSchema(billingServiceMethods.ByName("NotifyPossibleGap")),
- connect.WithHandlerOptions(opts...),
- )
- return "/billaged.v1.BillingService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- switch r.URL.Path {
- case BillingServiceSendMetricsBatchProcedure:
- billingServiceSendMetricsBatchHandler.ServeHTTP(w, r)
- case BillingServiceSendHeartbeatProcedure:
- billingServiceSendHeartbeatHandler.ServeHTTP(w, r)
- case BillingServiceNotifyVmStartedProcedure:
- billingServiceNotifyVmStartedHandler.ServeHTTP(w, r)
- case BillingServiceNotifyVmStoppedProcedure:
- billingServiceNotifyVmStoppedHandler.ServeHTTP(w, r)
- case BillingServiceNotifyPossibleGapProcedure:
- billingServiceNotifyPossibleGapHandler.ServeHTTP(w, r)
- default:
- http.NotFound(w, r)
- }
- })
-}
-
-// UnimplementedBillingServiceHandler returns CodeUnimplemented from all methods.
-type UnimplementedBillingServiceHandler struct{}
-
-func (UnimplementedBillingServiceHandler) SendMetricsBatch(context.Context, *connect.Request[v1.SendMetricsBatchRequest]) (*connect.Response[v1.SendMetricsBatchResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("billaged.v1.BillingService.SendMetricsBatch is not implemented"))
-}
-
-func (UnimplementedBillingServiceHandler) SendHeartbeat(context.Context, *connect.Request[v1.SendHeartbeatRequest]) (*connect.Response[v1.SendHeartbeatResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("billaged.v1.BillingService.SendHeartbeat is not implemented"))
-}
-
-func (UnimplementedBillingServiceHandler) NotifyVmStarted(context.Context, *connect.Request[v1.NotifyVmStartedRequest]) (*connect.Response[v1.NotifyVmStartedResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("billaged.v1.BillingService.NotifyVmStarted is not implemented"))
-}
-
-func (UnimplementedBillingServiceHandler) NotifyVmStopped(context.Context, *connect.Request[v1.NotifyVmStoppedRequest]) (*connect.Response[v1.NotifyVmStoppedResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("billaged.v1.BillingService.NotifyVmStopped is not implemented"))
-}
-
-func (UnimplementedBillingServiceHandler) NotifyPossibleGap(context.Context, *connect.Request[v1.NotifyPossibleGapRequest]) (*connect.Response[v1.NotifyPossibleGapResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("billaged.v1.BillingService.NotifyPossibleGap is not implemented"))
-}
diff --git a/go/gen/proto/billaged/v1/billing.pb.go b/go/gen/proto/billaged/v1/billing.pb.go
deleted file mode 100644
index 9bb6d24e2d..0000000000
--- a/go/gen/proto/billaged/v1/billing.pb.go
+++ /dev/null
@@ -1,753 +0,0 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// protoc-gen-go v1.36.8
-// protoc (unknown)
-// source: billaged/v1/billing.proto
-
-package billagedv1
-
-import (
- protoreflect "google.golang.org/protobuf/reflect/protoreflect"
- protoimpl "google.golang.org/protobuf/runtime/protoimpl"
- timestamppb "google.golang.org/protobuf/types/known/timestamppb"
- reflect "reflect"
- sync "sync"
- unsafe "unsafe"
-)
-
-const (
- // Verify that this generated code is sufficiently up-to-date.
- _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
- // Verify that runtime/protoimpl is sufficiently up-to-date.
- _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
-)
-
-type VMMetrics struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Timestamp *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
- CpuTimeNanos int64 `protobuf:"varint,2,opt,name=cpu_time_nanos,json=cpuTimeNanos,proto3" json:"cpu_time_nanos,omitempty"`
- MemoryUsageBytes int64 `protobuf:"varint,3,opt,name=memory_usage_bytes,json=memoryUsageBytes,proto3" json:"memory_usage_bytes,omitempty"`
- DiskReadBytes int64 `protobuf:"varint,4,opt,name=disk_read_bytes,json=diskReadBytes,proto3" json:"disk_read_bytes,omitempty"`
- DiskWriteBytes int64 `protobuf:"varint,5,opt,name=disk_write_bytes,json=diskWriteBytes,proto3" json:"disk_write_bytes,omitempty"`
- NetworkRxBytes int64 `protobuf:"varint,6,opt,name=network_rx_bytes,json=networkRxBytes,proto3" json:"network_rx_bytes,omitempty"`
- NetworkTxBytes int64 `protobuf:"varint,7,opt,name=network_tx_bytes,json=networkTxBytes,proto3" json:"network_tx_bytes,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *VMMetrics) Reset() {
- *x = VMMetrics{}
- mi := &file_billaged_v1_billing_proto_msgTypes[0]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *VMMetrics) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*VMMetrics) ProtoMessage() {}
-
-func (x *VMMetrics) ProtoReflect() protoreflect.Message {
- mi := &file_billaged_v1_billing_proto_msgTypes[0]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use VMMetrics.ProtoReflect.Descriptor instead.
-func (*VMMetrics) Descriptor() ([]byte, []int) {
- return file_billaged_v1_billing_proto_rawDescGZIP(), []int{0}
-}
-
-func (x *VMMetrics) GetTimestamp() *timestamppb.Timestamp {
- if x != nil {
- return x.Timestamp
- }
- return nil
-}
-
-func (x *VMMetrics) GetCpuTimeNanos() int64 {
- if x != nil {
- return x.CpuTimeNanos
- }
- return 0
-}
-
-func (x *VMMetrics) GetMemoryUsageBytes() int64 {
- if x != nil {
- return x.MemoryUsageBytes
- }
- return 0
-}
-
-func (x *VMMetrics) GetDiskReadBytes() int64 {
- if x != nil {
- return x.DiskReadBytes
- }
- return 0
-}
-
-func (x *VMMetrics) GetDiskWriteBytes() int64 {
- if x != nil {
- return x.DiskWriteBytes
- }
- return 0
-}
-
-func (x *VMMetrics) GetNetworkRxBytes() int64 {
- if x != nil {
- return x.NetworkRxBytes
- }
- return 0
-}
-
-func (x *VMMetrics) GetNetworkTxBytes() int64 {
- if x != nil {
- return x.NetworkTxBytes
- }
- return 0
-}
-
-type SendMetricsBatchRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- VmId string `protobuf:"bytes,1,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"`
- CustomerId string `protobuf:"bytes,2,opt,name=customer_id,json=customerId,proto3" json:"customer_id,omitempty"`
- Metrics []*VMMetrics `protobuf:"bytes,3,rep,name=metrics,proto3" json:"metrics,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *SendMetricsBatchRequest) Reset() {
- *x = SendMetricsBatchRequest{}
- mi := &file_billaged_v1_billing_proto_msgTypes[1]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *SendMetricsBatchRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*SendMetricsBatchRequest) ProtoMessage() {}
-
-func (x *SendMetricsBatchRequest) ProtoReflect() protoreflect.Message {
- mi := &file_billaged_v1_billing_proto_msgTypes[1]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use SendMetricsBatchRequest.ProtoReflect.Descriptor instead.
-func (*SendMetricsBatchRequest) Descriptor() ([]byte, []int) {
- return file_billaged_v1_billing_proto_rawDescGZIP(), []int{1}
-}
-
-func (x *SendMetricsBatchRequest) GetVmId() string {
- if x != nil {
- return x.VmId
- }
- return ""
-}
-
-func (x *SendMetricsBatchRequest) GetCustomerId() string {
- if x != nil {
- return x.CustomerId
- }
- return ""
-}
-
-func (x *SendMetricsBatchRequest) GetMetrics() []*VMMetrics {
- if x != nil {
- return x.Metrics
- }
- return nil
-}
-
-type SendMetricsBatchResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
- Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *SendMetricsBatchResponse) Reset() {
- *x = SendMetricsBatchResponse{}
- mi := &file_billaged_v1_billing_proto_msgTypes[2]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *SendMetricsBatchResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*SendMetricsBatchResponse) ProtoMessage() {}
-
-func (x *SendMetricsBatchResponse) ProtoReflect() protoreflect.Message {
- mi := &file_billaged_v1_billing_proto_msgTypes[2]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use SendMetricsBatchResponse.ProtoReflect.Descriptor instead.
-func (*SendMetricsBatchResponse) Descriptor() ([]byte, []int) {
- return file_billaged_v1_billing_proto_rawDescGZIP(), []int{2}
-}
-
-func (x *SendMetricsBatchResponse) GetSuccess() bool {
- if x != nil {
- return x.Success
- }
- return false
-}
-
-func (x *SendMetricsBatchResponse) GetMessage() string {
- if x != nil {
- return x.Message
- }
- return ""
-}
-
-type SendHeartbeatRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- InstanceId string `protobuf:"bytes,1,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"`
- ActiveVms []string `protobuf:"bytes,2,rep,name=active_vms,json=activeVms,proto3" json:"active_vms,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *SendHeartbeatRequest) Reset() {
- *x = SendHeartbeatRequest{}
- mi := &file_billaged_v1_billing_proto_msgTypes[3]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *SendHeartbeatRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*SendHeartbeatRequest) ProtoMessage() {}
-
-func (x *SendHeartbeatRequest) ProtoReflect() protoreflect.Message {
- mi := &file_billaged_v1_billing_proto_msgTypes[3]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use SendHeartbeatRequest.ProtoReflect.Descriptor instead.
-func (*SendHeartbeatRequest) Descriptor() ([]byte, []int) {
- return file_billaged_v1_billing_proto_rawDescGZIP(), []int{3}
-}
-
-func (x *SendHeartbeatRequest) GetInstanceId() string {
- if x != nil {
- return x.InstanceId
- }
- return ""
-}
-
-func (x *SendHeartbeatRequest) GetActiveVms() []string {
- if x != nil {
- return x.ActiveVms
- }
- return nil
-}
-
-type SendHeartbeatResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *SendHeartbeatResponse) Reset() {
- *x = SendHeartbeatResponse{}
- mi := &file_billaged_v1_billing_proto_msgTypes[4]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *SendHeartbeatResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*SendHeartbeatResponse) ProtoMessage() {}
-
-func (x *SendHeartbeatResponse) ProtoReflect() protoreflect.Message {
- mi := &file_billaged_v1_billing_proto_msgTypes[4]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use SendHeartbeatResponse.ProtoReflect.Descriptor instead.
-func (*SendHeartbeatResponse) Descriptor() ([]byte, []int) {
- return file_billaged_v1_billing_proto_rawDescGZIP(), []int{4}
-}
-
-func (x *SendHeartbeatResponse) GetSuccess() bool {
- if x != nil {
- return x.Success
- }
- return false
-}
-
-type NotifyVmStartedRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- VmId string `protobuf:"bytes,1,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"`
- CustomerId string `protobuf:"bytes,2,opt,name=customer_id,json=customerId,proto3" json:"customer_id,omitempty"`
- StartTime int64 `protobuf:"varint,3,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *NotifyVmStartedRequest) Reset() {
- *x = NotifyVmStartedRequest{}
- mi := &file_billaged_v1_billing_proto_msgTypes[5]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *NotifyVmStartedRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*NotifyVmStartedRequest) ProtoMessage() {}
-
-func (x *NotifyVmStartedRequest) ProtoReflect() protoreflect.Message {
- mi := &file_billaged_v1_billing_proto_msgTypes[5]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use NotifyVmStartedRequest.ProtoReflect.Descriptor instead.
-func (*NotifyVmStartedRequest) Descriptor() ([]byte, []int) {
- return file_billaged_v1_billing_proto_rawDescGZIP(), []int{5}
-}
-
-func (x *NotifyVmStartedRequest) GetVmId() string {
- if x != nil {
- return x.VmId
- }
- return ""
-}
-
-func (x *NotifyVmStartedRequest) GetCustomerId() string {
- if x != nil {
- return x.CustomerId
- }
- return ""
-}
-
-func (x *NotifyVmStartedRequest) GetStartTime() int64 {
- if x != nil {
- return x.StartTime
- }
- return 0
-}
-
-type NotifyVmStartedResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *NotifyVmStartedResponse) Reset() {
- *x = NotifyVmStartedResponse{}
- mi := &file_billaged_v1_billing_proto_msgTypes[6]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *NotifyVmStartedResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*NotifyVmStartedResponse) ProtoMessage() {}
-
-func (x *NotifyVmStartedResponse) ProtoReflect() protoreflect.Message {
- mi := &file_billaged_v1_billing_proto_msgTypes[6]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use NotifyVmStartedResponse.ProtoReflect.Descriptor instead.
-func (*NotifyVmStartedResponse) Descriptor() ([]byte, []int) {
- return file_billaged_v1_billing_proto_rawDescGZIP(), []int{6}
-}
-
-func (x *NotifyVmStartedResponse) GetSuccess() bool {
- if x != nil {
- return x.Success
- }
- return false
-}
-
-type NotifyVmStoppedRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- VmId string `protobuf:"bytes,1,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"`
- StopTime int64 `protobuf:"varint,2,opt,name=stop_time,json=stopTime,proto3" json:"stop_time,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *NotifyVmStoppedRequest) Reset() {
- *x = NotifyVmStoppedRequest{}
- mi := &file_billaged_v1_billing_proto_msgTypes[7]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *NotifyVmStoppedRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*NotifyVmStoppedRequest) ProtoMessage() {}
-
-func (x *NotifyVmStoppedRequest) ProtoReflect() protoreflect.Message {
- mi := &file_billaged_v1_billing_proto_msgTypes[7]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use NotifyVmStoppedRequest.ProtoReflect.Descriptor instead.
-func (*NotifyVmStoppedRequest) Descriptor() ([]byte, []int) {
- return file_billaged_v1_billing_proto_rawDescGZIP(), []int{7}
-}
-
-func (x *NotifyVmStoppedRequest) GetVmId() string {
- if x != nil {
- return x.VmId
- }
- return ""
-}
-
-func (x *NotifyVmStoppedRequest) GetStopTime() int64 {
- if x != nil {
- return x.StopTime
- }
- return 0
-}
-
-type NotifyVmStoppedResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *NotifyVmStoppedResponse) Reset() {
- *x = NotifyVmStoppedResponse{}
- mi := &file_billaged_v1_billing_proto_msgTypes[8]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *NotifyVmStoppedResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*NotifyVmStoppedResponse) ProtoMessage() {}
-
-func (x *NotifyVmStoppedResponse) ProtoReflect() protoreflect.Message {
- mi := &file_billaged_v1_billing_proto_msgTypes[8]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use NotifyVmStoppedResponse.ProtoReflect.Descriptor instead.
-func (*NotifyVmStoppedResponse) Descriptor() ([]byte, []int) {
- return file_billaged_v1_billing_proto_rawDescGZIP(), []int{8}
-}
-
-func (x *NotifyVmStoppedResponse) GetSuccess() bool {
- if x != nil {
- return x.Success
- }
- return false
-}
-
-type NotifyPossibleGapRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- VmId string `protobuf:"bytes,1,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"`
- LastSent int64 `protobuf:"varint,2,opt,name=last_sent,json=lastSent,proto3" json:"last_sent,omitempty"`
- ResumeTime int64 `protobuf:"varint,3,opt,name=resume_time,json=resumeTime,proto3" json:"resume_time,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *NotifyPossibleGapRequest) Reset() {
- *x = NotifyPossibleGapRequest{}
- mi := &file_billaged_v1_billing_proto_msgTypes[9]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *NotifyPossibleGapRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*NotifyPossibleGapRequest) ProtoMessage() {}
-
-func (x *NotifyPossibleGapRequest) ProtoReflect() protoreflect.Message {
- mi := &file_billaged_v1_billing_proto_msgTypes[9]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use NotifyPossibleGapRequest.ProtoReflect.Descriptor instead.
-func (*NotifyPossibleGapRequest) Descriptor() ([]byte, []int) {
- return file_billaged_v1_billing_proto_rawDescGZIP(), []int{9}
-}
-
-func (x *NotifyPossibleGapRequest) GetVmId() string {
- if x != nil {
- return x.VmId
- }
- return ""
-}
-
-func (x *NotifyPossibleGapRequest) GetLastSent() int64 {
- if x != nil {
- return x.LastSent
- }
- return 0
-}
-
-func (x *NotifyPossibleGapRequest) GetResumeTime() int64 {
- if x != nil {
- return x.ResumeTime
- }
- return 0
-}
-
-type NotifyPossibleGapResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *NotifyPossibleGapResponse) Reset() {
- *x = NotifyPossibleGapResponse{}
- mi := &file_billaged_v1_billing_proto_msgTypes[10]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *NotifyPossibleGapResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*NotifyPossibleGapResponse) ProtoMessage() {}
-
-func (x *NotifyPossibleGapResponse) ProtoReflect() protoreflect.Message {
- mi := &file_billaged_v1_billing_proto_msgTypes[10]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use NotifyPossibleGapResponse.ProtoReflect.Descriptor instead.
-func (*NotifyPossibleGapResponse) Descriptor() ([]byte, []int) {
- return file_billaged_v1_billing_proto_rawDescGZIP(), []int{10}
-}
-
-func (x *NotifyPossibleGapResponse) GetSuccess() bool {
- if x != nil {
- return x.Success
- }
- return false
-}
-
-var File_billaged_v1_billing_proto protoreflect.FileDescriptor
-
-const file_billaged_v1_billing_proto_rawDesc = "" +
- "\n" +
- "\x19billaged/v1/billing.proto\x12\vbillaged.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\xbf\x02\n" +
- "\tVMMetrics\x128\n" +
- "\ttimestamp\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\x12$\n" +
- "\x0ecpu_time_nanos\x18\x02 \x01(\x03R\fcpuTimeNanos\x12,\n" +
- "\x12memory_usage_bytes\x18\x03 \x01(\x03R\x10memoryUsageBytes\x12&\n" +
- "\x0fdisk_read_bytes\x18\x04 \x01(\x03R\rdiskReadBytes\x12(\n" +
- "\x10disk_write_bytes\x18\x05 \x01(\x03R\x0ediskWriteBytes\x12(\n" +
- "\x10network_rx_bytes\x18\x06 \x01(\x03R\x0enetworkRxBytes\x12(\n" +
- "\x10network_tx_bytes\x18\a \x01(\x03R\x0enetworkTxBytes\"\x81\x01\n" +
- "\x17SendMetricsBatchRequest\x12\x13\n" +
- "\x05vm_id\x18\x01 \x01(\tR\x04vmId\x12\x1f\n" +
- "\vcustomer_id\x18\x02 \x01(\tR\n" +
- "customerId\x120\n" +
- "\ametrics\x18\x03 \x03(\v2\x16.billaged.v1.VMMetricsR\ametrics\"N\n" +
- "\x18SendMetricsBatchResponse\x12\x18\n" +
- "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" +
- "\amessage\x18\x02 \x01(\tR\amessage\"V\n" +
- "\x14SendHeartbeatRequest\x12\x1f\n" +
- "\vinstance_id\x18\x01 \x01(\tR\n" +
- "instanceId\x12\x1d\n" +
- "\n" +
- "active_vms\x18\x02 \x03(\tR\tactiveVms\"1\n" +
- "\x15SendHeartbeatResponse\x12\x18\n" +
- "\asuccess\x18\x01 \x01(\bR\asuccess\"m\n" +
- "\x16NotifyVmStartedRequest\x12\x13\n" +
- "\x05vm_id\x18\x01 \x01(\tR\x04vmId\x12\x1f\n" +
- "\vcustomer_id\x18\x02 \x01(\tR\n" +
- "customerId\x12\x1d\n" +
- "\n" +
- "start_time\x18\x03 \x01(\x03R\tstartTime\"3\n" +
- "\x17NotifyVmStartedResponse\x12\x18\n" +
- "\asuccess\x18\x01 \x01(\bR\asuccess\"J\n" +
- "\x16NotifyVmStoppedRequest\x12\x13\n" +
- "\x05vm_id\x18\x01 \x01(\tR\x04vmId\x12\x1b\n" +
- "\tstop_time\x18\x02 \x01(\x03R\bstopTime\"3\n" +
- "\x17NotifyVmStoppedResponse\x12\x18\n" +
- "\asuccess\x18\x01 \x01(\bR\asuccess\"m\n" +
- "\x18NotifyPossibleGapRequest\x12\x13\n" +
- "\x05vm_id\x18\x01 \x01(\tR\x04vmId\x12\x1b\n" +
- "\tlast_sent\x18\x02 \x01(\x03R\blastSent\x12\x1f\n" +
- "\vresume_time\x18\x03 \x01(\x03R\n" +
- "resumeTime\"5\n" +
- "\x19NotifyPossibleGapResponse\x12\x18\n" +
- "\asuccess\x18\x01 \x01(\bR\asuccess2\xe9\x03\n" +
- "\x0eBillingService\x12_\n" +
- "\x10SendMetricsBatch\x12$.billaged.v1.SendMetricsBatchRequest\x1a%.billaged.v1.SendMetricsBatchResponse\x12V\n" +
- "\rSendHeartbeat\x12!.billaged.v1.SendHeartbeatRequest\x1a\".billaged.v1.SendHeartbeatResponse\x12\\\n" +
- "\x0fNotifyVmStarted\x12#.billaged.v1.NotifyVmStartedRequest\x1a$.billaged.v1.NotifyVmStartedResponse\x12\\\n" +
- "\x0fNotifyVmStopped\x12#.billaged.v1.NotifyVmStoppedRequest\x1a$.billaged.v1.NotifyVmStoppedResponse\x12b\n" +
- "\x11NotifyPossibleGap\x12%.billaged.v1.NotifyPossibleGapRequest\x1a&.billaged.v1.NotifyPossibleGapResponseB>Z google.protobuf.Timestamp
- 0, // 1: billaged.v1.SendMetricsBatchRequest.metrics:type_name -> billaged.v1.VMMetrics
- 1, // 2: billaged.v1.BillingService.SendMetricsBatch:input_type -> billaged.v1.SendMetricsBatchRequest
- 3, // 3: billaged.v1.BillingService.SendHeartbeat:input_type -> billaged.v1.SendHeartbeatRequest
- 5, // 4: billaged.v1.BillingService.NotifyVmStarted:input_type -> billaged.v1.NotifyVmStartedRequest
- 7, // 5: billaged.v1.BillingService.NotifyVmStopped:input_type -> billaged.v1.NotifyVmStoppedRequest
- 9, // 6: billaged.v1.BillingService.NotifyPossibleGap:input_type -> billaged.v1.NotifyPossibleGapRequest
- 2, // 7: billaged.v1.BillingService.SendMetricsBatch:output_type -> billaged.v1.SendMetricsBatchResponse
- 4, // 8: billaged.v1.BillingService.SendHeartbeat:output_type -> billaged.v1.SendHeartbeatResponse
- 6, // 9: billaged.v1.BillingService.NotifyVmStarted:output_type -> billaged.v1.NotifyVmStartedResponse
- 8, // 10: billaged.v1.BillingService.NotifyVmStopped:output_type -> billaged.v1.NotifyVmStoppedResponse
- 10, // 11: billaged.v1.BillingService.NotifyPossibleGap:output_type -> billaged.v1.NotifyPossibleGapResponse
- 7, // [7:12] is the sub-list for method output_type
- 2, // [2:7] is the sub-list for method input_type
- 2, // [2:2] is the sub-list for extension type_name
- 2, // [2:2] is the sub-list for extension extendee
- 0, // [0:2] is the sub-list for field type_name
-}
-
-func init() { file_billaged_v1_billing_proto_init() }
-func file_billaged_v1_billing_proto_init() {
- if File_billaged_v1_billing_proto != nil {
- return
- }
- type x struct{}
- out := protoimpl.TypeBuilder{
- File: protoimpl.DescBuilder{
- GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
- RawDescriptor: unsafe.Slice(unsafe.StringData(file_billaged_v1_billing_proto_rawDesc), len(file_billaged_v1_billing_proto_rawDesc)),
- NumEnums: 0,
- NumMessages: 11,
- NumExtensions: 0,
- NumServices: 1,
- },
- GoTypes: file_billaged_v1_billing_proto_goTypes,
- DependencyIndexes: file_billaged_v1_billing_proto_depIdxs,
- MessageInfos: file_billaged_v1_billing_proto_msgTypes,
- }.Build()
- File_billaged_v1_billing_proto = out.File
- file_billaged_v1_billing_proto_goTypes = nil
- file_billaged_v1_billing_proto_depIdxs = nil
-}
diff --git a/go/gen/proto/builderd/v1/builder.pb.go b/go/gen/proto/builderd/v1/builder.pb.go
deleted file mode 100644
index 0642fd1878..0000000000
--- a/go/gen/proto/builderd/v1/builder.pb.go
+++ /dev/null
@@ -1,3202 +0,0 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// protoc-gen-go v1.36.8
-// protoc (unknown)
-// source: builderd/v1/builder.proto
-
-package builderdv1
-
-import (
- protoreflect "google.golang.org/protobuf/reflect/protoreflect"
- protoimpl "google.golang.org/protobuf/runtime/protoimpl"
- timestamppb "google.golang.org/protobuf/types/known/timestamppb"
- reflect "reflect"
- sync "sync"
- unsafe "unsafe"
-)
-
-const (
- // Verify that this generated code is sufficiently up-to-date.
- _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
- // Verify that runtime/protoimpl is sufficiently up-to-date.
- _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
-)
-
-// Build job lifecycle states
-type BuildState int32
-
-const (
- BuildState_BUILD_STATE_UNSPECIFIED BuildState = 0
- BuildState_BUILD_STATE_PENDING BuildState = 1 // Job queued
- BuildState_BUILD_STATE_PULLING BuildState = 2 // Pulling Docker image or source
- BuildState_BUILD_STATE_EXTRACTING BuildState = 3 // Extracting/preparing source
- BuildState_BUILD_STATE_BUILDING BuildState = 4 // Building rootfs
- BuildState_BUILD_STATE_OPTIMIZING BuildState = 5 // Applying optimizations
- BuildState_BUILD_STATE_COMPLETED BuildState = 6 // Build successful
- BuildState_BUILD_STATE_FAILED BuildState = 7 // Build failed
- BuildState_BUILD_STATE_CANCELLED BuildState = 8 // Build cancelled
- BuildState_BUILD_STATE_CLEANING BuildState = 9 // Cleaning up resources
-)
-
-// Enum value maps for BuildState.
-var (
- BuildState_name = map[int32]string{
- 0: "BUILD_STATE_UNSPECIFIED",
- 1: "BUILD_STATE_PENDING",
- 2: "BUILD_STATE_PULLING",
- 3: "BUILD_STATE_EXTRACTING",
- 4: "BUILD_STATE_BUILDING",
- 5: "BUILD_STATE_OPTIMIZING",
- 6: "BUILD_STATE_COMPLETED",
- 7: "BUILD_STATE_FAILED",
- 8: "BUILD_STATE_CANCELLED",
- 9: "BUILD_STATE_CLEANING",
- }
- BuildState_value = map[string]int32{
- "BUILD_STATE_UNSPECIFIED": 0,
- "BUILD_STATE_PENDING": 1,
- "BUILD_STATE_PULLING": 2,
- "BUILD_STATE_EXTRACTING": 3,
- "BUILD_STATE_BUILDING": 4,
- "BUILD_STATE_OPTIMIZING": 5,
- "BUILD_STATE_COMPLETED": 6,
- "BUILD_STATE_FAILED": 7,
- "BUILD_STATE_CANCELLED": 8,
- "BUILD_STATE_CLEANING": 9,
- }
-)
-
-func (x BuildState) Enum() *BuildState {
- p := new(BuildState)
- *p = x
- return p
-}
-
-func (x BuildState) String() string {
- return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
-}
-
-func (BuildState) Descriptor() protoreflect.EnumDescriptor {
- return file_builderd_v1_builder_proto_enumTypes[0].Descriptor()
-}
-
-func (BuildState) Type() protoreflect.EnumType {
- return &file_builderd_v1_builder_proto_enumTypes[0]
-}
-
-func (x BuildState) Number() protoreflect.EnumNumber {
- return protoreflect.EnumNumber(x)
-}
-
-// Deprecated: Use BuildState.Descriptor instead.
-func (BuildState) EnumDescriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{0}
-}
-
-// Tenant service tiers
-type TenantTier int32
-
-const (
- TenantTier_TENANT_TIER_UNSPECIFIED TenantTier = 0
- TenantTier_TENANT_TIER_FREE TenantTier = 1 // Limited resources
- TenantTier_TENANT_TIER_PRO TenantTier = 2 // Standard resources
- TenantTier_TENANT_TIER_ENTERPRISE TenantTier = 3 // Higher limits + isolation
- TenantTier_TENANT_TIER_DEDICATED TenantTier = 4 // Dedicated infrastructure
-)
-
-// Enum value maps for TenantTier.
-var (
- TenantTier_name = map[int32]string{
- 0: "TENANT_TIER_UNSPECIFIED",
- 1: "TENANT_TIER_FREE",
- 2: "TENANT_TIER_PRO",
- 3: "TENANT_TIER_ENTERPRISE",
- 4: "TENANT_TIER_DEDICATED",
- }
- TenantTier_value = map[string]int32{
- "TENANT_TIER_UNSPECIFIED": 0,
- "TENANT_TIER_FREE": 1,
- "TENANT_TIER_PRO": 2,
- "TENANT_TIER_ENTERPRISE": 3,
- "TENANT_TIER_DEDICATED": 4,
- }
-)
-
-func (x TenantTier) Enum() *TenantTier {
- p := new(TenantTier)
- *p = x
- return p
-}
-
-func (x TenantTier) String() string {
- return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
-}
-
-func (TenantTier) Descriptor() protoreflect.EnumDescriptor {
- return file_builderd_v1_builder_proto_enumTypes[1].Descriptor()
-}
-
-func (TenantTier) Type() protoreflect.EnumType {
- return &file_builderd_v1_builder_proto_enumTypes[1]
-}
-
-func (x TenantTier) Number() protoreflect.EnumNumber {
- return protoreflect.EnumNumber(x)
-}
-
-// Deprecated: Use TenantTier.Descriptor instead.
-func (TenantTier) EnumDescriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{1}
-}
-
-// Init process strategies for microVMs
-type InitStrategy int32
-
-const (
- InitStrategy_INIT_STRATEGY_UNSPECIFIED InitStrategy = 0
- InitStrategy_INIT_STRATEGY_TINI InitStrategy = 1 // Use tini as init (recommended)
- InitStrategy_INIT_STRATEGY_DIRECT InitStrategy = 2 // Direct exec (risky)
- InitStrategy_INIT_STRATEGY_CUSTOM InitStrategy = 3 // Custom init script
-)
-
-// Enum value maps for InitStrategy.
-var (
- InitStrategy_name = map[int32]string{
- 0: "INIT_STRATEGY_UNSPECIFIED",
- 1: "INIT_STRATEGY_TINI",
- 2: "INIT_STRATEGY_DIRECT",
- 3: "INIT_STRATEGY_CUSTOM",
- }
- InitStrategy_value = map[string]int32{
- "INIT_STRATEGY_UNSPECIFIED": 0,
- "INIT_STRATEGY_TINI": 1,
- "INIT_STRATEGY_DIRECT": 2,
- "INIT_STRATEGY_CUSTOM": 3,
- }
-)
-
-func (x InitStrategy) Enum() *InitStrategy {
- p := new(InitStrategy)
- *p = x
- return p
-}
-
-func (x InitStrategy) String() string {
- return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
-}
-
-func (InitStrategy) Descriptor() protoreflect.EnumDescriptor {
- return file_builderd_v1_builder_proto_enumTypes[2].Descriptor()
-}
-
-func (InitStrategy) Type() protoreflect.EnumType {
- return &file_builderd_v1_builder_proto_enumTypes[2]
-}
-
-func (x InitStrategy) Number() protoreflect.EnumNumber {
- return protoreflect.EnumNumber(x)
-}
-
-// Deprecated: Use InitStrategy.Descriptor instead.
-func (InitStrategy) EnumDescriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{2}
-}
-
-// Build source types - extensible for future build types
-type BuildSource struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // Types that are valid to be assigned to SourceType:
- //
- // *BuildSource_DockerImage
- // *BuildSource_GitRepository
- // *BuildSource_Archive
- SourceType isBuildSource_SourceType `protobuf_oneof:"source_type"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *BuildSource) Reset() {
- *x = BuildSource{}
- mi := &file_builderd_v1_builder_proto_msgTypes[0]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *BuildSource) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*BuildSource) ProtoMessage() {}
-
-func (x *BuildSource) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[0]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use BuildSource.ProtoReflect.Descriptor instead.
-func (*BuildSource) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{0}
-}
-
-func (x *BuildSource) GetSourceType() isBuildSource_SourceType {
- if x != nil {
- return x.SourceType
- }
- return nil
-}
-
-func (x *BuildSource) GetDockerImage() *DockerImageSource {
- if x != nil {
- if x, ok := x.SourceType.(*BuildSource_DockerImage); ok {
- return x.DockerImage
- }
- }
- return nil
-}
-
-func (x *BuildSource) GetGitRepository() *GitRepositorySource {
- if x != nil {
- if x, ok := x.SourceType.(*BuildSource_GitRepository); ok {
- return x.GitRepository
- }
- }
- return nil
-}
-
-func (x *BuildSource) GetArchive() *ArchiveSource {
- if x != nil {
- if x, ok := x.SourceType.(*BuildSource_Archive); ok {
- return x.Archive
- }
- }
- return nil
-}
-
-type isBuildSource_SourceType interface {
- isBuildSource_SourceType()
-}
-
-type BuildSource_DockerImage struct {
- DockerImage *DockerImageSource `protobuf:"bytes,1,opt,name=docker_image,json=dockerImage,proto3,oneof"`
-}
-
-type BuildSource_GitRepository struct {
- GitRepository *GitRepositorySource `protobuf:"bytes,2,opt,name=git_repository,json=gitRepository,proto3,oneof"`
-}
-
-type BuildSource_Archive struct {
- Archive *ArchiveSource `protobuf:"bytes,3,opt,name=archive,proto3,oneof"` // Future: nix_flake = 4, buildpack = 5, etc.
-}
-
-func (*BuildSource_DockerImage) isBuildSource_SourceType() {}
-
-func (*BuildSource_GitRepository) isBuildSource_SourceType() {}
-
-func (*BuildSource_Archive) isBuildSource_SourceType() {}
-
-// Docker image extraction (first implementation)
-type DockerImageSource struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- ImageUri string `protobuf:"bytes,1,opt,name=image_uri,json=imageUri,proto3" json:"image_uri,omitempty"` // "ghcr.io/unkeyed/unkey:f4cfee5"
- Auth *DockerAuth `protobuf:"bytes,2,opt,name=auth,proto3" json:"auth,omitempty"` // Registry authentication
- PullTags []string `protobuf:"bytes,3,rep,name=pull_tags,json=pullTags,proto3" json:"pull_tags,omitempty"` // Additional tags to consider
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *DockerImageSource) Reset() {
- *x = DockerImageSource{}
- mi := &file_builderd_v1_builder_proto_msgTypes[1]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *DockerImageSource) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*DockerImageSource) ProtoMessage() {}
-
-func (x *DockerImageSource) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[1]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use DockerImageSource.ProtoReflect.Descriptor instead.
-func (*DockerImageSource) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{1}
-}
-
-func (x *DockerImageSource) GetImageUri() string {
- if x != nil {
- return x.ImageUri
- }
- return ""
-}
-
-func (x *DockerImageSource) GetAuth() *DockerAuth {
- if x != nil {
- return x.Auth
- }
- return nil
-}
-
-func (x *DockerImageSource) GetPullTags() []string {
- if x != nil {
- return x.PullTags
- }
- return nil
-}
-
-type DockerAuth struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
- Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
- Token string `protobuf:"bytes,3,opt,name=token,proto3" json:"token,omitempty"`
- Registry string `protobuf:"bytes,4,opt,name=registry,proto3" json:"registry,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *DockerAuth) Reset() {
- *x = DockerAuth{}
- mi := &file_builderd_v1_builder_proto_msgTypes[2]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *DockerAuth) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*DockerAuth) ProtoMessage() {}
-
-func (x *DockerAuth) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[2]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use DockerAuth.ProtoReflect.Descriptor instead.
-func (*DockerAuth) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{2}
-}
-
-func (x *DockerAuth) GetUsername() string {
- if x != nil {
- return x.Username
- }
- return ""
-}
-
-func (x *DockerAuth) GetPassword() string {
- if x != nil {
- return x.Password
- }
- return ""
-}
-
-func (x *DockerAuth) GetToken() string {
- if x != nil {
- return x.Token
- }
- return ""
-}
-
-func (x *DockerAuth) GetRegistry() string {
- if x != nil {
- return x.Registry
- }
- return ""
-}
-
-// Git repository builds (future)
-type GitRepositorySource struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- RepositoryUrl string `protobuf:"bytes,1,opt,name=repository_url,json=repositoryUrl,proto3" json:"repository_url,omitempty"` // "https://github.com/unkeyed/unkey"
- Ref string `protobuf:"bytes,2,opt,name=ref,proto3" json:"ref,omitempty"` // branch/tag/commit
- BuildContext string `protobuf:"bytes,3,opt,name=build_context,json=buildContext,proto3" json:"build_context,omitempty"` // subdirectory if needed
- Auth *GitAuth `protobuf:"bytes,4,opt,name=auth,proto3" json:"auth,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *GitRepositorySource) Reset() {
- *x = GitRepositorySource{}
- mi := &file_builderd_v1_builder_proto_msgTypes[3]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *GitRepositorySource) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*GitRepositorySource) ProtoMessage() {}
-
-func (x *GitRepositorySource) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[3]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use GitRepositorySource.ProtoReflect.Descriptor instead.
-func (*GitRepositorySource) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{3}
-}
-
-func (x *GitRepositorySource) GetRepositoryUrl() string {
- if x != nil {
- return x.RepositoryUrl
- }
- return ""
-}
-
-func (x *GitRepositorySource) GetRef() string {
- if x != nil {
- return x.Ref
- }
- return ""
-}
-
-func (x *GitRepositorySource) GetBuildContext() string {
- if x != nil {
- return x.BuildContext
- }
- return ""
-}
-
-func (x *GitRepositorySource) GetAuth() *GitAuth {
- if x != nil {
- return x.Auth
- }
- return nil
-}
-
-type GitAuth struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
- Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
- SshKey string `protobuf:"bytes,3,opt,name=ssh_key,json=sshKey,proto3" json:"ssh_key,omitempty"`
- Token string `protobuf:"bytes,4,opt,name=token,proto3" json:"token,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *GitAuth) Reset() {
- *x = GitAuth{}
- mi := &file_builderd_v1_builder_proto_msgTypes[4]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *GitAuth) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*GitAuth) ProtoMessage() {}
-
-func (x *GitAuth) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[4]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use GitAuth.ProtoReflect.Descriptor instead.
-func (*GitAuth) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{4}
-}
-
-func (x *GitAuth) GetUsername() string {
- if x != nil {
- return x.Username
- }
- return ""
-}
-
-func (x *GitAuth) GetPassword() string {
- if x != nil {
- return x.Password
- }
- return ""
-}
-
-func (x *GitAuth) GetSshKey() string {
- if x != nil {
- return x.SshKey
- }
- return ""
-}
-
-func (x *GitAuth) GetToken() string {
- if x != nil {
- return x.Token
- }
- return ""
-}
-
-// Archive builds (future)
-type ArchiveSource struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- ArchiveUrl string `protobuf:"bytes,1,opt,name=archive_url,json=archiveUrl,proto3" json:"archive_url,omitempty"` // URL to tar.gz, zip, etc.
- ArchiveType string `protobuf:"bytes,2,opt,name=archive_type,json=archiveType,proto3" json:"archive_type,omitempty"` // "tar.gz", "zip"
- BuildContext string `protobuf:"bytes,3,opt,name=build_context,json=buildContext,proto3" json:"build_context,omitempty"` // subdirectory in archive
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *ArchiveSource) Reset() {
- *x = ArchiveSource{}
- mi := &file_builderd_v1_builder_proto_msgTypes[5]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *ArchiveSource) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*ArchiveSource) ProtoMessage() {}
-
-func (x *ArchiveSource) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[5]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use ArchiveSource.ProtoReflect.Descriptor instead.
-func (*ArchiveSource) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{5}
-}
-
-func (x *ArchiveSource) GetArchiveUrl() string {
- if x != nil {
- return x.ArchiveUrl
- }
- return ""
-}
-
-func (x *ArchiveSource) GetArchiveType() string {
- if x != nil {
- return x.ArchiveType
- }
- return ""
-}
-
-func (x *ArchiveSource) GetBuildContext() string {
- if x != nil {
- return x.BuildContext
- }
- return ""
-}
-
-// Build target types - extensible
-type BuildTarget struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // Types that are valid to be assigned to TargetType:
- //
- // *BuildTarget_MicrovmRootfs
- // *BuildTarget_ContainerImage
- TargetType isBuildTarget_TargetType `protobuf_oneof:"target_type"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *BuildTarget) Reset() {
- *x = BuildTarget{}
- mi := &file_builderd_v1_builder_proto_msgTypes[6]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *BuildTarget) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*BuildTarget) ProtoMessage() {}
-
-func (x *BuildTarget) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[6]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use BuildTarget.ProtoReflect.Descriptor instead.
-func (*BuildTarget) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{6}
-}
-
-func (x *BuildTarget) GetTargetType() isBuildTarget_TargetType {
- if x != nil {
- return x.TargetType
- }
- return nil
-}
-
-func (x *BuildTarget) GetMicrovmRootfs() *MicroVMRootfs {
- if x != nil {
- if x, ok := x.TargetType.(*BuildTarget_MicrovmRootfs); ok {
- return x.MicrovmRootfs
- }
- }
- return nil
-}
-
-func (x *BuildTarget) GetContainerImage() *ContainerImage {
- if x != nil {
- if x, ok := x.TargetType.(*BuildTarget_ContainerImage); ok {
- return x.ContainerImage
- }
- }
- return nil
-}
-
-type isBuildTarget_TargetType interface {
- isBuildTarget_TargetType()
-}
-
-type BuildTarget_MicrovmRootfs struct {
- MicrovmRootfs *MicroVMRootfs `protobuf:"bytes,1,opt,name=microvm_rootfs,json=microvmRootfs,proto3,oneof"`
-}
-
-type BuildTarget_ContainerImage struct {
- ContainerImage *ContainerImage `protobuf:"bytes,2,opt,name=container_image,json=containerImage,proto3,oneof"` // Future: wasm_module = 3, lambda_layer = 4, etc.
-}
-
-func (*BuildTarget_MicrovmRootfs) isBuildTarget_TargetType() {}
-
-func (*BuildTarget_ContainerImage) isBuildTarget_TargetType() {}
-
-// MicroVM rootfs (our focus)
-type MicroVMRootfs struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- InitStrategy InitStrategy `protobuf:"varint,1,opt,name=init_strategy,json=initStrategy,proto3,enum=builderd.v1.InitStrategy" json:"init_strategy,omitempty"`
- RuntimeConfig *RuntimeConfig `protobuf:"bytes,2,opt,name=runtime_config,json=runtimeConfig,proto3" json:"runtime_config,omitempty"`
- Optimization *OptimizationSettings `protobuf:"bytes,3,opt,name=optimization,proto3" json:"optimization,omitempty"`
- PreservePaths []string `protobuf:"bytes,4,rep,name=preserve_paths,json=preservePaths,proto3" json:"preserve_paths,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *MicroVMRootfs) Reset() {
- *x = MicroVMRootfs{}
- mi := &file_builderd_v1_builder_proto_msgTypes[7]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *MicroVMRootfs) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*MicroVMRootfs) ProtoMessage() {}
-
-func (x *MicroVMRootfs) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[7]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use MicroVMRootfs.ProtoReflect.Descriptor instead.
-func (*MicroVMRootfs) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{7}
-}
-
-func (x *MicroVMRootfs) GetInitStrategy() InitStrategy {
- if x != nil {
- return x.InitStrategy
- }
- return InitStrategy_INIT_STRATEGY_UNSPECIFIED
-}
-
-func (x *MicroVMRootfs) GetRuntimeConfig() *RuntimeConfig {
- if x != nil {
- return x.RuntimeConfig
- }
- return nil
-}
-
-func (x *MicroVMRootfs) GetOptimization() *OptimizationSettings {
- if x != nil {
- return x.Optimization
- }
- return nil
-}
-
-func (x *MicroVMRootfs) GetPreservePaths() []string {
- if x != nil {
- return x.PreservePaths
- }
- return nil
-}
-
-// Container image (future)
-type ContainerImage struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- BaseImage string `protobuf:"bytes,1,opt,name=base_image,json=baseImage,proto3" json:"base_image,omitempty"`
- Layers []string `protobuf:"bytes,2,rep,name=layers,proto3" json:"layers,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *ContainerImage) Reset() {
- *x = ContainerImage{}
- mi := &file_builderd_v1_builder_proto_msgTypes[8]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *ContainerImage) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*ContainerImage) ProtoMessage() {}
-
-func (x *ContainerImage) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[8]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use ContainerImage.ProtoReflect.Descriptor instead.
-func (*ContainerImage) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{8}
-}
-
-func (x *ContainerImage) GetBaseImage() string {
- if x != nil {
- return x.BaseImage
- }
- return ""
-}
-
-func (x *ContainerImage) GetLayers() []string {
- if x != nil {
- return x.Layers
- }
- return nil
-}
-
-type RuntimeConfig struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Command []string `protobuf:"bytes,1,rep,name=command,proto3" json:"command,omitempty"` // Override CMD
- Entrypoint []string `protobuf:"bytes,2,rep,name=entrypoint,proto3" json:"entrypoint,omitempty"` // Override ENTRYPOINT
- WorkingDir string `protobuf:"bytes,3,opt,name=working_dir,json=workingDir,proto3" json:"working_dir,omitempty"` // Override WORKDIR
- Environment map[string]string `protobuf:"bytes,4,rep,name=environment,proto3" json:"environment,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // Environment variables
- ExposedPorts []string `protobuf:"bytes,5,rep,name=exposed_ports,json=exposedPorts,proto3" json:"exposed_ports,omitempty"` // Ports to expose
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *RuntimeConfig) Reset() {
- *x = RuntimeConfig{}
- mi := &file_builderd_v1_builder_proto_msgTypes[9]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *RuntimeConfig) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*RuntimeConfig) ProtoMessage() {}
-
-func (x *RuntimeConfig) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[9]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use RuntimeConfig.ProtoReflect.Descriptor instead.
-func (*RuntimeConfig) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{9}
-}
-
-func (x *RuntimeConfig) GetCommand() []string {
- if x != nil {
- return x.Command
- }
- return nil
-}
-
-func (x *RuntimeConfig) GetEntrypoint() []string {
- if x != nil {
- return x.Entrypoint
- }
- return nil
-}
-
-func (x *RuntimeConfig) GetWorkingDir() string {
- if x != nil {
- return x.WorkingDir
- }
- return ""
-}
-
-func (x *RuntimeConfig) GetEnvironment() map[string]string {
- if x != nil {
- return x.Environment
- }
- return nil
-}
-
-func (x *RuntimeConfig) GetExposedPorts() []string {
- if x != nil {
- return x.ExposedPorts
- }
- return nil
-}
-
-type OptimizationSettings struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- StripDebugSymbols bool `protobuf:"varint,1,opt,name=strip_debug_symbols,json=stripDebugSymbols,proto3" json:"strip_debug_symbols,omitempty"` // Strip debug info
- CompressBinaries bool `protobuf:"varint,2,opt,name=compress_binaries,json=compressBinaries,proto3" json:"compress_binaries,omitempty"` // Compress with UPX
- RemoveDocs bool `protobuf:"varint,3,opt,name=remove_docs,json=removeDocs,proto3" json:"remove_docs,omitempty"` // Remove documentation
- RemoveCache bool `protobuf:"varint,4,opt,name=remove_cache,json=removeCache,proto3" json:"remove_cache,omitempty"` // Remove package caches
- PreservePaths []string `protobuf:"bytes,5,rep,name=preserve_paths,json=preservePaths,proto3" json:"preserve_paths,omitempty"` // Paths to always keep
- ExcludePatterns []string `protobuf:"bytes,6,rep,name=exclude_patterns,json=excludePatterns,proto3" json:"exclude_patterns,omitempty"` // Files to exclude
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *OptimizationSettings) Reset() {
- *x = OptimizationSettings{}
- mi := &file_builderd_v1_builder_proto_msgTypes[10]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *OptimizationSettings) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*OptimizationSettings) ProtoMessage() {}
-
-func (x *OptimizationSettings) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[10]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use OptimizationSettings.ProtoReflect.Descriptor instead.
-func (*OptimizationSettings) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{10}
-}
-
-func (x *OptimizationSettings) GetStripDebugSymbols() bool {
- if x != nil {
- return x.StripDebugSymbols
- }
- return false
-}
-
-func (x *OptimizationSettings) GetCompressBinaries() bool {
- if x != nil {
- return x.CompressBinaries
- }
- return false
-}
-
-func (x *OptimizationSettings) GetRemoveDocs() bool {
- if x != nil {
- return x.RemoveDocs
- }
- return false
-}
-
-func (x *OptimizationSettings) GetRemoveCache() bool {
- if x != nil {
- return x.RemoveCache
- }
- return false
-}
-
-func (x *OptimizationSettings) GetPreservePaths() []string {
- if x != nil {
- return x.PreservePaths
- }
- return nil
-}
-
-func (x *OptimizationSettings) GetExcludePatterns() []string {
- if x != nil {
- return x.ExcludePatterns
- }
- return nil
-}
-
-// Build strategies - how to build from source to target
-type BuildStrategy struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // Types that are valid to be assigned to StrategyType:
- //
- // *BuildStrategy_DockerExtract
- // *BuildStrategy_GoApi
- // *BuildStrategy_Sinatra
- // *BuildStrategy_Nodejs
- StrategyType isBuildStrategy_StrategyType `protobuf_oneof:"strategy_type"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *BuildStrategy) Reset() {
- *x = BuildStrategy{}
- mi := &file_builderd_v1_builder_proto_msgTypes[11]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *BuildStrategy) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*BuildStrategy) ProtoMessage() {}
-
-func (x *BuildStrategy) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[11]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use BuildStrategy.ProtoReflect.Descriptor instead.
-func (*BuildStrategy) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{11}
-}
-
-func (x *BuildStrategy) GetStrategyType() isBuildStrategy_StrategyType {
- if x != nil {
- return x.StrategyType
- }
- return nil
-}
-
-func (x *BuildStrategy) GetDockerExtract() *DockerExtractStrategy {
- if x != nil {
- if x, ok := x.StrategyType.(*BuildStrategy_DockerExtract); ok {
- return x.DockerExtract
- }
- }
- return nil
-}
-
-func (x *BuildStrategy) GetGoApi() *GoApiStrategy {
- if x != nil {
- if x, ok := x.StrategyType.(*BuildStrategy_GoApi); ok {
- return x.GoApi
- }
- }
- return nil
-}
-
-func (x *BuildStrategy) GetSinatra() *SinatraStrategy {
- if x != nil {
- if x, ok := x.StrategyType.(*BuildStrategy_Sinatra); ok {
- return x.Sinatra
- }
- }
- return nil
-}
-
-func (x *BuildStrategy) GetNodejs() *NodejsStrategy {
- if x != nil {
- if x, ok := x.StrategyType.(*BuildStrategy_Nodejs); ok {
- return x.Nodejs
- }
- }
- return nil
-}
-
-type isBuildStrategy_StrategyType interface {
- isBuildStrategy_StrategyType()
-}
-
-type BuildStrategy_DockerExtract struct {
- DockerExtract *DockerExtractStrategy `protobuf:"bytes,1,opt,name=docker_extract,json=dockerExtract,proto3,oneof"`
-}
-
-type BuildStrategy_GoApi struct {
- GoApi *GoApiStrategy `protobuf:"bytes,2,opt,name=go_api,json=goApi,proto3,oneof"`
-}
-
-type BuildStrategy_Sinatra struct {
- Sinatra *SinatraStrategy `protobuf:"bytes,3,opt,name=sinatra,proto3,oneof"`
-}
-
-type BuildStrategy_Nodejs struct {
- Nodejs *NodejsStrategy `protobuf:"bytes,4,opt,name=nodejs,proto3,oneof"` // Future: python_wsgi = 5, rust_binary = 6, etc.
-}
-
-func (*BuildStrategy_DockerExtract) isBuildStrategy_StrategyType() {}
-
-func (*BuildStrategy_GoApi) isBuildStrategy_StrategyType() {}
-
-func (*BuildStrategy_Sinatra) isBuildStrategy_StrategyType() {}
-
-func (*BuildStrategy_Nodejs) isBuildStrategy_StrategyType() {}
-
-// Docker extraction strategy (first implementation)
-type DockerExtractStrategy struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- PreserveLayers bool `protobuf:"varint,1,opt,name=preserve_layers,json=preserveLayers,proto3" json:"preserve_layers,omitempty"` // Keep layer structure
- FlattenFilesystem bool `protobuf:"varint,2,opt,name=flatten_filesystem,json=flattenFilesystem,proto3" json:"flatten_filesystem,omitempty"` // Merge all layers
- ExcludePatterns []string `protobuf:"bytes,3,rep,name=exclude_patterns,json=excludePatterns,proto3" json:"exclude_patterns,omitempty"` // Files to exclude
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *DockerExtractStrategy) Reset() {
- *x = DockerExtractStrategy{}
- mi := &file_builderd_v1_builder_proto_msgTypes[12]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *DockerExtractStrategy) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*DockerExtractStrategy) ProtoMessage() {}
-
-func (x *DockerExtractStrategy) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[12]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use DockerExtractStrategy.ProtoReflect.Descriptor instead.
-func (*DockerExtractStrategy) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{12}
-}
-
-func (x *DockerExtractStrategy) GetPreserveLayers() bool {
- if x != nil {
- return x.PreserveLayers
- }
- return false
-}
-
-func (x *DockerExtractStrategy) GetFlattenFilesystem() bool {
- if x != nil {
- return x.FlattenFilesystem
- }
- return false
-}
-
-func (x *DockerExtractStrategy) GetExcludePatterns() []string {
- if x != nil {
- return x.ExcludePatterns
- }
- return nil
-}
-
-// Go API strategy (future)
-type GoApiStrategy struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- GoVersion string `protobuf:"bytes,1,opt,name=go_version,json=goVersion,proto3" json:"go_version,omitempty"` // "1.21", "latest"
- BuildFlags []string `protobuf:"bytes,2,rep,name=build_flags,json=buildFlags,proto3" json:"build_flags,omitempty"` // "-ldflags", "-tags"
- MainPackage string `protobuf:"bytes,3,opt,name=main_package,json=mainPackage,proto3" json:"main_package,omitempty"` // "./cmd/api"
- EnableCgo bool `protobuf:"varint,4,opt,name=enable_cgo,json=enableCgo,proto3" json:"enable_cgo,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *GoApiStrategy) Reset() {
- *x = GoApiStrategy{}
- mi := &file_builderd_v1_builder_proto_msgTypes[13]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *GoApiStrategy) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*GoApiStrategy) ProtoMessage() {}
-
-func (x *GoApiStrategy) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[13]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use GoApiStrategy.ProtoReflect.Descriptor instead.
-func (*GoApiStrategy) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{13}
-}
-
-func (x *GoApiStrategy) GetGoVersion() string {
- if x != nil {
- return x.GoVersion
- }
- return ""
-}
-
-func (x *GoApiStrategy) GetBuildFlags() []string {
- if x != nil {
- return x.BuildFlags
- }
- return nil
-}
-
-func (x *GoApiStrategy) GetMainPackage() string {
- if x != nil {
- return x.MainPackage
- }
- return ""
-}
-
-func (x *GoApiStrategy) GetEnableCgo() bool {
- if x != nil {
- return x.EnableCgo
- }
- return false
-}
-
-// Sinatra strategy (future)
-type SinatraStrategy struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- RubyVersion string `protobuf:"bytes,1,opt,name=ruby_version,json=rubyVersion,proto3" json:"ruby_version,omitempty"` // "3.2", "latest"
- GemfilePath string `protobuf:"bytes,2,opt,name=gemfile_path,json=gemfilePath,proto3" json:"gemfile_path,omitempty"` // "Gemfile"
- RackServer string `protobuf:"bytes,3,opt,name=rack_server,json=rackServer,proto3" json:"rack_server,omitempty"` // "puma", "unicorn"
- RackConfig map[string]string `protobuf:"bytes,4,rep,name=rack_config,json=rackConfig,proto3" json:"rack_config,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // Server-specific config
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *SinatraStrategy) Reset() {
- *x = SinatraStrategy{}
- mi := &file_builderd_v1_builder_proto_msgTypes[14]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *SinatraStrategy) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*SinatraStrategy) ProtoMessage() {}
-
-func (x *SinatraStrategy) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[14]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use SinatraStrategy.ProtoReflect.Descriptor instead.
-func (*SinatraStrategy) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{14}
-}
-
-func (x *SinatraStrategy) GetRubyVersion() string {
- if x != nil {
- return x.RubyVersion
- }
- return ""
-}
-
-func (x *SinatraStrategy) GetGemfilePath() string {
- if x != nil {
- return x.GemfilePath
- }
- return ""
-}
-
-func (x *SinatraStrategy) GetRackServer() string {
- if x != nil {
- return x.RackServer
- }
- return ""
-}
-
-func (x *SinatraStrategy) GetRackConfig() map[string]string {
- if x != nil {
- return x.RackConfig
- }
- return nil
-}
-
-// Node.js strategy (future)
-type NodejsStrategy struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- NodeVersion string `protobuf:"bytes,1,opt,name=node_version,json=nodeVersion,proto3" json:"node_version,omitempty"` // "18", "20", "latest"
- PackageManager string `protobuf:"bytes,2,opt,name=package_manager,json=packageManager,proto3" json:"package_manager,omitempty"` // "npm", "yarn", "pnpm"
- StartScript string `protobuf:"bytes,3,opt,name=start_script,json=startScript,proto3" json:"start_script,omitempty"` // "start", "server"
- EnableProduction bool `protobuf:"varint,4,opt,name=enable_production,json=enableProduction,proto3" json:"enable_production,omitempty"` // NODE_ENV=production
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *NodejsStrategy) Reset() {
- *x = NodejsStrategy{}
- mi := &file_builderd_v1_builder_proto_msgTypes[15]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *NodejsStrategy) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*NodejsStrategy) ProtoMessage() {}
-
-func (x *NodejsStrategy) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[15]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use NodejsStrategy.ProtoReflect.Descriptor instead.
-func (*NodejsStrategy) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{15}
-}
-
-func (x *NodejsStrategy) GetNodeVersion() string {
- if x != nil {
- return x.NodeVersion
- }
- return ""
-}
-
-func (x *NodejsStrategy) GetPackageManager() string {
- if x != nil {
- return x.PackageManager
- }
- return ""
-}
-
-func (x *NodejsStrategy) GetStartScript() string {
- if x != nil {
- return x.StartScript
- }
- return ""
-}
-
-func (x *NodejsStrategy) GetEnableProduction() bool {
- if x != nil {
- return x.EnableProduction
- }
- return false
-}
-
-// Main build configuration
-type BuildConfig struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // What we're building from
- Source *BuildSource `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"`
- // What we're building to
- Target *BuildTarget `protobuf:"bytes,2,opt,name=target,proto3" json:"target,omitempty"`
- // How to build it
- Strategy *BuildStrategy `protobuf:"bytes,3,opt,name=strategy,proto3" json:"strategy,omitempty"`
- // Build metadata
- BuildName string `protobuf:"bytes,4,opt,name=build_name,json=buildName,proto3" json:"build_name,omitempty"` // Human-readable name
- Labels map[string]string `protobuf:"bytes,5,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // Custom labels
- // Suggested asset ID to use when registering the built artifact
- // This allows the caller to pre-generate the asset ID
- SuggestedAssetId string `protobuf:"bytes,6,opt,name=suggested_asset_id,json=suggestedAssetId,proto3" json:"suggested_asset_id,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *BuildConfig) Reset() {
- *x = BuildConfig{}
- mi := &file_builderd_v1_builder_proto_msgTypes[16]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *BuildConfig) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*BuildConfig) ProtoMessage() {}
-
-func (x *BuildConfig) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[16]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use BuildConfig.ProtoReflect.Descriptor instead.
-func (*BuildConfig) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{16}
-}
-
-func (x *BuildConfig) GetSource() *BuildSource {
- if x != nil {
- return x.Source
- }
- return nil
-}
-
-func (x *BuildConfig) GetTarget() *BuildTarget {
- if x != nil {
- return x.Target
- }
- return nil
-}
-
-func (x *BuildConfig) GetStrategy() *BuildStrategy {
- if x != nil {
- return x.Strategy
- }
- return nil
-}
-
-func (x *BuildConfig) GetBuildName() string {
- if x != nil {
- return x.BuildName
- }
- return ""
-}
-
-func (x *BuildConfig) GetLabels() map[string]string {
- if x != nil {
- return x.Labels
- }
- return nil
-}
-
-func (x *BuildConfig) GetSuggestedAssetId() string {
- if x != nil {
- return x.SuggestedAssetId
- }
- return ""
-}
-
-// Build isolation metadata
-type BuildIsolation struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` // Unique sandbox identifier
- NetworkNamespace string `protobuf:"bytes,2,opt,name=network_namespace,json=networkNamespace,proto3" json:"network_namespace,omitempty"` // Network isolation
- FilesystemNamespace string `protobuf:"bytes,3,opt,name=filesystem_namespace,json=filesystemNamespace,proto3" json:"filesystem_namespace,omitempty"` // Filesystem isolation
- SecurityContexts []string `protobuf:"bytes,4,rep,name=security_contexts,json=securityContexts,proto3" json:"security_contexts,omitempty"` // SELinux/AppArmor contexts
- CgroupPath string `protobuf:"bytes,5,opt,name=cgroup_path,json=cgroupPath,proto3" json:"cgroup_path,omitempty"` // Resource cgroup
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *BuildIsolation) Reset() {
- *x = BuildIsolation{}
- mi := &file_builderd_v1_builder_proto_msgTypes[17]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *BuildIsolation) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*BuildIsolation) ProtoMessage() {}
-
-func (x *BuildIsolation) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[17]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use BuildIsolation.ProtoReflect.Descriptor instead.
-func (*BuildIsolation) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{17}
-}
-
-func (x *BuildIsolation) GetSandboxId() string {
- if x != nil {
- return x.SandboxId
- }
- return ""
-}
-
-func (x *BuildIsolation) GetNetworkNamespace() string {
- if x != nil {
- return x.NetworkNamespace
- }
- return ""
-}
-
-func (x *BuildIsolation) GetFilesystemNamespace() string {
- if x != nil {
- return x.FilesystemNamespace
- }
- return ""
-}
-
-func (x *BuildIsolation) GetSecurityContexts() []string {
- if x != nil {
- return x.SecurityContexts
- }
- return nil
-}
-
-func (x *BuildIsolation) GetCgroupPath() string {
- if x != nil {
- return x.CgroupPath
- }
- return ""
-}
-
-// Image metadata extracted from Docker images
-type ImageMetadata struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- OriginalImage string `protobuf:"bytes,1,opt,name=original_image,json=originalImage,proto3" json:"original_image,omitempty"` // Original Docker image
- ImageDigest string `protobuf:"bytes,2,opt,name=image_digest,json=imageDigest,proto3" json:"image_digest,omitempty"` // Docker image SHA256
- Layers []string `protobuf:"bytes,3,rep,name=layers,proto3" json:"layers,omitempty"` // Layer digests
- Labels map[string]string `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // Docker labels
- Command []string `protobuf:"bytes,5,rep,name=command,proto3" json:"command,omitempty"` // Original CMD
- Entrypoint []string `protobuf:"bytes,6,rep,name=entrypoint,proto3" json:"entrypoint,omitempty"` // Original ENTRYPOINT
- WorkingDir string `protobuf:"bytes,7,opt,name=working_dir,json=workingDir,proto3" json:"working_dir,omitempty"` // WORKDIR
- Env map[string]string `protobuf:"bytes,8,rep,name=env,proto3" json:"env,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // Environment variables
- ExposedPorts []string `protobuf:"bytes,9,rep,name=exposed_ports,json=exposedPorts,proto3" json:"exposed_ports,omitempty"` // EXPOSE ports
- User string `protobuf:"bytes,10,opt,name=user,proto3" json:"user,omitempty"` // USER directive
- Volumes []string `protobuf:"bytes,11,rep,name=volumes,proto3" json:"volumes,omitempty"` // VOLUME directives
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *ImageMetadata) Reset() {
- *x = ImageMetadata{}
- mi := &file_builderd_v1_builder_proto_msgTypes[18]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *ImageMetadata) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*ImageMetadata) ProtoMessage() {}
-
-func (x *ImageMetadata) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[18]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use ImageMetadata.ProtoReflect.Descriptor instead.
-func (*ImageMetadata) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{18}
-}
-
-func (x *ImageMetadata) GetOriginalImage() string {
- if x != nil {
- return x.OriginalImage
- }
- return ""
-}
-
-func (x *ImageMetadata) GetImageDigest() string {
- if x != nil {
- return x.ImageDigest
- }
- return ""
-}
-
-func (x *ImageMetadata) GetLayers() []string {
- if x != nil {
- return x.Layers
- }
- return nil
-}
-
-func (x *ImageMetadata) GetLabels() map[string]string {
- if x != nil {
- return x.Labels
- }
- return nil
-}
-
-func (x *ImageMetadata) GetCommand() []string {
- if x != nil {
- return x.Command
- }
- return nil
-}
-
-func (x *ImageMetadata) GetEntrypoint() []string {
- if x != nil {
- return x.Entrypoint
- }
- return nil
-}
-
-func (x *ImageMetadata) GetWorkingDir() string {
- if x != nil {
- return x.WorkingDir
- }
- return ""
-}
-
-func (x *ImageMetadata) GetEnv() map[string]string {
- if x != nil {
- return x.Env
- }
- return nil
-}
-
-func (x *ImageMetadata) GetExposedPorts() []string {
- if x != nil {
- return x.ExposedPorts
- }
- return nil
-}
-
-func (x *ImageMetadata) GetUser() string {
- if x != nil {
- return x.User
- }
- return ""
-}
-
-func (x *ImageMetadata) GetVolumes() []string {
- if x != nil {
- return x.Volumes
- }
- return nil
-}
-
-// Build performance metrics
-type BuildMetrics struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- PullDurationMs int64 `protobuf:"varint,1,opt,name=pull_duration_ms,json=pullDurationMs,proto3" json:"pull_duration_ms,omitempty"` // Time to pull image/source
- ExtractDurationMs int64 `protobuf:"varint,2,opt,name=extract_duration_ms,json=extractDurationMs,proto3" json:"extract_duration_ms,omitempty"` // Time to extract layers
- BuildDurationMs int64 `protobuf:"varint,3,opt,name=build_duration_ms,json=buildDurationMs,proto3" json:"build_duration_ms,omitempty"` // Time to build rootfs
- OptimizeDurationMs int64 `protobuf:"varint,4,opt,name=optimize_duration_ms,json=optimizeDurationMs,proto3" json:"optimize_duration_ms,omitempty"` // Time for optimizations
- TotalDurationMs int64 `protobuf:"varint,5,opt,name=total_duration_ms,json=totalDurationMs,proto3" json:"total_duration_ms,omitempty"` // Total build time
- OriginalSizeBytes int64 `protobuf:"varint,6,opt,name=original_size_bytes,json=originalSizeBytes,proto3" json:"original_size_bytes,omitempty"` // Original image/source size
- RootfsSizeBytes int64 `protobuf:"varint,7,opt,name=rootfs_size_bytes,json=rootfsSizeBytes,proto3" json:"rootfs_size_bytes,omitempty"` // Final rootfs size
- CompressionRatio int64 `protobuf:"varint,8,opt,name=compression_ratio,json=compressionRatio,proto3" json:"compression_ratio,omitempty"` // Size reduction percentage
- MemoryPeakBytes int64 `protobuf:"varint,9,opt,name=memory_peak_bytes,json=memoryPeakBytes,proto3" json:"memory_peak_bytes,omitempty"` // Peak memory usage
- DiskUsageBytes int64 `protobuf:"varint,10,opt,name=disk_usage_bytes,json=diskUsageBytes,proto3" json:"disk_usage_bytes,omitempty"` // Temporary disk usage
- CpuCoresUsed int32 `protobuf:"varint,11,opt,name=cpu_cores_used,json=cpuCoresUsed,proto3" json:"cpu_cores_used,omitempty"` // CPU cores utilized
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *BuildMetrics) Reset() {
- *x = BuildMetrics{}
- mi := &file_builderd_v1_builder_proto_msgTypes[19]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *BuildMetrics) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*BuildMetrics) ProtoMessage() {}
-
-func (x *BuildMetrics) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[19]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use BuildMetrics.ProtoReflect.Descriptor instead.
-func (*BuildMetrics) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{19}
-}
-
-func (x *BuildMetrics) GetPullDurationMs() int64 {
- if x != nil {
- return x.PullDurationMs
- }
- return 0
-}
-
-func (x *BuildMetrics) GetExtractDurationMs() int64 {
- if x != nil {
- return x.ExtractDurationMs
- }
- return 0
-}
-
-func (x *BuildMetrics) GetBuildDurationMs() int64 {
- if x != nil {
- return x.BuildDurationMs
- }
- return 0
-}
-
-func (x *BuildMetrics) GetOptimizeDurationMs() int64 {
- if x != nil {
- return x.OptimizeDurationMs
- }
- return 0
-}
-
-func (x *BuildMetrics) GetTotalDurationMs() int64 {
- if x != nil {
- return x.TotalDurationMs
- }
- return 0
-}
-
-func (x *BuildMetrics) GetOriginalSizeBytes() int64 {
- if x != nil {
- return x.OriginalSizeBytes
- }
- return 0
-}
-
-func (x *BuildMetrics) GetRootfsSizeBytes() int64 {
- if x != nil {
- return x.RootfsSizeBytes
- }
- return 0
-}
-
-func (x *BuildMetrics) GetCompressionRatio() int64 {
- if x != nil {
- return x.CompressionRatio
- }
- return 0
-}
-
-func (x *BuildMetrics) GetMemoryPeakBytes() int64 {
- if x != nil {
- return x.MemoryPeakBytes
- }
- return 0
-}
-
-func (x *BuildMetrics) GetDiskUsageBytes() int64 {
- if x != nil {
- return x.DiskUsageBytes
- }
- return 0
-}
-
-func (x *BuildMetrics) GetCpuCoresUsed() int32 {
- if x != nil {
- return x.CpuCoresUsed
- }
- return 0
-}
-
-// Complete build job information
-type BuildJob struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- BuildId string `protobuf:"bytes,1,opt,name=build_id,json=buildId,proto3" json:"build_id,omitempty"` // Unique build identifier
- Config *BuildConfig `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"` // Build configuration
- State BuildState `protobuf:"varint,3,opt,name=state,proto3,enum=builderd.v1.BuildState" json:"state,omitempty"` // Current build state
- // Timestamps
- CreatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
- StartedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=started_at,json=startedAt,proto3" json:"started_at,omitempty"`
- CompletedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=completed_at,json=completedAt,proto3" json:"completed_at,omitempty"`
- // Results
- RootfsPath string `protobuf:"bytes,7,opt,name=rootfs_path,json=rootfsPath,proto3" json:"rootfs_path,omitempty"` // Path to built rootfs
- RootfsSizeBytes int64 `protobuf:"varint,8,opt,name=rootfs_size_bytes,json=rootfsSizeBytes,proto3" json:"rootfs_size_bytes,omitempty"` // Size of rootfs
- RootfsChecksum string `protobuf:"bytes,9,opt,name=rootfs_checksum,json=rootfsChecksum,proto3" json:"rootfs_checksum,omitempty"` // SHA256 of rootfs
- // Build metadata
- ImageMetadata *ImageMetadata `protobuf:"bytes,10,opt,name=image_metadata,json=imageMetadata,proto3" json:"image_metadata,omitempty"`
- Metrics *BuildMetrics `protobuf:"bytes,11,opt,name=metrics,proto3" json:"metrics,omitempty"`
- Isolation *BuildIsolation `protobuf:"bytes,12,opt,name=isolation,proto3" json:"isolation,omitempty"`
- // Error information
- ErrorMessage string `protobuf:"bytes,13,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"`
- BuildLogs []string `protobuf:"bytes,14,rep,name=build_logs,json=buildLogs,proto3" json:"build_logs,omitempty"`
- // Progress information
- ProgressPercent int32 `protobuf:"varint,15,opt,name=progress_percent,json=progressPercent,proto3" json:"progress_percent,omitempty"` // 0-100
- CurrentStep string `protobuf:"bytes,16,opt,name=current_step,json=currentStep,proto3" json:"current_step,omitempty"` // Current build step
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *BuildJob) Reset() {
- *x = BuildJob{}
- mi := &file_builderd_v1_builder_proto_msgTypes[20]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *BuildJob) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*BuildJob) ProtoMessage() {}
-
-func (x *BuildJob) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[20]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use BuildJob.ProtoReflect.Descriptor instead.
-func (*BuildJob) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{20}
-}
-
-func (x *BuildJob) GetBuildId() string {
- if x != nil {
- return x.BuildId
- }
- return ""
-}
-
-func (x *BuildJob) GetConfig() *BuildConfig {
- if x != nil {
- return x.Config
- }
- return nil
-}
-
-func (x *BuildJob) GetState() BuildState {
- if x != nil {
- return x.State
- }
- return BuildState_BUILD_STATE_UNSPECIFIED
-}
-
-func (x *BuildJob) GetCreatedAt() *timestamppb.Timestamp {
- if x != nil {
- return x.CreatedAt
- }
- return nil
-}
-
-func (x *BuildJob) GetStartedAt() *timestamppb.Timestamp {
- if x != nil {
- return x.StartedAt
- }
- return nil
-}
-
-func (x *BuildJob) GetCompletedAt() *timestamppb.Timestamp {
- if x != nil {
- return x.CompletedAt
- }
- return nil
-}
-
-func (x *BuildJob) GetRootfsPath() string {
- if x != nil {
- return x.RootfsPath
- }
- return ""
-}
-
-func (x *BuildJob) GetRootfsSizeBytes() int64 {
- if x != nil {
- return x.RootfsSizeBytes
- }
- return 0
-}
-
-func (x *BuildJob) GetRootfsChecksum() string {
- if x != nil {
- return x.RootfsChecksum
- }
- return ""
-}
-
-func (x *BuildJob) GetImageMetadata() *ImageMetadata {
- if x != nil {
- return x.ImageMetadata
- }
- return nil
-}
-
-func (x *BuildJob) GetMetrics() *BuildMetrics {
- if x != nil {
- return x.Metrics
- }
- return nil
-}
-
-func (x *BuildJob) GetIsolation() *BuildIsolation {
- if x != nil {
- return x.Isolation
- }
- return nil
-}
-
-func (x *BuildJob) GetErrorMessage() string {
- if x != nil {
- return x.ErrorMessage
- }
- return ""
-}
-
-func (x *BuildJob) GetBuildLogs() []string {
- if x != nil {
- return x.BuildLogs
- }
- return nil
-}
-
-func (x *BuildJob) GetProgressPercent() int32 {
- if x != nil {
- return x.ProgressPercent
- }
- return 0
-}
-
-func (x *BuildJob) GetCurrentStep() string {
- if x != nil {
- return x.CurrentStep
- }
- return ""
-}
-
-// Build log entry for streaming
-type StreamBuildLogsResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Timestamp *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
- Level string `protobuf:"bytes,2,opt,name=level,proto3" json:"level,omitempty"` // "info", "warn", "error", "debug"
- Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"`
- Component string `protobuf:"bytes,4,opt,name=component,proto3" json:"component,omitempty"` // "puller", "extractor", "builder"
- Metadata map[string]string `protobuf:"bytes,5,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *StreamBuildLogsResponse) Reset() {
- *x = StreamBuildLogsResponse{}
- mi := &file_builderd_v1_builder_proto_msgTypes[21]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *StreamBuildLogsResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*StreamBuildLogsResponse) ProtoMessage() {}
-
-func (x *StreamBuildLogsResponse) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[21]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use StreamBuildLogsResponse.ProtoReflect.Descriptor instead.
-func (*StreamBuildLogsResponse) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{21}
-}
-
-func (x *StreamBuildLogsResponse) GetTimestamp() *timestamppb.Timestamp {
- if x != nil {
- return x.Timestamp
- }
- return nil
-}
-
-func (x *StreamBuildLogsResponse) GetLevel() string {
- if x != nil {
- return x.Level
- }
- return ""
-}
-
-func (x *StreamBuildLogsResponse) GetMessage() string {
- if x != nil {
- return x.Message
- }
- return ""
-}
-
-func (x *StreamBuildLogsResponse) GetComponent() string {
- if x != nil {
- return x.Component
- }
- return ""
-}
-
-func (x *StreamBuildLogsResponse) GetMetadata() map[string]string {
- if x != nil {
- return x.Metadata
- }
- return nil
-}
-
-// Request/Response messages
-type CreateBuildRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Config *BuildConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *CreateBuildRequest) Reset() {
- *x = CreateBuildRequest{}
- mi := &file_builderd_v1_builder_proto_msgTypes[22]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *CreateBuildRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*CreateBuildRequest) ProtoMessage() {}
-
-func (x *CreateBuildRequest) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[22]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use CreateBuildRequest.ProtoReflect.Descriptor instead.
-func (*CreateBuildRequest) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{22}
-}
-
-func (x *CreateBuildRequest) GetConfig() *BuildConfig {
- if x != nil {
- return x.Config
- }
- return nil
-}
-
-type CreateBuildResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- BuildId string `protobuf:"bytes,1,opt,name=build_id,json=buildId,proto3" json:"build_id,omitempty"`
- State BuildState `protobuf:"varint,2,opt,name=state,proto3,enum=builderd.v1.BuildState" json:"state,omitempty"`
- CreatedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
- RootfsPath string `protobuf:"bytes,4,opt,name=rootfs_path,json=rootfsPath,proto3" json:"rootfs_path,omitempty"` // Path to the generated rootfs for VM creation
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *CreateBuildResponse) Reset() {
- *x = CreateBuildResponse{}
- mi := &file_builderd_v1_builder_proto_msgTypes[23]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *CreateBuildResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*CreateBuildResponse) ProtoMessage() {}
-
-func (x *CreateBuildResponse) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[23]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use CreateBuildResponse.ProtoReflect.Descriptor instead.
-func (*CreateBuildResponse) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{23}
-}
-
-func (x *CreateBuildResponse) GetBuildId() string {
- if x != nil {
- return x.BuildId
- }
- return ""
-}
-
-func (x *CreateBuildResponse) GetState() BuildState {
- if x != nil {
- return x.State
- }
- return BuildState_BUILD_STATE_UNSPECIFIED
-}
-
-func (x *CreateBuildResponse) GetCreatedAt() *timestamppb.Timestamp {
- if x != nil {
- return x.CreatedAt
- }
- return nil
-}
-
-func (x *CreateBuildResponse) GetRootfsPath() string {
- if x != nil {
- return x.RootfsPath
- }
- return ""
-}
-
-type GetBuildRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- BuildId string `protobuf:"bytes,1,opt,name=build_id,json=buildId,proto3" json:"build_id,omitempty"`
- TenantId string `protobuf:"bytes,2,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` // For authorization
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *GetBuildRequest) Reset() {
- *x = GetBuildRequest{}
- mi := &file_builderd_v1_builder_proto_msgTypes[24]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *GetBuildRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*GetBuildRequest) ProtoMessage() {}
-
-func (x *GetBuildRequest) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[24]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use GetBuildRequest.ProtoReflect.Descriptor instead.
-func (*GetBuildRequest) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{24}
-}
-
-func (x *GetBuildRequest) GetBuildId() string {
- if x != nil {
- return x.BuildId
- }
- return ""
-}
-
-func (x *GetBuildRequest) GetTenantId() string {
- if x != nil {
- return x.TenantId
- }
- return ""
-}
-
-type GetBuildResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Build *BuildJob `protobuf:"bytes,1,opt,name=build,proto3" json:"build,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *GetBuildResponse) Reset() {
- *x = GetBuildResponse{}
- mi := &file_builderd_v1_builder_proto_msgTypes[25]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *GetBuildResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*GetBuildResponse) ProtoMessage() {}
-
-func (x *GetBuildResponse) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[25]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use GetBuildResponse.ProtoReflect.Descriptor instead.
-func (*GetBuildResponse) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{25}
-}
-
-func (x *GetBuildResponse) GetBuild() *BuildJob {
- if x != nil {
- return x.Build
- }
- return nil
-}
-
-type ListBuildsRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- StateFilter []BuildState `protobuf:"varint,1,rep,packed,name=state_filter,json=stateFilter,proto3,enum=builderd.v1.BuildState" json:"state_filter,omitempty"`
- PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
- PageToken string `protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *ListBuildsRequest) Reset() {
- *x = ListBuildsRequest{}
- mi := &file_builderd_v1_builder_proto_msgTypes[26]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *ListBuildsRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*ListBuildsRequest) ProtoMessage() {}
-
-func (x *ListBuildsRequest) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[26]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use ListBuildsRequest.ProtoReflect.Descriptor instead.
-func (*ListBuildsRequest) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{26}
-}
-
-func (x *ListBuildsRequest) GetStateFilter() []BuildState {
- if x != nil {
- return x.StateFilter
- }
- return nil
-}
-
-func (x *ListBuildsRequest) GetPageSize() int32 {
- if x != nil {
- return x.PageSize
- }
- return 0
-}
-
-func (x *ListBuildsRequest) GetPageToken() string {
- if x != nil {
- return x.PageToken
- }
- return ""
-}
-
-type ListBuildsResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Builds []*BuildJob `protobuf:"bytes,1,rep,name=builds,proto3" json:"builds,omitempty"`
- NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"`
- TotalCount int32 `protobuf:"varint,3,opt,name=total_count,json=totalCount,proto3" json:"total_count,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *ListBuildsResponse) Reset() {
- *x = ListBuildsResponse{}
- mi := &file_builderd_v1_builder_proto_msgTypes[27]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *ListBuildsResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*ListBuildsResponse) ProtoMessage() {}
-
-func (x *ListBuildsResponse) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[27]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use ListBuildsResponse.ProtoReflect.Descriptor instead.
-func (*ListBuildsResponse) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{27}
-}
-
-func (x *ListBuildsResponse) GetBuilds() []*BuildJob {
- if x != nil {
- return x.Builds
- }
- return nil
-}
-
-func (x *ListBuildsResponse) GetNextPageToken() string {
- if x != nil {
- return x.NextPageToken
- }
- return ""
-}
-
-func (x *ListBuildsResponse) GetTotalCount() int32 {
- if x != nil {
- return x.TotalCount
- }
- return 0
-}
-
-type CancelBuildRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- BuildId string `protobuf:"bytes,1,opt,name=build_id,json=buildId,proto3" json:"build_id,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *CancelBuildRequest) Reset() {
- *x = CancelBuildRequest{}
- mi := &file_builderd_v1_builder_proto_msgTypes[28]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *CancelBuildRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*CancelBuildRequest) ProtoMessage() {}
-
-func (x *CancelBuildRequest) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[28]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use CancelBuildRequest.ProtoReflect.Descriptor instead.
-func (*CancelBuildRequest) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{28}
-}
-
-func (x *CancelBuildRequest) GetBuildId() string {
- if x != nil {
- return x.BuildId
- }
- return ""
-}
-
-type CancelBuildResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
- State BuildState `protobuf:"varint,2,opt,name=state,proto3,enum=builderd.v1.BuildState" json:"state,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *CancelBuildResponse) Reset() {
- *x = CancelBuildResponse{}
- mi := &file_builderd_v1_builder_proto_msgTypes[29]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *CancelBuildResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*CancelBuildResponse) ProtoMessage() {}
-
-func (x *CancelBuildResponse) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[29]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use CancelBuildResponse.ProtoReflect.Descriptor instead.
-func (*CancelBuildResponse) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{29}
-}
-
-func (x *CancelBuildResponse) GetSuccess() bool {
- if x != nil {
- return x.Success
- }
- return false
-}
-
-func (x *CancelBuildResponse) GetState() BuildState {
- if x != nil {
- return x.State
- }
- return BuildState_BUILD_STATE_UNSPECIFIED
-}
-
-type DeleteBuildRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- BuildId string `protobuf:"bytes,1,opt,name=build_id,json=buildId,proto3" json:"build_id,omitempty"`
- Force bool `protobuf:"varint,2,opt,name=force,proto3" json:"force,omitempty"` // Delete even if running
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *DeleteBuildRequest) Reset() {
- *x = DeleteBuildRequest{}
- mi := &file_builderd_v1_builder_proto_msgTypes[30]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *DeleteBuildRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*DeleteBuildRequest) ProtoMessage() {}
-
-func (x *DeleteBuildRequest) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[30]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use DeleteBuildRequest.ProtoReflect.Descriptor instead.
-func (*DeleteBuildRequest) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{30}
-}
-
-func (x *DeleteBuildRequest) GetBuildId() string {
- if x != nil {
- return x.BuildId
- }
- return ""
-}
-
-func (x *DeleteBuildRequest) GetForce() bool {
- if x != nil {
- return x.Force
- }
- return false
-}
-
-type DeleteBuildResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *DeleteBuildResponse) Reset() {
- *x = DeleteBuildResponse{}
- mi := &file_builderd_v1_builder_proto_msgTypes[31]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *DeleteBuildResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*DeleteBuildResponse) ProtoMessage() {}
-
-func (x *DeleteBuildResponse) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[31]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use DeleteBuildResponse.ProtoReflect.Descriptor instead.
-func (*DeleteBuildResponse) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{31}
-}
-
-func (x *DeleteBuildResponse) GetSuccess() bool {
- if x != nil {
- return x.Success
- }
- return false
-}
-
-type StreamBuildLogsRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- BuildId string `protobuf:"bytes,1,opt,name=build_id,json=buildId,proto3" json:"build_id,omitempty"`
- Follow bool `protobuf:"varint,2,opt,name=follow,proto3" json:"follow,omitempty"` // Continue streaming new logs
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *StreamBuildLogsRequest) Reset() {
- *x = StreamBuildLogsRequest{}
- mi := &file_builderd_v1_builder_proto_msgTypes[32]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *StreamBuildLogsRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*StreamBuildLogsRequest) ProtoMessage() {}
-
-func (x *StreamBuildLogsRequest) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[32]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use StreamBuildLogsRequest.ProtoReflect.Descriptor instead.
-func (*StreamBuildLogsRequest) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{32}
-}
-
-func (x *StreamBuildLogsRequest) GetBuildId() string {
- if x != nil {
- return x.BuildId
- }
- return ""
-}
-
-func (x *StreamBuildLogsRequest) GetFollow() bool {
- if x != nil {
- return x.Follow
- }
- return false
-}
-
-type GetBuildStatsRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- TenantId string `protobuf:"bytes,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"`
- StartTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"`
- EndTime *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=end_time,json=endTime,proto3" json:"end_time,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *GetBuildStatsRequest) Reset() {
- *x = GetBuildStatsRequest{}
- mi := &file_builderd_v1_builder_proto_msgTypes[33]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *GetBuildStatsRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*GetBuildStatsRequest) ProtoMessage() {}
-
-func (x *GetBuildStatsRequest) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[33]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use GetBuildStatsRequest.ProtoReflect.Descriptor instead.
-func (*GetBuildStatsRequest) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{33}
-}
-
-func (x *GetBuildStatsRequest) GetTenantId() string {
- if x != nil {
- return x.TenantId
- }
- return ""
-}
-
-func (x *GetBuildStatsRequest) GetStartTime() *timestamppb.Timestamp {
- if x != nil {
- return x.StartTime
- }
- return nil
-}
-
-func (x *GetBuildStatsRequest) GetEndTime() *timestamppb.Timestamp {
- if x != nil {
- return x.EndTime
- }
- return nil
-}
-
-type GetBuildStatsResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- TotalBuilds int32 `protobuf:"varint,1,opt,name=total_builds,json=totalBuilds,proto3" json:"total_builds,omitempty"`
- SuccessfulBuilds int32 `protobuf:"varint,2,opt,name=successful_builds,json=successfulBuilds,proto3" json:"successful_builds,omitempty"`
- FailedBuilds int32 `protobuf:"varint,3,opt,name=failed_builds,json=failedBuilds,proto3" json:"failed_builds,omitempty"`
- AvgBuildTimeMs int64 `protobuf:"varint,4,opt,name=avg_build_time_ms,json=avgBuildTimeMs,proto3" json:"avg_build_time_ms,omitempty"`
- TotalStorageBytes int64 `protobuf:"varint,5,opt,name=total_storage_bytes,json=totalStorageBytes,proto3" json:"total_storage_bytes,omitempty"`
- TotalComputeMinutes int64 `protobuf:"varint,6,opt,name=total_compute_minutes,json=totalComputeMinutes,proto3" json:"total_compute_minutes,omitempty"`
- RecentBuilds []*BuildJob `protobuf:"bytes,7,rep,name=recent_builds,json=recentBuilds,proto3" json:"recent_builds,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *GetBuildStatsResponse) Reset() {
- *x = GetBuildStatsResponse{}
- mi := &file_builderd_v1_builder_proto_msgTypes[34]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *GetBuildStatsResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*GetBuildStatsResponse) ProtoMessage() {}
-
-func (x *GetBuildStatsResponse) ProtoReflect() protoreflect.Message {
- mi := &file_builderd_v1_builder_proto_msgTypes[34]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use GetBuildStatsResponse.ProtoReflect.Descriptor instead.
-func (*GetBuildStatsResponse) Descriptor() ([]byte, []int) {
- return file_builderd_v1_builder_proto_rawDescGZIP(), []int{34}
-}
-
-func (x *GetBuildStatsResponse) GetTotalBuilds() int32 {
- if x != nil {
- return x.TotalBuilds
- }
- return 0
-}
-
-func (x *GetBuildStatsResponse) GetSuccessfulBuilds() int32 {
- if x != nil {
- return x.SuccessfulBuilds
- }
- return 0
-}
-
-func (x *GetBuildStatsResponse) GetFailedBuilds() int32 {
- if x != nil {
- return x.FailedBuilds
- }
- return 0
-}
-
-func (x *GetBuildStatsResponse) GetAvgBuildTimeMs() int64 {
- if x != nil {
- return x.AvgBuildTimeMs
- }
- return 0
-}
-
-func (x *GetBuildStatsResponse) GetTotalStorageBytes() int64 {
- if x != nil {
- return x.TotalStorageBytes
- }
- return 0
-}
-
-func (x *GetBuildStatsResponse) GetTotalComputeMinutes() int64 {
- if x != nil {
- return x.TotalComputeMinutes
- }
- return 0
-}
-
-func (x *GetBuildStatsResponse) GetRecentBuilds() []*BuildJob {
- if x != nil {
- return x.RecentBuilds
- }
- return nil
-}
-
-var File_builderd_v1_builder_proto protoreflect.FileDescriptor
-
-const file_builderd_v1_builder_proto_rawDesc = "" +
- "\n" +
- "\x19builderd/v1/builder.proto\x12\vbuilderd.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\xe4\x01\n" +
- "\vBuildSource\x12C\n" +
- "\fdocker_image\x18\x01 \x01(\v2\x1e.builderd.v1.DockerImageSourceH\x00R\vdockerImage\x12I\n" +
- "\x0egit_repository\x18\x02 \x01(\v2 .builderd.v1.GitRepositorySourceH\x00R\rgitRepository\x126\n" +
- "\aarchive\x18\x03 \x01(\v2\x1a.builderd.v1.ArchiveSourceH\x00R\aarchiveB\r\n" +
- "\vsource_type\"z\n" +
- "\x11DockerImageSource\x12\x1b\n" +
- "\timage_uri\x18\x01 \x01(\tR\bimageUri\x12+\n" +
- "\x04auth\x18\x02 \x01(\v2\x17.builderd.v1.DockerAuthR\x04auth\x12\x1b\n" +
- "\tpull_tags\x18\x03 \x03(\tR\bpullTags\"v\n" +
- "\n" +
- "DockerAuth\x12\x1a\n" +
- "\busername\x18\x01 \x01(\tR\busername\x12\x1a\n" +
- "\bpassword\x18\x02 \x01(\tR\bpassword\x12\x14\n" +
- "\x05token\x18\x03 \x01(\tR\x05token\x12\x1a\n" +
- "\bregistry\x18\x04 \x01(\tR\bregistry\"\x9d\x01\n" +
- "\x13GitRepositorySource\x12%\n" +
- "\x0erepository_url\x18\x01 \x01(\tR\rrepositoryUrl\x12\x10\n" +
- "\x03ref\x18\x02 \x01(\tR\x03ref\x12#\n" +
- "\rbuild_context\x18\x03 \x01(\tR\fbuildContext\x12(\n" +
- "\x04auth\x18\x04 \x01(\v2\x14.builderd.v1.GitAuthR\x04auth\"p\n" +
- "\aGitAuth\x12\x1a\n" +
- "\busername\x18\x01 \x01(\tR\busername\x12\x1a\n" +
- "\bpassword\x18\x02 \x01(\tR\bpassword\x12\x17\n" +
- "\assh_key\x18\x03 \x01(\tR\x06sshKey\x12\x14\n" +
- "\x05token\x18\x04 \x01(\tR\x05token\"x\n" +
- "\rArchiveSource\x12\x1f\n" +
- "\varchive_url\x18\x01 \x01(\tR\n" +
- "archiveUrl\x12!\n" +
- "\farchive_type\x18\x02 \x01(\tR\varchiveType\x12#\n" +
- "\rbuild_context\x18\x03 \x01(\tR\fbuildContext\"\xa9\x01\n" +
- "\vBuildTarget\x12C\n" +
- "\x0emicrovm_rootfs\x18\x01 \x01(\v2\x1a.builderd.v1.MicroVMRootfsH\x00R\rmicrovmRootfs\x12F\n" +
- "\x0fcontainer_image\x18\x02 \x01(\v2\x1b.builderd.v1.ContainerImageH\x00R\x0econtainerImageB\r\n" +
- "\vtarget_type\"\x80\x02\n" +
- "\rMicroVMRootfs\x12>\n" +
- "\rinit_strategy\x18\x01 \x01(\x0e2\x19.builderd.v1.InitStrategyR\finitStrategy\x12A\n" +
- "\x0eruntime_config\x18\x02 \x01(\v2\x1a.builderd.v1.RuntimeConfigR\rruntimeConfig\x12E\n" +
- "\foptimization\x18\x03 \x01(\v2!.builderd.v1.OptimizationSettingsR\foptimization\x12%\n" +
- "\x0epreserve_paths\x18\x04 \x03(\tR\rpreservePaths\"G\n" +
- "\x0eContainerImage\x12\x1d\n" +
- "\n" +
- "base_image\x18\x01 \x01(\tR\tbaseImage\x12\x16\n" +
- "\x06layers\x18\x02 \x03(\tR\x06layers\"\x9e\x02\n" +
- "\rRuntimeConfig\x12\x18\n" +
- "\acommand\x18\x01 \x03(\tR\acommand\x12\x1e\n" +
- "\n" +
- "entrypoint\x18\x02 \x03(\tR\n" +
- "entrypoint\x12\x1f\n" +
- "\vworking_dir\x18\x03 \x01(\tR\n" +
- "workingDir\x12M\n" +
- "\venvironment\x18\x04 \x03(\v2+.builderd.v1.RuntimeConfig.EnvironmentEntryR\venvironment\x12#\n" +
- "\rexposed_ports\x18\x05 \x03(\tR\fexposedPorts\x1a>\n" +
- "\x10EnvironmentEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x89\x02\n" +
- "\x14OptimizationSettings\x12.\n" +
- "\x13strip_debug_symbols\x18\x01 \x01(\bR\x11stripDebugSymbols\x12+\n" +
- "\x11compress_binaries\x18\x02 \x01(\bR\x10compressBinaries\x12\x1f\n" +
- "\vremove_docs\x18\x03 \x01(\bR\n" +
- "removeDocs\x12!\n" +
- "\fremove_cache\x18\x04 \x01(\bR\vremoveCache\x12%\n" +
- "\x0epreserve_paths\x18\x05 \x03(\tR\rpreservePaths\x12)\n" +
- "\x10exclude_patterns\x18\x06 \x03(\tR\x0fexcludePatterns\"\x93\x02\n" +
- "\rBuildStrategy\x12K\n" +
- "\x0edocker_extract\x18\x01 \x01(\v2\".builderd.v1.DockerExtractStrategyH\x00R\rdockerExtract\x123\n" +
- "\x06go_api\x18\x02 \x01(\v2\x1a.builderd.v1.GoApiStrategyH\x00R\x05goApi\x128\n" +
- "\asinatra\x18\x03 \x01(\v2\x1c.builderd.v1.SinatraStrategyH\x00R\asinatra\x125\n" +
- "\x06nodejs\x18\x04 \x01(\v2\x1b.builderd.v1.NodejsStrategyH\x00R\x06nodejsB\x0f\n" +
- "\rstrategy_type\"\x9a\x01\n" +
- "\x15DockerExtractStrategy\x12'\n" +
- "\x0fpreserve_layers\x18\x01 \x01(\bR\x0epreserveLayers\x12-\n" +
- "\x12flatten_filesystem\x18\x02 \x01(\bR\x11flattenFilesystem\x12)\n" +
- "\x10exclude_patterns\x18\x03 \x03(\tR\x0fexcludePatterns\"\x91\x01\n" +
- "\rGoApiStrategy\x12\x1d\n" +
- "\n" +
- "go_version\x18\x01 \x01(\tR\tgoVersion\x12\x1f\n" +
- "\vbuild_flags\x18\x02 \x03(\tR\n" +
- "buildFlags\x12!\n" +
- "\fmain_package\x18\x03 \x01(\tR\vmainPackage\x12\x1d\n" +
- "\n" +
- "enable_cgo\x18\x04 \x01(\bR\tenableCgo\"\x86\x02\n" +
- "\x0fSinatraStrategy\x12!\n" +
- "\fruby_version\x18\x01 \x01(\tR\vrubyVersion\x12!\n" +
- "\fgemfile_path\x18\x02 \x01(\tR\vgemfilePath\x12\x1f\n" +
- "\vrack_server\x18\x03 \x01(\tR\n" +
- "rackServer\x12M\n" +
- "\vrack_config\x18\x04 \x03(\v2,.builderd.v1.SinatraStrategy.RackConfigEntryR\n" +
- "rackConfig\x1a=\n" +
- "\x0fRackConfigEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xac\x01\n" +
- "\x0eNodejsStrategy\x12!\n" +
- "\fnode_version\x18\x01 \x01(\tR\vnodeVersion\x12'\n" +
- "\x0fpackage_manager\x18\x02 \x01(\tR\x0epackageManager\x12!\n" +
- "\fstart_script\x18\x03 \x01(\tR\vstartScript\x12+\n" +
- "\x11enable_production\x18\x04 \x01(\bR\x10enableProduction\"\xef\x02\n" +
- "\vBuildConfig\x120\n" +
- "\x06source\x18\x01 \x01(\v2\x18.builderd.v1.BuildSourceR\x06source\x120\n" +
- "\x06target\x18\x02 \x01(\v2\x18.builderd.v1.BuildTargetR\x06target\x126\n" +
- "\bstrategy\x18\x03 \x01(\v2\x1a.builderd.v1.BuildStrategyR\bstrategy\x12\x1d\n" +
- "\n" +
- "build_name\x18\x04 \x01(\tR\tbuildName\x12<\n" +
- "\x06labels\x18\x05 \x03(\v2$.builderd.v1.BuildConfig.LabelsEntryR\x06labels\x12,\n" +
- "\x12suggested_asset_id\x18\x06 \x01(\tR\x10suggestedAssetId\x1a9\n" +
- "\vLabelsEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xdd\x01\n" +
- "\x0eBuildIsolation\x12\x1d\n" +
- "\n" +
- "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12+\n" +
- "\x11network_namespace\x18\x02 \x01(\tR\x10networkNamespace\x121\n" +
- "\x14filesystem_namespace\x18\x03 \x01(\tR\x13filesystemNamespace\x12+\n" +
- "\x11security_contexts\x18\x04 \x03(\tR\x10securityContexts\x12\x1f\n" +
- "\vcgroup_path\x18\x05 \x01(\tR\n" +
- "cgroupPath\"\x89\x04\n" +
- "\rImageMetadata\x12%\n" +
- "\x0eoriginal_image\x18\x01 \x01(\tR\roriginalImage\x12!\n" +
- "\fimage_digest\x18\x02 \x01(\tR\vimageDigest\x12\x16\n" +
- "\x06layers\x18\x03 \x03(\tR\x06layers\x12>\n" +
- "\x06labels\x18\x04 \x03(\v2&.builderd.v1.ImageMetadata.LabelsEntryR\x06labels\x12\x18\n" +
- "\acommand\x18\x05 \x03(\tR\acommand\x12\x1e\n" +
- "\n" +
- "entrypoint\x18\x06 \x03(\tR\n" +
- "entrypoint\x12\x1f\n" +
- "\vworking_dir\x18\a \x01(\tR\n" +
- "workingDir\x125\n" +
- "\x03env\x18\b \x03(\v2#.builderd.v1.ImageMetadata.EnvEntryR\x03env\x12#\n" +
- "\rexposed_ports\x18\t \x03(\tR\fexposedPorts\x12\x12\n" +
- "\x04user\x18\n" +
- " \x01(\tR\x04user\x12\x18\n" +
- "\avolumes\x18\v \x03(\tR\avolumes\x1a9\n" +
- "\vLabelsEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a6\n" +
- "\bEnvEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xf7\x03\n" +
- "\fBuildMetrics\x12(\n" +
- "\x10pull_duration_ms\x18\x01 \x01(\x03R\x0epullDurationMs\x12.\n" +
- "\x13extract_duration_ms\x18\x02 \x01(\x03R\x11extractDurationMs\x12*\n" +
- "\x11build_duration_ms\x18\x03 \x01(\x03R\x0fbuildDurationMs\x120\n" +
- "\x14optimize_duration_ms\x18\x04 \x01(\x03R\x12optimizeDurationMs\x12*\n" +
- "\x11total_duration_ms\x18\x05 \x01(\x03R\x0ftotalDurationMs\x12.\n" +
- "\x13original_size_bytes\x18\x06 \x01(\x03R\x11originalSizeBytes\x12*\n" +
- "\x11rootfs_size_bytes\x18\a \x01(\x03R\x0frootfsSizeBytes\x12+\n" +
- "\x11compression_ratio\x18\b \x01(\x03R\x10compressionRatio\x12*\n" +
- "\x11memory_peak_bytes\x18\t \x01(\x03R\x0fmemoryPeakBytes\x12(\n" +
- "\x10disk_usage_bytes\x18\n" +
- " \x01(\x03R\x0ediskUsageBytes\x12$\n" +
- "\x0ecpu_cores_used\x18\v \x01(\x05R\fcpuCoresUsed\"\xf6\x05\n" +
- "\bBuildJob\x12\x19\n" +
- "\bbuild_id\x18\x01 \x01(\tR\abuildId\x120\n" +
- "\x06config\x18\x02 \x01(\v2\x18.builderd.v1.BuildConfigR\x06config\x12-\n" +
- "\x05state\x18\x03 \x01(\x0e2\x17.builderd.v1.BuildStateR\x05state\x129\n" +
- "\n" +
- "created_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" +
- "\n" +
- "started_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tstartedAt\x12=\n" +
- "\fcompleted_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\vcompletedAt\x12\x1f\n" +
- "\vrootfs_path\x18\a \x01(\tR\n" +
- "rootfsPath\x12*\n" +
- "\x11rootfs_size_bytes\x18\b \x01(\x03R\x0frootfsSizeBytes\x12'\n" +
- "\x0frootfs_checksum\x18\t \x01(\tR\x0erootfsChecksum\x12A\n" +
- "\x0eimage_metadata\x18\n" +
- " \x01(\v2\x1a.builderd.v1.ImageMetadataR\rimageMetadata\x123\n" +
- "\ametrics\x18\v \x01(\v2\x19.builderd.v1.BuildMetricsR\ametrics\x129\n" +
- "\tisolation\x18\f \x01(\v2\x1b.builderd.v1.BuildIsolationR\tisolation\x12#\n" +
- "\rerror_message\x18\r \x01(\tR\ferrorMessage\x12\x1d\n" +
- "\n" +
- "build_logs\x18\x0e \x03(\tR\tbuildLogs\x12)\n" +
- "\x10progress_percent\x18\x0f \x01(\x05R\x0fprogressPercent\x12!\n" +
- "\fcurrent_step\x18\x10 \x01(\tR\vcurrentStep\"\xae\x02\n" +
- "\x17StreamBuildLogsResponse\x128\n" +
- "\ttimestamp\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\x12\x14\n" +
- "\x05level\x18\x02 \x01(\tR\x05level\x12\x18\n" +
- "\amessage\x18\x03 \x01(\tR\amessage\x12\x1c\n" +
- "\tcomponent\x18\x04 \x01(\tR\tcomponent\x12N\n" +
- "\bmetadata\x18\x05 \x03(\v22.builderd.v1.StreamBuildLogsResponse.MetadataEntryR\bmetadata\x1a;\n" +
- "\rMetadataEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"F\n" +
- "\x12CreateBuildRequest\x120\n" +
- "\x06config\x18\x01 \x01(\v2\x18.builderd.v1.BuildConfigR\x06config\"\xbb\x01\n" +
- "\x13CreateBuildResponse\x12\x19\n" +
- "\bbuild_id\x18\x01 \x01(\tR\abuildId\x12-\n" +
- "\x05state\x18\x02 \x01(\x0e2\x17.builderd.v1.BuildStateR\x05state\x129\n" +
- "\n" +
- "created_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12\x1f\n" +
- "\vrootfs_path\x18\x04 \x01(\tR\n" +
- "rootfsPath\"I\n" +
- "\x0fGetBuildRequest\x12\x19\n" +
- "\bbuild_id\x18\x01 \x01(\tR\abuildId\x12\x1b\n" +
- "\ttenant_id\x18\x02 \x01(\tR\btenantId\"?\n" +
- "\x10GetBuildResponse\x12+\n" +
- "\x05build\x18\x01 \x01(\v2\x15.builderd.v1.BuildJobR\x05build\"\x8b\x01\n" +
- "\x11ListBuildsRequest\x12:\n" +
- "\fstate_filter\x18\x01 \x03(\x0e2\x17.builderd.v1.BuildStateR\vstateFilter\x12\x1b\n" +
- "\tpage_size\x18\x02 \x01(\x05R\bpageSize\x12\x1d\n" +
- "\n" +
- "page_token\x18\x03 \x01(\tR\tpageToken\"\x8c\x01\n" +
- "\x12ListBuildsResponse\x12-\n" +
- "\x06builds\x18\x01 \x03(\v2\x15.builderd.v1.BuildJobR\x06builds\x12&\n" +
- "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\x12\x1f\n" +
- "\vtotal_count\x18\x03 \x01(\x05R\n" +
- "totalCount\"/\n" +
- "\x12CancelBuildRequest\x12\x19\n" +
- "\bbuild_id\x18\x01 \x01(\tR\abuildId\"^\n" +
- "\x13CancelBuildResponse\x12\x18\n" +
- "\asuccess\x18\x01 \x01(\bR\asuccess\x12-\n" +
- "\x05state\x18\x02 \x01(\x0e2\x17.builderd.v1.BuildStateR\x05state\"E\n" +
- "\x12DeleteBuildRequest\x12\x19\n" +
- "\bbuild_id\x18\x01 \x01(\tR\abuildId\x12\x14\n" +
- "\x05force\x18\x02 \x01(\bR\x05force\"/\n" +
- "\x13DeleteBuildResponse\x12\x18\n" +
- "\asuccess\x18\x01 \x01(\bR\asuccess\"K\n" +
- "\x16StreamBuildLogsRequest\x12\x19\n" +
- "\bbuild_id\x18\x01 \x01(\tR\abuildId\x12\x16\n" +
- "\x06follow\x18\x02 \x01(\bR\x06follow\"\xa5\x01\n" +
- "\x14GetBuildStatsRequest\x12\x1b\n" +
- "\ttenant_id\x18\x01 \x01(\tR\btenantId\x129\n" +
- "\n" +
- "start_time\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\tstartTime\x125\n" +
- "\bend_time\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\aendTime\"\xd7\x02\n" +
- "\x15GetBuildStatsResponse\x12!\n" +
- "\ftotal_builds\x18\x01 \x01(\x05R\vtotalBuilds\x12+\n" +
- "\x11successful_builds\x18\x02 \x01(\x05R\x10successfulBuilds\x12#\n" +
- "\rfailed_builds\x18\x03 \x01(\x05R\ffailedBuilds\x12)\n" +
- "\x11avg_build_time_ms\x18\x04 \x01(\x03R\x0eavgBuildTimeMs\x12.\n" +
- "\x13total_storage_bytes\x18\x05 \x01(\x03R\x11totalStorageBytes\x122\n" +
- "\x15total_compute_minutes\x18\x06 \x01(\x03R\x13totalComputeMinutes\x12:\n" +
- "\rrecent_builds\x18\a \x03(\v2\x15.builderd.v1.BuildJobR\frecentBuilds*\x95\x02\n" +
- "\n" +
- "BuildState\x12\x1b\n" +
- "\x17BUILD_STATE_UNSPECIFIED\x10\x00\x12\x17\n" +
- "\x13BUILD_STATE_PENDING\x10\x01\x12\x17\n" +
- "\x13BUILD_STATE_PULLING\x10\x02\x12\x1a\n" +
- "\x16BUILD_STATE_EXTRACTING\x10\x03\x12\x18\n" +
- "\x14BUILD_STATE_BUILDING\x10\x04\x12\x1a\n" +
- "\x16BUILD_STATE_OPTIMIZING\x10\x05\x12\x19\n" +
- "\x15BUILD_STATE_COMPLETED\x10\x06\x12\x16\n" +
- "\x12BUILD_STATE_FAILED\x10\a\x12\x19\n" +
- "\x15BUILD_STATE_CANCELLED\x10\b\x12\x18\n" +
- "\x14BUILD_STATE_CLEANING\x10\t*\x8b\x01\n" +
- "\n" +
- "TenantTier\x12\x1b\n" +
- "\x17TENANT_TIER_UNSPECIFIED\x10\x00\x12\x14\n" +
- "\x10TENANT_TIER_FREE\x10\x01\x12\x13\n" +
- "\x0fTENANT_TIER_PRO\x10\x02\x12\x1a\n" +
- "\x16TENANT_TIER_ENTERPRISE\x10\x03\x12\x19\n" +
- "\x15TENANT_TIER_DEDICATED\x10\x04*y\n" +
- "\fInitStrategy\x12\x1d\n" +
- "\x19INIT_STRATEGY_UNSPECIFIED\x10\x00\x12\x16\n" +
- "\x12INIT_STRATEGY_TINI\x10\x01\x12\x18\n" +
- "\x14INIT_STRATEGY_DIRECT\x10\x02\x12\x18\n" +
- "\x14INIT_STRATEGY_CUSTOM\x10\x032\xd6\x04\n" +
- "\x0eBuilderService\x12P\n" +
- "\vCreateBuild\x12\x1f.builderd.v1.CreateBuildRequest\x1a .builderd.v1.CreateBuildResponse\x12G\n" +
- "\bGetBuild\x12\x1c.builderd.v1.GetBuildRequest\x1a\x1d.builderd.v1.GetBuildResponse\x12M\n" +
- "\n" +
- "ListBuilds\x12\x1e.builderd.v1.ListBuildsRequest\x1a\x1f.builderd.v1.ListBuildsResponse\x12P\n" +
- "\vCancelBuild\x12\x1f.builderd.v1.CancelBuildRequest\x1a .builderd.v1.CancelBuildResponse\x12P\n" +
- "\vDeleteBuild\x12\x1f.builderd.v1.DeleteBuildRequest\x1a .builderd.v1.DeleteBuildResponse\x12^\n" +
- "\x0fStreamBuildLogs\x12#.builderd.v1.StreamBuildLogsRequest\x1a$.builderd.v1.StreamBuildLogsResponse0\x01\x12V\n" +
- "\rGetBuildStats\x12!.builderd.v1.GetBuildStatsRequest\x1a\".builderd.v1.GetBuildStatsResponseB>Z builderd.v1.DockerImageSource
- 6, // 1: builderd.v1.BuildSource.git_repository:type_name -> builderd.v1.GitRepositorySource
- 8, // 2: builderd.v1.BuildSource.archive:type_name -> builderd.v1.ArchiveSource
- 5, // 3: builderd.v1.DockerImageSource.auth:type_name -> builderd.v1.DockerAuth
- 7, // 4: builderd.v1.GitRepositorySource.auth:type_name -> builderd.v1.GitAuth
- 10, // 5: builderd.v1.BuildTarget.microvm_rootfs:type_name -> builderd.v1.MicroVMRootfs
- 11, // 6: builderd.v1.BuildTarget.container_image:type_name -> builderd.v1.ContainerImage
- 2, // 7: builderd.v1.MicroVMRootfs.init_strategy:type_name -> builderd.v1.InitStrategy
- 12, // 8: builderd.v1.MicroVMRootfs.runtime_config:type_name -> builderd.v1.RuntimeConfig
- 13, // 9: builderd.v1.MicroVMRootfs.optimization:type_name -> builderd.v1.OptimizationSettings
- 38, // 10: builderd.v1.RuntimeConfig.environment:type_name -> builderd.v1.RuntimeConfig.EnvironmentEntry
- 15, // 11: builderd.v1.BuildStrategy.docker_extract:type_name -> builderd.v1.DockerExtractStrategy
- 16, // 12: builderd.v1.BuildStrategy.go_api:type_name -> builderd.v1.GoApiStrategy
- 17, // 13: builderd.v1.BuildStrategy.sinatra:type_name -> builderd.v1.SinatraStrategy
- 18, // 14: builderd.v1.BuildStrategy.nodejs:type_name -> builderd.v1.NodejsStrategy
- 39, // 15: builderd.v1.SinatraStrategy.rack_config:type_name -> builderd.v1.SinatraStrategy.RackConfigEntry
- 3, // 16: builderd.v1.BuildConfig.source:type_name -> builderd.v1.BuildSource
- 9, // 17: builderd.v1.BuildConfig.target:type_name -> builderd.v1.BuildTarget
- 14, // 18: builderd.v1.BuildConfig.strategy:type_name -> builderd.v1.BuildStrategy
- 40, // 19: builderd.v1.BuildConfig.labels:type_name -> builderd.v1.BuildConfig.LabelsEntry
- 41, // 20: builderd.v1.ImageMetadata.labels:type_name -> builderd.v1.ImageMetadata.LabelsEntry
- 42, // 21: builderd.v1.ImageMetadata.env:type_name -> builderd.v1.ImageMetadata.EnvEntry
- 19, // 22: builderd.v1.BuildJob.config:type_name -> builderd.v1.BuildConfig
- 0, // 23: builderd.v1.BuildJob.state:type_name -> builderd.v1.BuildState
- 44, // 24: builderd.v1.BuildJob.created_at:type_name -> google.protobuf.Timestamp
- 44, // 25: builderd.v1.BuildJob.started_at:type_name -> google.protobuf.Timestamp
- 44, // 26: builderd.v1.BuildJob.completed_at:type_name -> google.protobuf.Timestamp
- 21, // 27: builderd.v1.BuildJob.image_metadata:type_name -> builderd.v1.ImageMetadata
- 22, // 28: builderd.v1.BuildJob.metrics:type_name -> builderd.v1.BuildMetrics
- 20, // 29: builderd.v1.BuildJob.isolation:type_name -> builderd.v1.BuildIsolation
- 44, // 30: builderd.v1.StreamBuildLogsResponse.timestamp:type_name -> google.protobuf.Timestamp
- 43, // 31: builderd.v1.StreamBuildLogsResponse.metadata:type_name -> builderd.v1.StreamBuildLogsResponse.MetadataEntry
- 19, // 32: builderd.v1.CreateBuildRequest.config:type_name -> builderd.v1.BuildConfig
- 0, // 33: builderd.v1.CreateBuildResponse.state:type_name -> builderd.v1.BuildState
- 44, // 34: builderd.v1.CreateBuildResponse.created_at:type_name -> google.protobuf.Timestamp
- 23, // 35: builderd.v1.GetBuildResponse.build:type_name -> builderd.v1.BuildJob
- 0, // 36: builderd.v1.ListBuildsRequest.state_filter:type_name -> builderd.v1.BuildState
- 23, // 37: builderd.v1.ListBuildsResponse.builds:type_name -> builderd.v1.BuildJob
- 0, // 38: builderd.v1.CancelBuildResponse.state:type_name -> builderd.v1.BuildState
- 44, // 39: builderd.v1.GetBuildStatsRequest.start_time:type_name -> google.protobuf.Timestamp
- 44, // 40: builderd.v1.GetBuildStatsRequest.end_time:type_name -> google.protobuf.Timestamp
- 23, // 41: builderd.v1.GetBuildStatsResponse.recent_builds:type_name -> builderd.v1.BuildJob
- 25, // 42: builderd.v1.BuilderService.CreateBuild:input_type -> builderd.v1.CreateBuildRequest
- 27, // 43: builderd.v1.BuilderService.GetBuild:input_type -> builderd.v1.GetBuildRequest
- 29, // 44: builderd.v1.BuilderService.ListBuilds:input_type -> builderd.v1.ListBuildsRequest
- 31, // 45: builderd.v1.BuilderService.CancelBuild:input_type -> builderd.v1.CancelBuildRequest
- 33, // 46: builderd.v1.BuilderService.DeleteBuild:input_type -> builderd.v1.DeleteBuildRequest
- 35, // 47: builderd.v1.BuilderService.StreamBuildLogs:input_type -> builderd.v1.StreamBuildLogsRequest
- 36, // 48: builderd.v1.BuilderService.GetBuildStats:input_type -> builderd.v1.GetBuildStatsRequest
- 26, // 49: builderd.v1.BuilderService.CreateBuild:output_type -> builderd.v1.CreateBuildResponse
- 28, // 50: builderd.v1.BuilderService.GetBuild:output_type -> builderd.v1.GetBuildResponse
- 30, // 51: builderd.v1.BuilderService.ListBuilds:output_type -> builderd.v1.ListBuildsResponse
- 32, // 52: builderd.v1.BuilderService.CancelBuild:output_type -> builderd.v1.CancelBuildResponse
- 34, // 53: builderd.v1.BuilderService.DeleteBuild:output_type -> builderd.v1.DeleteBuildResponse
- 24, // 54: builderd.v1.BuilderService.StreamBuildLogs:output_type -> builderd.v1.StreamBuildLogsResponse
- 37, // 55: builderd.v1.BuilderService.GetBuildStats:output_type -> builderd.v1.GetBuildStatsResponse
- 49, // [49:56] is the sub-list for method output_type
- 42, // [42:49] is the sub-list for method input_type
- 42, // [42:42] is the sub-list for extension type_name
- 42, // [42:42] is the sub-list for extension extendee
- 0, // [0:42] is the sub-list for field type_name
-}
-
-func init() { file_builderd_v1_builder_proto_init() }
-func file_builderd_v1_builder_proto_init() {
- if File_builderd_v1_builder_proto != nil {
- return
- }
- file_builderd_v1_builder_proto_msgTypes[0].OneofWrappers = []any{
- (*BuildSource_DockerImage)(nil),
- (*BuildSource_GitRepository)(nil),
- (*BuildSource_Archive)(nil),
- }
- file_builderd_v1_builder_proto_msgTypes[6].OneofWrappers = []any{
- (*BuildTarget_MicrovmRootfs)(nil),
- (*BuildTarget_ContainerImage)(nil),
- }
- file_builderd_v1_builder_proto_msgTypes[11].OneofWrappers = []any{
- (*BuildStrategy_DockerExtract)(nil),
- (*BuildStrategy_GoApi)(nil),
- (*BuildStrategy_Sinatra)(nil),
- (*BuildStrategy_Nodejs)(nil),
- }
- type x struct{}
- out := protoimpl.TypeBuilder{
- File: protoimpl.DescBuilder{
- GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
- RawDescriptor: unsafe.Slice(unsafe.StringData(file_builderd_v1_builder_proto_rawDesc), len(file_builderd_v1_builder_proto_rawDesc)),
- NumEnums: 3,
- NumMessages: 41,
- NumExtensions: 0,
- NumServices: 1,
- },
- GoTypes: file_builderd_v1_builder_proto_goTypes,
- DependencyIndexes: file_builderd_v1_builder_proto_depIdxs,
- EnumInfos: file_builderd_v1_builder_proto_enumTypes,
- MessageInfos: file_builderd_v1_builder_proto_msgTypes,
- }.Build()
- File_builderd_v1_builder_proto = out.File
- file_builderd_v1_builder_proto_goTypes = nil
- file_builderd_v1_builder_proto_depIdxs = nil
-}
diff --git a/go/gen/proto/builderd/v1/builderdv1connect/builder.connect.go b/go/gen/proto/builderd/v1/builderdv1connect/builder.connect.go
deleted file mode 100644
index 1c910e6a57..0000000000
--- a/go/gen/proto/builderd/v1/builderdv1connect/builder.connect.go
+++ /dev/null
@@ -1,296 +0,0 @@
-// Code generated by protoc-gen-connect-go. DO NOT EDIT.
-//
-// Source: builderd/v1/builder.proto
-
-package builderdv1connect
-
-import (
- connect "connectrpc.com/connect"
- context "context"
- errors "errors"
- v1 "github.com/unkeyed/unkey/go/gen/proto/builderd/v1"
- http "net/http"
- strings "strings"
-)
-
-// This is a compile-time assertion to ensure that this generated file and the connect package are
-// compatible. If you get a compiler error that this constant is not defined, this code was
-// generated with a version of connect newer than the one compiled into your binary. You can fix the
-// problem by either regenerating this code with an older version of connect or updating the connect
-// version compiled into your binary.
-const _ = connect.IsAtLeastVersion1_13_0
-
-const (
- // BuilderServiceName is the fully-qualified name of the BuilderService service.
- BuilderServiceName = "builderd.v1.BuilderService"
-)
-
-// These constants are the fully-qualified names of the RPCs defined in this package. They're
-// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
-//
-// Note that these are different from the fully-qualified method names used by
-// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
-// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
-// period.
-const (
- // BuilderServiceCreateBuildProcedure is the fully-qualified name of the BuilderService's
- // CreateBuild RPC.
- BuilderServiceCreateBuildProcedure = "/builderd.v1.BuilderService/CreateBuild"
- // BuilderServiceGetBuildProcedure is the fully-qualified name of the BuilderService's GetBuild RPC.
- BuilderServiceGetBuildProcedure = "/builderd.v1.BuilderService/GetBuild"
- // BuilderServiceListBuildsProcedure is the fully-qualified name of the BuilderService's ListBuilds
- // RPC.
- BuilderServiceListBuildsProcedure = "/builderd.v1.BuilderService/ListBuilds"
- // BuilderServiceCancelBuildProcedure is the fully-qualified name of the BuilderService's
- // CancelBuild RPC.
- BuilderServiceCancelBuildProcedure = "/builderd.v1.BuilderService/CancelBuild"
- // BuilderServiceDeleteBuildProcedure is the fully-qualified name of the BuilderService's
- // DeleteBuild RPC.
- BuilderServiceDeleteBuildProcedure = "/builderd.v1.BuilderService/DeleteBuild"
- // BuilderServiceStreamBuildLogsProcedure is the fully-qualified name of the BuilderService's
- // StreamBuildLogs RPC.
- BuilderServiceStreamBuildLogsProcedure = "/builderd.v1.BuilderService/StreamBuildLogs"
- // BuilderServiceGetBuildStatsProcedure is the fully-qualified name of the BuilderService's
- // GetBuildStats RPC.
- BuilderServiceGetBuildStatsProcedure = "/builderd.v1.BuilderService/GetBuildStats"
-)
-
-// BuilderServiceClient is a client for the builderd.v1.BuilderService service.
-type BuilderServiceClient interface {
- // Create a new build job
- CreateBuild(context.Context, *connect.Request[v1.CreateBuildRequest]) (*connect.Response[v1.CreateBuildResponse], error)
- // Get build status and progress
- GetBuild(context.Context, *connect.Request[v1.GetBuildRequest]) (*connect.Response[v1.GetBuildResponse], error)
- // List builds with filtering (tenant-scoped)
- ListBuilds(context.Context, *connect.Request[v1.ListBuildsRequest]) (*connect.Response[v1.ListBuildsResponse], error)
- // Cancel a running build
- CancelBuild(context.Context, *connect.Request[v1.CancelBuildRequest]) (*connect.Response[v1.CancelBuildResponse], error)
- // Delete a build and its artifacts
- DeleteBuild(context.Context, *connect.Request[v1.DeleteBuildRequest]) (*connect.Response[v1.DeleteBuildResponse], error)
- // Stream build logs in real-time
- StreamBuildLogs(context.Context, *connect.Request[v1.StreamBuildLogsRequest]) (*connect.ServerStreamForClient[v1.StreamBuildLogsResponse], error)
- // Get build statistics
- GetBuildStats(context.Context, *connect.Request[v1.GetBuildStatsRequest]) (*connect.Response[v1.GetBuildStatsResponse], error)
-}
-
-// NewBuilderServiceClient constructs a client for the builderd.v1.BuilderService service. By
-// default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses,
-// and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the
-// connect.WithGRPC() or connect.WithGRPCWeb() options.
-//
-// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
-// http://api.acme.com or https://acme.com/grpc).
-func NewBuilderServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) BuilderServiceClient {
- baseURL = strings.TrimRight(baseURL, "/")
- builderServiceMethods := v1.File_builderd_v1_builder_proto.Services().ByName("BuilderService").Methods()
- return &builderServiceClient{
- createBuild: connect.NewClient[v1.CreateBuildRequest, v1.CreateBuildResponse](
- httpClient,
- baseURL+BuilderServiceCreateBuildProcedure,
- connect.WithSchema(builderServiceMethods.ByName("CreateBuild")),
- connect.WithClientOptions(opts...),
- ),
- getBuild: connect.NewClient[v1.GetBuildRequest, v1.GetBuildResponse](
- httpClient,
- baseURL+BuilderServiceGetBuildProcedure,
- connect.WithSchema(builderServiceMethods.ByName("GetBuild")),
- connect.WithClientOptions(opts...),
- ),
- listBuilds: connect.NewClient[v1.ListBuildsRequest, v1.ListBuildsResponse](
- httpClient,
- baseURL+BuilderServiceListBuildsProcedure,
- connect.WithSchema(builderServiceMethods.ByName("ListBuilds")),
- connect.WithClientOptions(opts...),
- ),
- cancelBuild: connect.NewClient[v1.CancelBuildRequest, v1.CancelBuildResponse](
- httpClient,
- baseURL+BuilderServiceCancelBuildProcedure,
- connect.WithSchema(builderServiceMethods.ByName("CancelBuild")),
- connect.WithClientOptions(opts...),
- ),
- deleteBuild: connect.NewClient[v1.DeleteBuildRequest, v1.DeleteBuildResponse](
- httpClient,
- baseURL+BuilderServiceDeleteBuildProcedure,
- connect.WithSchema(builderServiceMethods.ByName("DeleteBuild")),
- connect.WithClientOptions(opts...),
- ),
- streamBuildLogs: connect.NewClient[v1.StreamBuildLogsRequest, v1.StreamBuildLogsResponse](
- httpClient,
- baseURL+BuilderServiceStreamBuildLogsProcedure,
- connect.WithSchema(builderServiceMethods.ByName("StreamBuildLogs")),
- connect.WithClientOptions(opts...),
- ),
- getBuildStats: connect.NewClient[v1.GetBuildStatsRequest, v1.GetBuildStatsResponse](
- httpClient,
- baseURL+BuilderServiceGetBuildStatsProcedure,
- connect.WithSchema(builderServiceMethods.ByName("GetBuildStats")),
- connect.WithClientOptions(opts...),
- ),
- }
-}
-
-// builderServiceClient implements BuilderServiceClient.
-type builderServiceClient struct {
- createBuild *connect.Client[v1.CreateBuildRequest, v1.CreateBuildResponse]
- getBuild *connect.Client[v1.GetBuildRequest, v1.GetBuildResponse]
- listBuilds *connect.Client[v1.ListBuildsRequest, v1.ListBuildsResponse]
- cancelBuild *connect.Client[v1.CancelBuildRequest, v1.CancelBuildResponse]
- deleteBuild *connect.Client[v1.DeleteBuildRequest, v1.DeleteBuildResponse]
- streamBuildLogs *connect.Client[v1.StreamBuildLogsRequest, v1.StreamBuildLogsResponse]
- getBuildStats *connect.Client[v1.GetBuildStatsRequest, v1.GetBuildStatsResponse]
-}
-
-// CreateBuild calls builderd.v1.BuilderService.CreateBuild.
-func (c *builderServiceClient) CreateBuild(ctx context.Context, req *connect.Request[v1.CreateBuildRequest]) (*connect.Response[v1.CreateBuildResponse], error) {
- return c.createBuild.CallUnary(ctx, req)
-}
-
-// GetBuild calls builderd.v1.BuilderService.GetBuild.
-func (c *builderServiceClient) GetBuild(ctx context.Context, req *connect.Request[v1.GetBuildRequest]) (*connect.Response[v1.GetBuildResponse], error) {
- return c.getBuild.CallUnary(ctx, req)
-}
-
-// ListBuilds calls builderd.v1.BuilderService.ListBuilds.
-func (c *builderServiceClient) ListBuilds(ctx context.Context, req *connect.Request[v1.ListBuildsRequest]) (*connect.Response[v1.ListBuildsResponse], error) {
- return c.listBuilds.CallUnary(ctx, req)
-}
-
-// CancelBuild calls builderd.v1.BuilderService.CancelBuild.
-func (c *builderServiceClient) CancelBuild(ctx context.Context, req *connect.Request[v1.CancelBuildRequest]) (*connect.Response[v1.CancelBuildResponse], error) {
- return c.cancelBuild.CallUnary(ctx, req)
-}
-
-// DeleteBuild calls builderd.v1.BuilderService.DeleteBuild.
-func (c *builderServiceClient) DeleteBuild(ctx context.Context, req *connect.Request[v1.DeleteBuildRequest]) (*connect.Response[v1.DeleteBuildResponse], error) {
- return c.deleteBuild.CallUnary(ctx, req)
-}
-
-// StreamBuildLogs calls builderd.v1.BuilderService.StreamBuildLogs.
-func (c *builderServiceClient) StreamBuildLogs(ctx context.Context, req *connect.Request[v1.StreamBuildLogsRequest]) (*connect.ServerStreamForClient[v1.StreamBuildLogsResponse], error) {
- return c.streamBuildLogs.CallServerStream(ctx, req)
-}
-
-// GetBuildStats calls builderd.v1.BuilderService.GetBuildStats.
-func (c *builderServiceClient) GetBuildStats(ctx context.Context, req *connect.Request[v1.GetBuildStatsRequest]) (*connect.Response[v1.GetBuildStatsResponse], error) {
- return c.getBuildStats.CallUnary(ctx, req)
-}
-
-// BuilderServiceHandler is an implementation of the builderd.v1.BuilderService service.
-type BuilderServiceHandler interface {
- // Create a new build job
- CreateBuild(context.Context, *connect.Request[v1.CreateBuildRequest]) (*connect.Response[v1.CreateBuildResponse], error)
- // Get build status and progress
- GetBuild(context.Context, *connect.Request[v1.GetBuildRequest]) (*connect.Response[v1.GetBuildResponse], error)
- // List builds with filtering (tenant-scoped)
- ListBuilds(context.Context, *connect.Request[v1.ListBuildsRequest]) (*connect.Response[v1.ListBuildsResponse], error)
- // Cancel a running build
- CancelBuild(context.Context, *connect.Request[v1.CancelBuildRequest]) (*connect.Response[v1.CancelBuildResponse], error)
- // Delete a build and its artifacts
- DeleteBuild(context.Context, *connect.Request[v1.DeleteBuildRequest]) (*connect.Response[v1.DeleteBuildResponse], error)
- // Stream build logs in real-time
- StreamBuildLogs(context.Context, *connect.Request[v1.StreamBuildLogsRequest], *connect.ServerStream[v1.StreamBuildLogsResponse]) error
- // Get build statistics
- GetBuildStats(context.Context, *connect.Request[v1.GetBuildStatsRequest]) (*connect.Response[v1.GetBuildStatsResponse], error)
-}
-
-// NewBuilderServiceHandler builds an HTTP handler from the service implementation. It returns the
-// path on which to mount the handler and the handler itself.
-//
-// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
-// and JSON codecs. They also support gzip compression.
-func NewBuilderServiceHandler(svc BuilderServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
- builderServiceMethods := v1.File_builderd_v1_builder_proto.Services().ByName("BuilderService").Methods()
- builderServiceCreateBuildHandler := connect.NewUnaryHandler(
- BuilderServiceCreateBuildProcedure,
- svc.CreateBuild,
- connect.WithSchema(builderServiceMethods.ByName("CreateBuild")),
- connect.WithHandlerOptions(opts...),
- )
- builderServiceGetBuildHandler := connect.NewUnaryHandler(
- BuilderServiceGetBuildProcedure,
- svc.GetBuild,
- connect.WithSchema(builderServiceMethods.ByName("GetBuild")),
- connect.WithHandlerOptions(opts...),
- )
- builderServiceListBuildsHandler := connect.NewUnaryHandler(
- BuilderServiceListBuildsProcedure,
- svc.ListBuilds,
- connect.WithSchema(builderServiceMethods.ByName("ListBuilds")),
- connect.WithHandlerOptions(opts...),
- )
- builderServiceCancelBuildHandler := connect.NewUnaryHandler(
- BuilderServiceCancelBuildProcedure,
- svc.CancelBuild,
- connect.WithSchema(builderServiceMethods.ByName("CancelBuild")),
- connect.WithHandlerOptions(opts...),
- )
- builderServiceDeleteBuildHandler := connect.NewUnaryHandler(
- BuilderServiceDeleteBuildProcedure,
- svc.DeleteBuild,
- connect.WithSchema(builderServiceMethods.ByName("DeleteBuild")),
- connect.WithHandlerOptions(opts...),
- )
- builderServiceStreamBuildLogsHandler := connect.NewServerStreamHandler(
- BuilderServiceStreamBuildLogsProcedure,
- svc.StreamBuildLogs,
- connect.WithSchema(builderServiceMethods.ByName("StreamBuildLogs")),
- connect.WithHandlerOptions(opts...),
- )
- builderServiceGetBuildStatsHandler := connect.NewUnaryHandler(
- BuilderServiceGetBuildStatsProcedure,
- svc.GetBuildStats,
- connect.WithSchema(builderServiceMethods.ByName("GetBuildStats")),
- connect.WithHandlerOptions(opts...),
- )
- return "/builderd.v1.BuilderService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- switch r.URL.Path {
- case BuilderServiceCreateBuildProcedure:
- builderServiceCreateBuildHandler.ServeHTTP(w, r)
- case BuilderServiceGetBuildProcedure:
- builderServiceGetBuildHandler.ServeHTTP(w, r)
- case BuilderServiceListBuildsProcedure:
- builderServiceListBuildsHandler.ServeHTTP(w, r)
- case BuilderServiceCancelBuildProcedure:
- builderServiceCancelBuildHandler.ServeHTTP(w, r)
- case BuilderServiceDeleteBuildProcedure:
- builderServiceDeleteBuildHandler.ServeHTTP(w, r)
- case BuilderServiceStreamBuildLogsProcedure:
- builderServiceStreamBuildLogsHandler.ServeHTTP(w, r)
- case BuilderServiceGetBuildStatsProcedure:
- builderServiceGetBuildStatsHandler.ServeHTTP(w, r)
- default:
- http.NotFound(w, r)
- }
- })
-}
-
-// UnimplementedBuilderServiceHandler returns CodeUnimplemented from all methods.
-type UnimplementedBuilderServiceHandler struct{}
-
-func (UnimplementedBuilderServiceHandler) CreateBuild(context.Context, *connect.Request[v1.CreateBuildRequest]) (*connect.Response[v1.CreateBuildResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("builderd.v1.BuilderService.CreateBuild is not implemented"))
-}
-
-func (UnimplementedBuilderServiceHandler) GetBuild(context.Context, *connect.Request[v1.GetBuildRequest]) (*connect.Response[v1.GetBuildResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("builderd.v1.BuilderService.GetBuild is not implemented"))
-}
-
-func (UnimplementedBuilderServiceHandler) ListBuilds(context.Context, *connect.Request[v1.ListBuildsRequest]) (*connect.Response[v1.ListBuildsResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("builderd.v1.BuilderService.ListBuilds is not implemented"))
-}
-
-func (UnimplementedBuilderServiceHandler) CancelBuild(context.Context, *connect.Request[v1.CancelBuildRequest]) (*connect.Response[v1.CancelBuildResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("builderd.v1.BuilderService.CancelBuild is not implemented"))
-}
-
-func (UnimplementedBuilderServiceHandler) DeleteBuild(context.Context, *connect.Request[v1.DeleteBuildRequest]) (*connect.Response[v1.DeleteBuildResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("builderd.v1.BuilderService.DeleteBuild is not implemented"))
-}
-
-func (UnimplementedBuilderServiceHandler) StreamBuildLogs(context.Context, *connect.Request[v1.StreamBuildLogsRequest], *connect.ServerStream[v1.StreamBuildLogsResponse]) error {
- return connect.NewError(connect.CodeUnimplemented, errors.New("builderd.v1.BuilderService.StreamBuildLogs is not implemented"))
-}
-
-func (UnimplementedBuilderServiceHandler) GetBuildStats(context.Context, *connect.Request[v1.GetBuildStatsRequest]) (*connect.Response[v1.GetBuildStatsResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("builderd.v1.BuilderService.GetBuildStats is not implemented"))
-}
diff --git a/go/gen/proto/krane/v1/deployment.pb.go b/go/gen/proto/krane/v1/deployment.pb.go
new file mode 100644
index 0000000000..32577a5f5e
--- /dev/null
+++ b/go/gen/proto/krane/v1/deployment.pb.go
@@ -0,0 +1,696 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.36.8
+// protoc (unknown)
+// source: krane/v1/deployment.proto
+
+package kranev1
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+ unsafe "unsafe"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type DeploymentStatus int32
+
+const (
+ DeploymentStatus_DEPLOYMENT_STATUS_UNSPECIFIED DeploymentStatus = 0
+ DeploymentStatus_DEPLOYMENT_STATUS_PENDING DeploymentStatus = 1 // Deployment request accepted, container/pod creation in progress
+ DeploymentStatus_DEPLOYMENT_STATUS_RUNNING DeploymentStatus = 2 // Container/pod is running and healthy
+ DeploymentStatus_DEPLOYMENT_STATUS_TERMINATING DeploymentStatus = 3 // Container/pod is being terminated
+)
+
+// Enum value maps for DeploymentStatus.
+var (
+ DeploymentStatus_name = map[int32]string{
+ 0: "DEPLOYMENT_STATUS_UNSPECIFIED",
+ 1: "DEPLOYMENT_STATUS_PENDING",
+ 2: "DEPLOYMENT_STATUS_RUNNING",
+ 3: "DEPLOYMENT_STATUS_TERMINATING",
+ }
+ DeploymentStatus_value = map[string]int32{
+ "DEPLOYMENT_STATUS_UNSPECIFIED": 0,
+ "DEPLOYMENT_STATUS_PENDING": 1,
+ "DEPLOYMENT_STATUS_RUNNING": 2,
+ "DEPLOYMENT_STATUS_TERMINATING": 3,
+ }
+)
+
+func (x DeploymentStatus) Enum() *DeploymentStatus {
+ p := new(DeploymentStatus)
+ *p = x
+ return p
+}
+
+func (x DeploymentStatus) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (DeploymentStatus) Descriptor() protoreflect.EnumDescriptor {
+ return file_krane_v1_deployment_proto_enumTypes[0].Descriptor()
+}
+
+func (DeploymentStatus) Type() protoreflect.EnumType {
+ return &file_krane_v1_deployment_proto_enumTypes[0]
+}
+
+func (x DeploymentStatus) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use DeploymentStatus.Descriptor instead.
+func (DeploymentStatus) EnumDescriptor() ([]byte, []int) {
+ return file_krane_v1_deployment_proto_rawDescGZIP(), []int{0}
+}
+
+type DeploymentRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
+ DeploymentId string `protobuf:"bytes,2,opt,name=deployment_id,json=deploymentId,proto3" json:"deployment_id,omitempty"`
+ Image string `protobuf:"bytes,3,opt,name=image,proto3" json:"image,omitempty"`
+ Replicas uint32 `protobuf:"varint,4,opt,name=replicas,proto3" json:"replicas,omitempty"`
+ CpuMillicores uint32 `protobuf:"varint,5,opt,name=cpu_millicores,json=cpuMillicores,proto3" json:"cpu_millicores,omitempty"`
+ MemorySizeMib uint64 `protobuf:"varint,6,opt,name=memory_size_mib,json=memorySizeMib,proto3" json:"memory_size_mib,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *DeploymentRequest) Reset() {
+ *x = DeploymentRequest{}
+ mi := &file_krane_v1_deployment_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *DeploymentRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeploymentRequest) ProtoMessage() {}
+
+func (x *DeploymentRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_krane_v1_deployment_proto_msgTypes[0]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DeploymentRequest.ProtoReflect.Descriptor instead.
+func (*DeploymentRequest) Descriptor() ([]byte, []int) {
+ return file_krane_v1_deployment_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *DeploymentRequest) GetNamespace() string {
+ if x != nil {
+ return x.Namespace
+ }
+ return ""
+}
+
+func (x *DeploymentRequest) GetDeploymentId() string {
+ if x != nil {
+ return x.DeploymentId
+ }
+ return ""
+}
+
+func (x *DeploymentRequest) GetImage() string {
+ if x != nil {
+ return x.Image
+ }
+ return ""
+}
+
+func (x *DeploymentRequest) GetReplicas() uint32 {
+ if x != nil {
+ return x.Replicas
+ }
+ return 0
+}
+
+func (x *DeploymentRequest) GetCpuMillicores() uint32 {
+ if x != nil {
+ return x.CpuMillicores
+ }
+ return 0
+}
+
+func (x *DeploymentRequest) GetMemorySizeMib() uint64 {
+ if x != nil {
+ return x.MemorySizeMib
+ }
+ return 0
+}
+
+type CreateDeploymentRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Deployment *DeploymentRequest `protobuf:"bytes,1,opt,name=deployment,proto3" json:"deployment,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *CreateDeploymentRequest) Reset() {
+ *x = CreateDeploymentRequest{}
+ mi := &file_krane_v1_deployment_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *CreateDeploymentRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CreateDeploymentRequest) ProtoMessage() {}
+
+func (x *CreateDeploymentRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_krane_v1_deployment_proto_msgTypes[1]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CreateDeploymentRequest.ProtoReflect.Descriptor instead.
+func (*CreateDeploymentRequest) Descriptor() ([]byte, []int) {
+ return file_krane_v1_deployment_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *CreateDeploymentRequest) GetDeployment() *DeploymentRequest {
+ if x != nil {
+ return x.Deployment
+ }
+ return nil
+}
+
+type CreateDeploymentResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Status DeploymentStatus `protobuf:"varint,1,opt,name=status,proto3,enum=krane.v1.DeploymentStatus" json:"status,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *CreateDeploymentResponse) Reset() {
+ *x = CreateDeploymentResponse{}
+ mi := &file_krane_v1_deployment_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *CreateDeploymentResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CreateDeploymentResponse) ProtoMessage() {}
+
+func (x *CreateDeploymentResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_krane_v1_deployment_proto_msgTypes[2]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CreateDeploymentResponse.ProtoReflect.Descriptor instead.
+func (*CreateDeploymentResponse) Descriptor() ([]byte, []int) {
+ return file_krane_v1_deployment_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *CreateDeploymentResponse) GetStatus() DeploymentStatus {
+ if x != nil {
+ return x.Status
+ }
+ return DeploymentStatus_DEPLOYMENT_STATUS_UNSPECIFIED
+}
+
+type UpdateDeploymentRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Deployment *DeploymentRequest `protobuf:"bytes,1,opt,name=deployment,proto3" json:"deployment,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *UpdateDeploymentRequest) Reset() {
+ *x = UpdateDeploymentRequest{}
+ mi := &file_krane_v1_deployment_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *UpdateDeploymentRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UpdateDeploymentRequest) ProtoMessage() {}
+
+func (x *UpdateDeploymentRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_krane_v1_deployment_proto_msgTypes[3]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use UpdateDeploymentRequest.ProtoReflect.Descriptor instead.
+func (*UpdateDeploymentRequest) Descriptor() ([]byte, []int) {
+ return file_krane_v1_deployment_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *UpdateDeploymentRequest) GetDeployment() *DeploymentRequest {
+ if x != nil {
+ return x.Deployment
+ }
+ return nil
+}
+
+type UpdateDeploymentResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ PodIds []string `protobuf:"bytes,1,rep,name=pod_ids,json=podIds,proto3" json:"pod_ids,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *UpdateDeploymentResponse) Reset() {
+ *x = UpdateDeploymentResponse{}
+ mi := &file_krane_v1_deployment_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *UpdateDeploymentResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UpdateDeploymentResponse) ProtoMessage() {}
+
+func (x *UpdateDeploymentResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_krane_v1_deployment_proto_msgTypes[4]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use UpdateDeploymentResponse.ProtoReflect.Descriptor instead.
+func (*UpdateDeploymentResponse) Descriptor() ([]byte, []int) {
+ return file_krane_v1_deployment_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *UpdateDeploymentResponse) GetPodIds() []string {
+ if x != nil {
+ return x.PodIds
+ }
+ return nil
+}
+
+type DeleteDeploymentRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
+ DeploymentId string `protobuf:"bytes,2,opt,name=deployment_id,json=deploymentId,proto3" json:"deployment_id,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *DeleteDeploymentRequest) Reset() {
+ *x = DeleteDeploymentRequest{}
+ mi := &file_krane_v1_deployment_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *DeleteDeploymentRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeleteDeploymentRequest) ProtoMessage() {}
+
+func (x *DeleteDeploymentRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_krane_v1_deployment_proto_msgTypes[5]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DeleteDeploymentRequest.ProtoReflect.Descriptor instead.
+func (*DeleteDeploymentRequest) Descriptor() ([]byte, []int) {
+ return file_krane_v1_deployment_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *DeleteDeploymentRequest) GetNamespace() string {
+ if x != nil {
+ return x.Namespace
+ }
+ return ""
+}
+
+func (x *DeleteDeploymentRequest) GetDeploymentId() string {
+ if x != nil {
+ return x.DeploymentId
+ }
+ return ""
+}
+
+type DeleteDeploymentResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *DeleteDeploymentResponse) Reset() {
+ *x = DeleteDeploymentResponse{}
+ mi := &file_krane_v1_deployment_proto_msgTypes[6]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *DeleteDeploymentResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeleteDeploymentResponse) ProtoMessage() {}
+
+func (x *DeleteDeploymentResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_krane_v1_deployment_proto_msgTypes[6]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DeleteDeploymentResponse.ProtoReflect.Descriptor instead.
+func (*DeleteDeploymentResponse) Descriptor() ([]byte, []int) {
+ return file_krane_v1_deployment_proto_rawDescGZIP(), []int{6}
+}
+
+type GetDeploymentRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
+ DeploymentId string `protobuf:"bytes,2,opt,name=deployment_id,json=deploymentId,proto3" json:"deployment_id,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *GetDeploymentRequest) Reset() {
+ *x = GetDeploymentRequest{}
+ mi := &file_krane_v1_deployment_proto_msgTypes[7]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *GetDeploymentRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetDeploymentRequest) ProtoMessage() {}
+
+func (x *GetDeploymentRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_krane_v1_deployment_proto_msgTypes[7]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetDeploymentRequest.ProtoReflect.Descriptor instead.
+func (*GetDeploymentRequest) Descriptor() ([]byte, []int) {
+ return file_krane_v1_deployment_proto_rawDescGZIP(), []int{7}
+}
+
+func (x *GetDeploymentRequest) GetNamespace() string {
+ if x != nil {
+ return x.Namespace
+ }
+ return ""
+}
+
+func (x *GetDeploymentRequest) GetDeploymentId() string {
+ if x != nil {
+ return x.DeploymentId
+ }
+ return ""
+}
+
+type GetDeploymentResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Instances []*Instance `protobuf:"bytes,2,rep,name=instances,proto3" json:"instances,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *GetDeploymentResponse) Reset() {
+ *x = GetDeploymentResponse{}
+ mi := &file_krane_v1_deployment_proto_msgTypes[8]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *GetDeploymentResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetDeploymentResponse) ProtoMessage() {}
+
+func (x *GetDeploymentResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_krane_v1_deployment_proto_msgTypes[8]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetDeploymentResponse.ProtoReflect.Descriptor instead.
+func (*GetDeploymentResponse) Descriptor() ([]byte, []int) {
+ return file_krane_v1_deployment_proto_rawDescGZIP(), []int{8}
+}
+
+func (x *GetDeploymentResponse) GetInstances() []*Instance {
+ if x != nil {
+ return x.Instances
+ }
+ return nil
+}
+
+type Instance struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+ Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"`
+ Status DeploymentStatus `protobuf:"varint,3,opt,name=status,proto3,enum=krane.v1.DeploymentStatus" json:"status,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Instance) Reset() {
+ *x = Instance{}
+ mi := &file_krane_v1_deployment_proto_msgTypes[9]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Instance) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Instance) ProtoMessage() {}
+
+func (x *Instance) ProtoReflect() protoreflect.Message {
+ mi := &file_krane_v1_deployment_proto_msgTypes[9]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Instance.ProtoReflect.Descriptor instead.
+func (*Instance) Descriptor() ([]byte, []int) {
+ return file_krane_v1_deployment_proto_rawDescGZIP(), []int{9}
+}
+
+func (x *Instance) GetId() string {
+ if x != nil {
+ return x.Id
+ }
+ return ""
+}
+
+func (x *Instance) GetAddress() string {
+ if x != nil {
+ return x.Address
+ }
+ return ""
+}
+
+func (x *Instance) GetStatus() DeploymentStatus {
+ if x != nil {
+ return x.Status
+ }
+ return DeploymentStatus_DEPLOYMENT_STATUS_UNSPECIFIED
+}
+
+var File_krane_v1_deployment_proto protoreflect.FileDescriptor
+
+const file_krane_v1_deployment_proto_rawDesc = "" +
+ "\n" +
+ "\x19krane/v1/deployment.proto\x12\bkrane.v1\"\xd7\x01\n" +
+ "\x11DeploymentRequest\x12\x1c\n" +
+ "\tnamespace\x18\x01 \x01(\tR\tnamespace\x12#\n" +
+ "\rdeployment_id\x18\x02 \x01(\tR\fdeploymentId\x12\x14\n" +
+ "\x05image\x18\x03 \x01(\tR\x05image\x12\x1a\n" +
+ "\breplicas\x18\x04 \x01(\rR\breplicas\x12%\n" +
+ "\x0ecpu_millicores\x18\x05 \x01(\rR\rcpuMillicores\x12&\n" +
+ "\x0fmemory_size_mib\x18\x06 \x01(\x04R\rmemorySizeMib\"V\n" +
+ "\x17CreateDeploymentRequest\x12;\n" +
+ "\n" +
+ "deployment\x18\x01 \x01(\v2\x1b.krane.v1.DeploymentRequestR\n" +
+ "deployment\"N\n" +
+ "\x18CreateDeploymentResponse\x122\n" +
+ "\x06status\x18\x01 \x01(\x0e2\x1a.krane.v1.DeploymentStatusR\x06status\"V\n" +
+ "\x17UpdateDeploymentRequest\x12;\n" +
+ "\n" +
+ "deployment\x18\x01 \x01(\v2\x1b.krane.v1.DeploymentRequestR\n" +
+ "deployment\"3\n" +
+ "\x18UpdateDeploymentResponse\x12\x17\n" +
+ "\apod_ids\x18\x01 \x03(\tR\x06podIds\"\\\n" +
+ "\x17DeleteDeploymentRequest\x12\x1c\n" +
+ "\tnamespace\x18\x01 \x01(\tR\tnamespace\x12#\n" +
+ "\rdeployment_id\x18\x02 \x01(\tR\fdeploymentId\"\x1a\n" +
+ "\x18DeleteDeploymentResponse\"Y\n" +
+ "\x14GetDeploymentRequest\x12\x1c\n" +
+ "\tnamespace\x18\x01 \x01(\tR\tnamespace\x12#\n" +
+ "\rdeployment_id\x18\x02 \x01(\tR\fdeploymentId\"I\n" +
+ "\x15GetDeploymentResponse\x120\n" +
+ "\tinstances\x18\x02 \x03(\v2\x12.krane.v1.InstanceR\tinstances\"h\n" +
+ "\bInstance\x12\x0e\n" +
+ "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" +
+ "\aaddress\x18\x02 \x01(\tR\aaddress\x122\n" +
+ "\x06status\x18\x03 \x01(\x0e2\x1a.krane.v1.DeploymentStatusR\x06status*\x96\x01\n" +
+ "\x10DeploymentStatus\x12!\n" +
+ "\x1dDEPLOYMENT_STATUS_UNSPECIFIED\x10\x00\x12\x1d\n" +
+ "\x19DEPLOYMENT_STATUS_PENDING\x10\x01\x12\x1d\n" +
+ "\x19DEPLOYMENT_STATUS_RUNNING\x10\x02\x12!\n" +
+ "\x1dDEPLOYMENT_STATUS_TERMINATING\x10\x032\x9b\x02\n" +
+ "\x11DeploymentService\x12Y\n" +
+ "\x10CreateDeployment\x12!.krane.v1.CreateDeploymentRequest\x1a\".krane.v1.CreateDeploymentResponse\x12P\n" +
+ "\rGetDeployment\x12\x1e.krane.v1.GetDeploymentRequest\x1a\x1f.krane.v1.GetDeploymentResponse\x12Y\n" +
+ "\x10DeleteDeployment\x12!.krane.v1.DeleteDeploymentRequest\x1a\".krane.v1.DeleteDeploymentResponseB8Z6github.com/unkeyed/unkey/go/gen/proto/krane/v1;kranev1b\x06proto3"
+
+var (
+ file_krane_v1_deployment_proto_rawDescOnce sync.Once
+ file_krane_v1_deployment_proto_rawDescData []byte
+)
+
+func file_krane_v1_deployment_proto_rawDescGZIP() []byte {
+ file_krane_v1_deployment_proto_rawDescOnce.Do(func() {
+ file_krane_v1_deployment_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_krane_v1_deployment_proto_rawDesc), len(file_krane_v1_deployment_proto_rawDesc)))
+ })
+ return file_krane_v1_deployment_proto_rawDescData
+}
+
+var file_krane_v1_deployment_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_krane_v1_deployment_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
+var file_krane_v1_deployment_proto_goTypes = []any{
+ (DeploymentStatus)(0), // 0: krane.v1.DeploymentStatus
+ (*DeploymentRequest)(nil), // 1: krane.v1.DeploymentRequest
+ (*CreateDeploymentRequest)(nil), // 2: krane.v1.CreateDeploymentRequest
+ (*CreateDeploymentResponse)(nil), // 3: krane.v1.CreateDeploymentResponse
+ (*UpdateDeploymentRequest)(nil), // 4: krane.v1.UpdateDeploymentRequest
+ (*UpdateDeploymentResponse)(nil), // 5: krane.v1.UpdateDeploymentResponse
+ (*DeleteDeploymentRequest)(nil), // 6: krane.v1.DeleteDeploymentRequest
+ (*DeleteDeploymentResponse)(nil), // 7: krane.v1.DeleteDeploymentResponse
+ (*GetDeploymentRequest)(nil), // 8: krane.v1.GetDeploymentRequest
+ (*GetDeploymentResponse)(nil), // 9: krane.v1.GetDeploymentResponse
+ (*Instance)(nil), // 10: krane.v1.Instance
+}
+var file_krane_v1_deployment_proto_depIdxs = []int32{
+ 1, // 0: krane.v1.CreateDeploymentRequest.deployment:type_name -> krane.v1.DeploymentRequest
+ 0, // 1: krane.v1.CreateDeploymentResponse.status:type_name -> krane.v1.DeploymentStatus
+ 1, // 2: krane.v1.UpdateDeploymentRequest.deployment:type_name -> krane.v1.DeploymentRequest
+ 10, // 3: krane.v1.GetDeploymentResponse.instances:type_name -> krane.v1.Instance
+ 0, // 4: krane.v1.Instance.status:type_name -> krane.v1.DeploymentStatus
+ 2, // 5: krane.v1.DeploymentService.CreateDeployment:input_type -> krane.v1.CreateDeploymentRequest
+ 8, // 6: krane.v1.DeploymentService.GetDeployment:input_type -> krane.v1.GetDeploymentRequest
+ 6, // 7: krane.v1.DeploymentService.DeleteDeployment:input_type -> krane.v1.DeleteDeploymentRequest
+ 3, // 8: krane.v1.DeploymentService.CreateDeployment:output_type -> krane.v1.CreateDeploymentResponse
+ 9, // 9: krane.v1.DeploymentService.GetDeployment:output_type -> krane.v1.GetDeploymentResponse
+ 7, // 10: krane.v1.DeploymentService.DeleteDeployment:output_type -> krane.v1.DeleteDeploymentResponse
+ 8, // [8:11] is the sub-list for method output_type
+ 5, // [5:8] is the sub-list for method input_type
+ 5, // [5:5] is the sub-list for extension type_name
+ 5, // [5:5] is the sub-list for extension extendee
+ 0, // [0:5] is the sub-list for field type_name
+}
+
+func init() { file_krane_v1_deployment_proto_init() }
+func file_krane_v1_deployment_proto_init() {
+ if File_krane_v1_deployment_proto != nil {
+ return
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: unsafe.Slice(unsafe.StringData(file_krane_v1_deployment_proto_rawDesc), len(file_krane_v1_deployment_proto_rawDesc)),
+ NumEnums: 1,
+ NumMessages: 10,
+ NumExtensions: 0,
+ NumServices: 1,
+ },
+ GoTypes: file_krane_v1_deployment_proto_goTypes,
+ DependencyIndexes: file_krane_v1_deployment_proto_depIdxs,
+ EnumInfos: file_krane_v1_deployment_proto_enumTypes,
+ MessageInfos: file_krane_v1_deployment_proto_msgTypes,
+ }.Build()
+ File_krane_v1_deployment_proto = out.File
+ file_krane_v1_deployment_proto_goTypes = nil
+ file_krane_v1_deployment_proto_depIdxs = nil
+}
diff --git a/go/gen/proto/krane/v1/kranev1connect/deployment.connect.go b/go/gen/proto/krane/v1/kranev1connect/deployment.connect.go
new file mode 100644
index 0000000000..f43fb89d72
--- /dev/null
+++ b/go/gen/proto/krane/v1/kranev1connect/deployment.connect.go
@@ -0,0 +1,173 @@
+// Code generated by protoc-gen-connect-go. DO NOT EDIT.
+//
+// Source: krane/v1/deployment.proto
+
+package kranev1connect
+
+import (
+ connect "connectrpc.com/connect"
+ context "context"
+ errors "errors"
+ v1 "github.com/unkeyed/unkey/go/gen/proto/krane/v1"
+ http "net/http"
+ strings "strings"
+)
+
+// This is a compile-time assertion to ensure that this generated file and the connect package are
+// compatible. If you get a compiler error that this constant is not defined, this code was
+// generated with a version of connect newer than the one compiled into your binary. You can fix the
+// problem by either regenerating this code with an older version of connect or updating the connect
+// version compiled into your binary.
+const _ = connect.IsAtLeastVersion1_13_0
+
+const (
+ // DeploymentServiceName is the fully-qualified name of the DeploymentService service.
+ DeploymentServiceName = "krane.v1.DeploymentService"
+)
+
+// These constants are the fully-qualified names of the RPCs defined in this package. They're
+// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
+//
+// Note that these are different from the fully-qualified method names used by
+// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
+// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
+// period.
+const (
+ // DeploymentServiceCreateDeploymentProcedure is the fully-qualified name of the DeploymentService's
+ // CreateDeployment RPC.
+ DeploymentServiceCreateDeploymentProcedure = "/krane.v1.DeploymentService/CreateDeployment"
+ // DeploymentServiceGetDeploymentProcedure is the fully-qualified name of the DeploymentService's
+ // GetDeployment RPC.
+ DeploymentServiceGetDeploymentProcedure = "/krane.v1.DeploymentService/GetDeployment"
+ // DeploymentServiceDeleteDeploymentProcedure is the fully-qualified name of the DeploymentService's
+ // DeleteDeployment RPC.
+ DeploymentServiceDeleteDeploymentProcedure = "/krane.v1.DeploymentService/DeleteDeployment"
+)
+
+// DeploymentServiceClient is a client for the krane.v1.DeploymentService service.
+type DeploymentServiceClient interface {
+ // CreateDeployment
+ CreateDeployment(context.Context, *connect.Request[v1.CreateDeploymentRequest]) (*connect.Response[v1.CreateDeploymentResponse], error)
+ // GetDeployment
+ GetDeployment(context.Context, *connect.Request[v1.GetDeploymentRequest]) (*connect.Response[v1.GetDeploymentResponse], error)
+ // DeleteDeployment
+ DeleteDeployment(context.Context, *connect.Request[v1.DeleteDeploymentRequest]) (*connect.Response[v1.DeleteDeploymentResponse], error)
+}
+
+// NewDeploymentServiceClient constructs a client for the krane.v1.DeploymentService service. By
+// default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses,
+// and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the
+// connect.WithGRPC() or connect.WithGRPCWeb() options.
+//
+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
+// http://api.acme.com or https://acme.com/grpc).
+func NewDeploymentServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) DeploymentServiceClient {
+ baseURL = strings.TrimRight(baseURL, "/")
+ deploymentServiceMethods := v1.File_krane_v1_deployment_proto.Services().ByName("DeploymentService").Methods()
+ return &deploymentServiceClient{
+ createDeployment: connect.NewClient[v1.CreateDeploymentRequest, v1.CreateDeploymentResponse](
+ httpClient,
+ baseURL+DeploymentServiceCreateDeploymentProcedure,
+ connect.WithSchema(deploymentServiceMethods.ByName("CreateDeployment")),
+ connect.WithClientOptions(opts...),
+ ),
+ getDeployment: connect.NewClient[v1.GetDeploymentRequest, v1.GetDeploymentResponse](
+ httpClient,
+ baseURL+DeploymentServiceGetDeploymentProcedure,
+ connect.WithSchema(deploymentServiceMethods.ByName("GetDeployment")),
+ connect.WithClientOptions(opts...),
+ ),
+ deleteDeployment: connect.NewClient[v1.DeleteDeploymentRequest, v1.DeleteDeploymentResponse](
+ httpClient,
+ baseURL+DeploymentServiceDeleteDeploymentProcedure,
+ connect.WithSchema(deploymentServiceMethods.ByName("DeleteDeployment")),
+ connect.WithClientOptions(opts...),
+ ),
+ }
+}
+
+// deploymentServiceClient implements DeploymentServiceClient.
+type deploymentServiceClient struct {
+ createDeployment *connect.Client[v1.CreateDeploymentRequest, v1.CreateDeploymentResponse]
+ getDeployment *connect.Client[v1.GetDeploymentRequest, v1.GetDeploymentResponse]
+ deleteDeployment *connect.Client[v1.DeleteDeploymentRequest, v1.DeleteDeploymentResponse]
+}
+
+// CreateDeployment calls krane.v1.DeploymentService.CreateDeployment.
+func (c *deploymentServiceClient) CreateDeployment(ctx context.Context, req *connect.Request[v1.CreateDeploymentRequest]) (*connect.Response[v1.CreateDeploymentResponse], error) {
+ return c.createDeployment.CallUnary(ctx, req)
+}
+
+// GetDeployment calls krane.v1.DeploymentService.GetDeployment.
+func (c *deploymentServiceClient) GetDeployment(ctx context.Context, req *connect.Request[v1.GetDeploymentRequest]) (*connect.Response[v1.GetDeploymentResponse], error) {
+ return c.getDeployment.CallUnary(ctx, req)
+}
+
+// DeleteDeployment calls krane.v1.DeploymentService.DeleteDeployment.
+func (c *deploymentServiceClient) DeleteDeployment(ctx context.Context, req *connect.Request[v1.DeleteDeploymentRequest]) (*connect.Response[v1.DeleteDeploymentResponse], error) {
+ return c.deleteDeployment.CallUnary(ctx, req)
+}
+
+// DeploymentServiceHandler is an implementation of the krane.v1.DeploymentService service.
+type DeploymentServiceHandler interface {
+ // CreateDeployment
+ CreateDeployment(context.Context, *connect.Request[v1.CreateDeploymentRequest]) (*connect.Response[v1.CreateDeploymentResponse], error)
+ // GetDeployment
+ GetDeployment(context.Context, *connect.Request[v1.GetDeploymentRequest]) (*connect.Response[v1.GetDeploymentResponse], error)
+ // DeleteDeployment
+ DeleteDeployment(context.Context, *connect.Request[v1.DeleteDeploymentRequest]) (*connect.Response[v1.DeleteDeploymentResponse], error)
+}
+
+// NewDeploymentServiceHandler builds an HTTP handler from the service implementation. It returns
+// the path on which to mount the handler and the handler itself.
+//
+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
+// and JSON codecs. They also support gzip compression.
+func NewDeploymentServiceHandler(svc DeploymentServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
+ deploymentServiceMethods := v1.File_krane_v1_deployment_proto.Services().ByName("DeploymentService").Methods()
+ deploymentServiceCreateDeploymentHandler := connect.NewUnaryHandler(
+ DeploymentServiceCreateDeploymentProcedure,
+ svc.CreateDeployment,
+ connect.WithSchema(deploymentServiceMethods.ByName("CreateDeployment")),
+ connect.WithHandlerOptions(opts...),
+ )
+ deploymentServiceGetDeploymentHandler := connect.NewUnaryHandler(
+ DeploymentServiceGetDeploymentProcedure,
+ svc.GetDeployment,
+ connect.WithSchema(deploymentServiceMethods.ByName("GetDeployment")),
+ connect.WithHandlerOptions(opts...),
+ )
+ deploymentServiceDeleteDeploymentHandler := connect.NewUnaryHandler(
+ DeploymentServiceDeleteDeploymentProcedure,
+ svc.DeleteDeployment,
+ connect.WithSchema(deploymentServiceMethods.ByName("DeleteDeployment")),
+ connect.WithHandlerOptions(opts...),
+ )
+ return "/krane.v1.DeploymentService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ switch r.URL.Path {
+ case DeploymentServiceCreateDeploymentProcedure:
+ deploymentServiceCreateDeploymentHandler.ServeHTTP(w, r)
+ case DeploymentServiceGetDeploymentProcedure:
+ deploymentServiceGetDeploymentHandler.ServeHTTP(w, r)
+ case DeploymentServiceDeleteDeploymentProcedure:
+ deploymentServiceDeleteDeploymentHandler.ServeHTTP(w, r)
+ default:
+ http.NotFound(w, r)
+ }
+ })
+}
+
+// UnimplementedDeploymentServiceHandler returns CodeUnimplemented from all methods.
+type UnimplementedDeploymentServiceHandler struct{}
+
+func (UnimplementedDeploymentServiceHandler) CreateDeployment(context.Context, *connect.Request[v1.CreateDeploymentRequest]) (*connect.Response[v1.CreateDeploymentResponse], error) {
+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("krane.v1.DeploymentService.CreateDeployment is not implemented"))
+}
+
+func (UnimplementedDeploymentServiceHandler) GetDeployment(context.Context, *connect.Request[v1.GetDeploymentRequest]) (*connect.Response[v1.GetDeploymentResponse], error) {
+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("krane.v1.DeploymentService.GetDeployment is not implemented"))
+}
+
+func (UnimplementedDeploymentServiceHandler) DeleteDeployment(context.Context, *connect.Request[v1.DeleteDeploymentRequest]) (*connect.Response[v1.DeleteDeploymentResponse], error) {
+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("krane.v1.DeploymentService.DeleteDeployment is not implemented"))
+}
diff --git a/go/gen/proto/metald/v1/deployment.pb.go b/go/gen/proto/metald/v1/deployment.pb.go
deleted file mode 100644
index 92f49e615b..0000000000
--- a/go/gen/proto/metald/v1/deployment.pb.go
+++ /dev/null
@@ -1,618 +0,0 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// protoc-gen-go v1.36.8
-// protoc (unknown)
-// source: metald/v1/deployment.proto
-
-package metaldv1
-
-import (
- protoreflect "google.golang.org/protobuf/reflect/protoreflect"
- protoimpl "google.golang.org/protobuf/runtime/protoimpl"
- reflect "reflect"
- sync "sync"
- unsafe "unsafe"
-)
-
-const (
- // Verify that this generated code is sufficiently up-to-date.
- _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
- // Verify that runtime/protoimpl is sufficiently up-to-date.
- _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
-)
-
-type DeploymentRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- DeploymentId string `protobuf:"bytes,1,opt,name=deployment_id,json=deploymentId,proto3" json:"deployment_id,omitempty"`
- Image string `protobuf:"bytes,2,opt,name=image,proto3" json:"image,omitempty"`
- VmCount uint32 `protobuf:"varint,3,opt,name=vm_count,json=vmCount,proto3" json:"vm_count,omitempty"`
- Cpu uint32 `protobuf:"varint,4,opt,name=cpu,proto3" json:"cpu,omitempty"`
- MemorySizeMib uint64 `protobuf:"varint,5,opt,name=memory_size_mib,json=memorySizeMib,proto3" json:"memory_size_mib,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *DeploymentRequest) Reset() {
- *x = DeploymentRequest{}
- mi := &file_metald_v1_deployment_proto_msgTypes[0]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *DeploymentRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*DeploymentRequest) ProtoMessage() {}
-
-func (x *DeploymentRequest) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_deployment_proto_msgTypes[0]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use DeploymentRequest.ProtoReflect.Descriptor instead.
-func (*DeploymentRequest) Descriptor() ([]byte, []int) {
- return file_metald_v1_deployment_proto_rawDescGZIP(), []int{0}
-}
-
-func (x *DeploymentRequest) GetDeploymentId() string {
- if x != nil {
- return x.DeploymentId
- }
- return ""
-}
-
-func (x *DeploymentRequest) GetImage() string {
- if x != nil {
- return x.Image
- }
- return ""
-}
-
-func (x *DeploymentRequest) GetVmCount() uint32 {
- if x != nil {
- return x.VmCount
- }
- return 0
-}
-
-func (x *DeploymentRequest) GetCpu() uint32 {
- if x != nil {
- return x.Cpu
- }
- return 0
-}
-
-func (x *DeploymentRequest) GetMemorySizeMib() uint64 {
- if x != nil {
- return x.MemorySizeMib
- }
- return 0
-}
-
-type CreateDeploymentRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Deployment *DeploymentRequest `protobuf:"bytes,1,opt,name=deployment,proto3" json:"deployment,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *CreateDeploymentRequest) Reset() {
- *x = CreateDeploymentRequest{}
- mi := &file_metald_v1_deployment_proto_msgTypes[1]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *CreateDeploymentRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*CreateDeploymentRequest) ProtoMessage() {}
-
-func (x *CreateDeploymentRequest) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_deployment_proto_msgTypes[1]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use CreateDeploymentRequest.ProtoReflect.Descriptor instead.
-func (*CreateDeploymentRequest) Descriptor() ([]byte, []int) {
- return file_metald_v1_deployment_proto_rawDescGZIP(), []int{1}
-}
-
-func (x *CreateDeploymentRequest) GetDeployment() *DeploymentRequest {
- if x != nil {
- return x.Deployment
- }
- return nil
-}
-
-type CreateDeploymentResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- VmIds []string `protobuf:"bytes,1,rep,name=vm_ids,json=vmIds,proto3" json:"vm_ids,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *CreateDeploymentResponse) Reset() {
- *x = CreateDeploymentResponse{}
- mi := &file_metald_v1_deployment_proto_msgTypes[2]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *CreateDeploymentResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*CreateDeploymentResponse) ProtoMessage() {}
-
-func (x *CreateDeploymentResponse) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_deployment_proto_msgTypes[2]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use CreateDeploymentResponse.ProtoReflect.Descriptor instead.
-func (*CreateDeploymentResponse) Descriptor() ([]byte, []int) {
- return file_metald_v1_deployment_proto_rawDescGZIP(), []int{2}
-}
-
-func (x *CreateDeploymentResponse) GetVmIds() []string {
- if x != nil {
- return x.VmIds
- }
- return nil
-}
-
-type UpdateDeploymentRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Deployment *DeploymentRequest `protobuf:"bytes,1,opt,name=deployment,proto3" json:"deployment,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *UpdateDeploymentRequest) Reset() {
- *x = UpdateDeploymentRequest{}
- mi := &file_metald_v1_deployment_proto_msgTypes[3]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *UpdateDeploymentRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*UpdateDeploymentRequest) ProtoMessage() {}
-
-func (x *UpdateDeploymentRequest) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_deployment_proto_msgTypes[3]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use UpdateDeploymentRequest.ProtoReflect.Descriptor instead.
-func (*UpdateDeploymentRequest) Descriptor() ([]byte, []int) {
- return file_metald_v1_deployment_proto_rawDescGZIP(), []int{3}
-}
-
-func (x *UpdateDeploymentRequest) GetDeployment() *DeploymentRequest {
- if x != nil {
- return x.Deployment
- }
- return nil
-}
-
-type UpdateDeploymentResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- VmIds []string `protobuf:"bytes,1,rep,name=vm_ids,json=vmIds,proto3" json:"vm_ids,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *UpdateDeploymentResponse) Reset() {
- *x = UpdateDeploymentResponse{}
- mi := &file_metald_v1_deployment_proto_msgTypes[4]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *UpdateDeploymentResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*UpdateDeploymentResponse) ProtoMessage() {}
-
-func (x *UpdateDeploymentResponse) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_deployment_proto_msgTypes[4]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use UpdateDeploymentResponse.ProtoReflect.Descriptor instead.
-func (*UpdateDeploymentResponse) Descriptor() ([]byte, []int) {
- return file_metald_v1_deployment_proto_rawDescGZIP(), []int{4}
-}
-
-func (x *UpdateDeploymentResponse) GetVmIds() []string {
- if x != nil {
- return x.VmIds
- }
- return nil
-}
-
-type DeleteDeploymentRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- DeploymentId string `protobuf:"bytes,1,opt,name=deployment_id,json=deploymentId,proto3" json:"deployment_id,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *DeleteDeploymentRequest) Reset() {
- *x = DeleteDeploymentRequest{}
- mi := &file_metald_v1_deployment_proto_msgTypes[5]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *DeleteDeploymentRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*DeleteDeploymentRequest) ProtoMessage() {}
-
-func (x *DeleteDeploymentRequest) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_deployment_proto_msgTypes[5]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use DeleteDeploymentRequest.ProtoReflect.Descriptor instead.
-func (*DeleteDeploymentRequest) Descriptor() ([]byte, []int) {
- return file_metald_v1_deployment_proto_rawDescGZIP(), []int{5}
-}
-
-func (x *DeleteDeploymentRequest) GetDeploymentId() string {
- if x != nil {
- return x.DeploymentId
- }
- return ""
-}
-
-type DeleteDeploymentResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *DeleteDeploymentResponse) Reset() {
- *x = DeleteDeploymentResponse{}
- mi := &file_metald_v1_deployment_proto_msgTypes[6]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *DeleteDeploymentResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*DeleteDeploymentResponse) ProtoMessage() {}
-
-func (x *DeleteDeploymentResponse) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_deployment_proto_msgTypes[6]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use DeleteDeploymentResponse.ProtoReflect.Descriptor instead.
-func (*DeleteDeploymentResponse) Descriptor() ([]byte, []int) {
- return file_metald_v1_deployment_proto_rawDescGZIP(), []int{6}
-}
-
-type GetDeploymentRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- DeploymentId string `protobuf:"bytes,1,opt,name=deployment_id,json=deploymentId,proto3" json:"deployment_id,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *GetDeploymentRequest) Reset() {
- *x = GetDeploymentRequest{}
- mi := &file_metald_v1_deployment_proto_msgTypes[7]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *GetDeploymentRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*GetDeploymentRequest) ProtoMessage() {}
-
-func (x *GetDeploymentRequest) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_deployment_proto_msgTypes[7]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use GetDeploymentRequest.ProtoReflect.Descriptor instead.
-func (*GetDeploymentRequest) Descriptor() ([]byte, []int) {
- return file_metald_v1_deployment_proto_rawDescGZIP(), []int{7}
-}
-
-func (x *GetDeploymentRequest) GetDeploymentId() string {
- if x != nil {
- return x.DeploymentId
- }
- return ""
-}
-
-type GetDeploymentResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- DeploymentId string `protobuf:"bytes,1,opt,name=deployment_id,json=deploymentId,proto3" json:"deployment_id,omitempty"`
- Vms []*GetDeploymentResponse_Vm `protobuf:"bytes,2,rep,name=vms,proto3" json:"vms,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *GetDeploymentResponse) Reset() {
- *x = GetDeploymentResponse{}
- mi := &file_metald_v1_deployment_proto_msgTypes[8]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *GetDeploymentResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*GetDeploymentResponse) ProtoMessage() {}
-
-func (x *GetDeploymentResponse) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_deployment_proto_msgTypes[8]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use GetDeploymentResponse.ProtoReflect.Descriptor instead.
-func (*GetDeploymentResponse) Descriptor() ([]byte, []int) {
- return file_metald_v1_deployment_proto_rawDescGZIP(), []int{8}
-}
-
-func (x *GetDeploymentResponse) GetDeploymentId() string {
- if x != nil {
- return x.DeploymentId
- }
- return ""
-}
-
-func (x *GetDeploymentResponse) GetVms() []*GetDeploymentResponse_Vm {
- if x != nil {
- return x.Vms
- }
- return nil
-}
-
-type GetDeploymentResponse_Vm struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
- Host string `protobuf:"bytes,2,opt,name=host,proto3" json:"host,omitempty"`
- State VmState `protobuf:"varint,3,opt,name=state,proto3,enum=metald.v1.VmState" json:"state,omitempty"`
- Port uint32 `protobuf:"varint,4,opt,name=port,proto3" json:"port,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *GetDeploymentResponse_Vm) Reset() {
- *x = GetDeploymentResponse_Vm{}
- mi := &file_metald_v1_deployment_proto_msgTypes[9]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *GetDeploymentResponse_Vm) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*GetDeploymentResponse_Vm) ProtoMessage() {}
-
-func (x *GetDeploymentResponse_Vm) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_deployment_proto_msgTypes[9]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use GetDeploymentResponse_Vm.ProtoReflect.Descriptor instead.
-func (*GetDeploymentResponse_Vm) Descriptor() ([]byte, []int) {
- return file_metald_v1_deployment_proto_rawDescGZIP(), []int{8, 0}
-}
-
-func (x *GetDeploymentResponse_Vm) GetId() string {
- if x != nil {
- return x.Id
- }
- return ""
-}
-
-func (x *GetDeploymentResponse_Vm) GetHost() string {
- if x != nil {
- return x.Host
- }
- return ""
-}
-
-func (x *GetDeploymentResponse_Vm) GetState() VmState {
- if x != nil {
- return x.State
- }
- return VmState_VM_STATE_UNSPECIFIED
-}
-
-func (x *GetDeploymentResponse_Vm) GetPort() uint32 {
- if x != nil {
- return x.Port
- }
- return 0
-}
-
-var File_metald_v1_deployment_proto protoreflect.FileDescriptor
-
-const file_metald_v1_deployment_proto_rawDesc = "" +
- "\n" +
- "\x1ametald/v1/deployment.proto\x12\tmetald.v1\x1a\x12metald/v1/vm.proto\"\xa3\x01\n" +
- "\x11DeploymentRequest\x12#\n" +
- "\rdeployment_id\x18\x01 \x01(\tR\fdeploymentId\x12\x14\n" +
- "\x05image\x18\x02 \x01(\tR\x05image\x12\x19\n" +
- "\bvm_count\x18\x03 \x01(\rR\avmCount\x12\x10\n" +
- "\x03cpu\x18\x04 \x01(\rR\x03cpu\x12&\n" +
- "\x0fmemory_size_mib\x18\x05 \x01(\x04R\rmemorySizeMib\"W\n" +
- "\x17CreateDeploymentRequest\x12<\n" +
- "\n" +
- "deployment\x18\x01 \x01(\v2\x1c.metald.v1.DeploymentRequestR\n" +
- "deployment\"1\n" +
- "\x18CreateDeploymentResponse\x12\x15\n" +
- "\x06vm_ids\x18\x01 \x03(\tR\x05vmIds\"W\n" +
- "\x17UpdateDeploymentRequest\x12<\n" +
- "\n" +
- "deployment\x18\x01 \x01(\v2\x1c.metald.v1.DeploymentRequestR\n" +
- "deployment\"1\n" +
- "\x18UpdateDeploymentResponse\x12\x15\n" +
- "\x06vm_ids\x18\x01 \x03(\tR\x05vmIds\">\n" +
- "\x17DeleteDeploymentRequest\x12#\n" +
- "\rdeployment_id\x18\x01 \x01(\tR\fdeploymentId\"\x1a\n" +
- "\x18DeleteDeploymentResponse\";\n" +
- "\x14GetDeploymentRequest\x12#\n" +
- "\rdeployment_id\x18\x01 \x01(\tR\fdeploymentId\"\xdb\x01\n" +
- "\x15GetDeploymentResponse\x12#\n" +
- "\rdeployment_id\x18\x01 \x01(\tR\fdeploymentId\x125\n" +
- "\x03vms\x18\x02 \x03(\v2#.metald.v1.GetDeploymentResponse.VmR\x03vms\x1af\n" +
- "\x02Vm\x12\x0e\n" +
- "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
- "\x04host\x18\x02 \x01(\tR\x04host\x12(\n" +
- "\x05state\x18\x03 \x01(\x0e2\x12.metald.v1.VmStateR\x05state\x12\x12\n" +
- "\x04port\x18\x04 \x01(\rR\x04portB:Z8github.com/unkeyed/unkey/go/gen/proto/metald/v1;metaldv1b\x06proto3"
-
-var (
- file_metald_v1_deployment_proto_rawDescOnce sync.Once
- file_metald_v1_deployment_proto_rawDescData []byte
-)
-
-func file_metald_v1_deployment_proto_rawDescGZIP() []byte {
- file_metald_v1_deployment_proto_rawDescOnce.Do(func() {
- file_metald_v1_deployment_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_metald_v1_deployment_proto_rawDesc), len(file_metald_v1_deployment_proto_rawDesc)))
- })
- return file_metald_v1_deployment_proto_rawDescData
-}
-
-var file_metald_v1_deployment_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
-var file_metald_v1_deployment_proto_goTypes = []any{
- (*DeploymentRequest)(nil), // 0: metald.v1.DeploymentRequest
- (*CreateDeploymentRequest)(nil), // 1: metald.v1.CreateDeploymentRequest
- (*CreateDeploymentResponse)(nil), // 2: metald.v1.CreateDeploymentResponse
- (*UpdateDeploymentRequest)(nil), // 3: metald.v1.UpdateDeploymentRequest
- (*UpdateDeploymentResponse)(nil), // 4: metald.v1.UpdateDeploymentResponse
- (*DeleteDeploymentRequest)(nil), // 5: metald.v1.DeleteDeploymentRequest
- (*DeleteDeploymentResponse)(nil), // 6: metald.v1.DeleteDeploymentResponse
- (*GetDeploymentRequest)(nil), // 7: metald.v1.GetDeploymentRequest
- (*GetDeploymentResponse)(nil), // 8: metald.v1.GetDeploymentResponse
- (*GetDeploymentResponse_Vm)(nil), // 9: metald.v1.GetDeploymentResponse.Vm
- (VmState)(0), // 10: metald.v1.VmState
-}
-var file_metald_v1_deployment_proto_depIdxs = []int32{
- 0, // 0: metald.v1.CreateDeploymentRequest.deployment:type_name -> metald.v1.DeploymentRequest
- 0, // 1: metald.v1.UpdateDeploymentRequest.deployment:type_name -> metald.v1.DeploymentRequest
- 9, // 2: metald.v1.GetDeploymentResponse.vms:type_name -> metald.v1.GetDeploymentResponse.Vm
- 10, // 3: metald.v1.GetDeploymentResponse.Vm.state:type_name -> metald.v1.VmState
- 4, // [4:4] is the sub-list for method output_type
- 4, // [4:4] is the sub-list for method input_type
- 4, // [4:4] is the sub-list for extension type_name
- 4, // [4:4] is the sub-list for extension extendee
- 0, // [0:4] is the sub-list for field type_name
-}
-
-func init() { file_metald_v1_deployment_proto_init() }
-func file_metald_v1_deployment_proto_init() {
- if File_metald_v1_deployment_proto != nil {
- return
- }
- file_metald_v1_vm_proto_init()
- type x struct{}
- out := protoimpl.TypeBuilder{
- File: protoimpl.DescBuilder{
- GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
- RawDescriptor: unsafe.Slice(unsafe.StringData(file_metald_v1_deployment_proto_rawDesc), len(file_metald_v1_deployment_proto_rawDesc)),
- NumEnums: 0,
- NumMessages: 10,
- NumExtensions: 0,
- NumServices: 0,
- },
- GoTypes: file_metald_v1_deployment_proto_goTypes,
- DependencyIndexes: file_metald_v1_deployment_proto_depIdxs,
- MessageInfos: file_metald_v1_deployment_proto_msgTypes,
- }.Build()
- File_metald_v1_deployment_proto = out.File
- file_metald_v1_deployment_proto_goTypes = nil
- file_metald_v1_deployment_proto_depIdxs = nil
-}
diff --git a/go/gen/proto/metald/v1/metald.pb.go b/go/gen/proto/metald/v1/metald.pb.go
deleted file mode 100644
index c93e23fdf6..0000000000
--- a/go/gen/proto/metald/v1/metald.pb.go
+++ /dev/null
@@ -1,129 +0,0 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// protoc-gen-go v1.36.8
-// protoc (unknown)
-// source: metald/v1/metald.proto
-
-package metaldv1
-
-import (
- protoreflect "google.golang.org/protobuf/reflect/protoreflect"
- protoimpl "google.golang.org/protobuf/runtime/protoimpl"
- reflect "reflect"
- unsafe "unsafe"
-)
-
-const (
- // Verify that this generated code is sufficiently up-to-date.
- _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
- // Verify that runtime/protoimpl is sufficiently up-to-date.
- _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
-)
-
-var File_metald_v1_metald_proto protoreflect.FileDescriptor
-
-const file_metald_v1_metald_proto_rawDesc = "" +
- "\n" +
- "\x16metald/v1/metald.proto\x12\tmetald.v1\x1a\x1ametald/v1/deployment.proto\x1a\x12metald/v1/vm.proto2\xe0\a\n" +
- "\tVmService\x12[\n" +
- "\x10CreateDeployment\x12\".metald.v1.CreateDeploymentRequest\x1a#.metald.v1.CreateDeploymentResponse\x12[\n" +
- "\x10UpdateDeployment\x12\".metald.v1.UpdateDeploymentRequest\x1a#.metald.v1.UpdateDeploymentResponse\x12[\n" +
- "\x10DeleteDeployment\x12\".metald.v1.DeleteDeploymentRequest\x1a#.metald.v1.DeleteDeploymentResponse\x12R\n" +
- "\rGetDeployment\x12\x1f.metald.v1.GetDeploymentRequest\x1a .metald.v1.GetDeploymentResponse\x12C\n" +
- "\bCreateVm\x12\x1a.metald.v1.CreateVmRequest\x1a\x1b.metald.v1.CreateVmResponse\x12C\n" +
- "\bDeleteVm\x12\x1a.metald.v1.DeleteVmRequest\x1a\x1b.metald.v1.DeleteVmResponse\x12=\n" +
- "\x06BootVm\x12\x18.metald.v1.BootVmRequest\x1a\x19.metald.v1.BootVmResponse\x12I\n" +
- "\n" +
- "ShutdownVm\x12\x1c.metald.v1.ShutdownVmRequest\x1a\x1d.metald.v1.ShutdownVmResponse\x12@\n" +
- "\aPauseVm\x12\x19.metald.v1.PauseVmRequest\x1a\x1a.metald.v1.PauseVmResponse\x12C\n" +
- "\bResumeVm\x12\x1a.metald.v1.ResumeVmRequest\x1a\x1b.metald.v1.ResumeVmResponse\x12C\n" +
- "\bRebootVm\x12\x1a.metald.v1.RebootVmRequest\x1a\x1b.metald.v1.RebootVmResponse\x12F\n" +
- "\tGetVmInfo\x12\x1b.metald.v1.GetVmInfoRequest\x1a\x1c.metald.v1.GetVmInfoResponse\x12@\n" +
- "\aListVms\x12\x19.metald.v1.ListVmsRequest\x1a\x1a.metald.v1.ListVmsResponseB:Z8github.com/unkeyed/unkey/go/gen/proto/metald/v1;metaldv1b\x06proto3"
-
-var file_metald_v1_metald_proto_goTypes = []any{
- (*CreateDeploymentRequest)(nil), // 0: metald.v1.CreateDeploymentRequest
- (*UpdateDeploymentRequest)(nil), // 1: metald.v1.UpdateDeploymentRequest
- (*DeleteDeploymentRequest)(nil), // 2: metald.v1.DeleteDeploymentRequest
- (*GetDeploymentRequest)(nil), // 3: metald.v1.GetDeploymentRequest
- (*CreateVmRequest)(nil), // 4: metald.v1.CreateVmRequest
- (*DeleteVmRequest)(nil), // 5: metald.v1.DeleteVmRequest
- (*BootVmRequest)(nil), // 6: metald.v1.BootVmRequest
- (*ShutdownVmRequest)(nil), // 7: metald.v1.ShutdownVmRequest
- (*PauseVmRequest)(nil), // 8: metald.v1.PauseVmRequest
- (*ResumeVmRequest)(nil), // 9: metald.v1.ResumeVmRequest
- (*RebootVmRequest)(nil), // 10: metald.v1.RebootVmRequest
- (*GetVmInfoRequest)(nil), // 11: metald.v1.GetVmInfoRequest
- (*ListVmsRequest)(nil), // 12: metald.v1.ListVmsRequest
- (*CreateDeploymentResponse)(nil), // 13: metald.v1.CreateDeploymentResponse
- (*UpdateDeploymentResponse)(nil), // 14: metald.v1.UpdateDeploymentResponse
- (*DeleteDeploymentResponse)(nil), // 15: metald.v1.DeleteDeploymentResponse
- (*GetDeploymentResponse)(nil), // 16: metald.v1.GetDeploymentResponse
- (*CreateVmResponse)(nil), // 17: metald.v1.CreateVmResponse
- (*DeleteVmResponse)(nil), // 18: metald.v1.DeleteVmResponse
- (*BootVmResponse)(nil), // 19: metald.v1.BootVmResponse
- (*ShutdownVmResponse)(nil), // 20: metald.v1.ShutdownVmResponse
- (*PauseVmResponse)(nil), // 21: metald.v1.PauseVmResponse
- (*ResumeVmResponse)(nil), // 22: metald.v1.ResumeVmResponse
- (*RebootVmResponse)(nil), // 23: metald.v1.RebootVmResponse
- (*GetVmInfoResponse)(nil), // 24: metald.v1.GetVmInfoResponse
- (*ListVmsResponse)(nil), // 25: metald.v1.ListVmsResponse
-}
-var file_metald_v1_metald_proto_depIdxs = []int32{
- 0, // 0: metald.v1.VmService.CreateDeployment:input_type -> metald.v1.CreateDeploymentRequest
- 1, // 1: metald.v1.VmService.UpdateDeployment:input_type -> metald.v1.UpdateDeploymentRequest
- 2, // 2: metald.v1.VmService.DeleteDeployment:input_type -> metald.v1.DeleteDeploymentRequest
- 3, // 3: metald.v1.VmService.GetDeployment:input_type -> metald.v1.GetDeploymentRequest
- 4, // 4: metald.v1.VmService.CreateVm:input_type -> metald.v1.CreateVmRequest
- 5, // 5: metald.v1.VmService.DeleteVm:input_type -> metald.v1.DeleteVmRequest
- 6, // 6: metald.v1.VmService.BootVm:input_type -> metald.v1.BootVmRequest
- 7, // 7: metald.v1.VmService.ShutdownVm:input_type -> metald.v1.ShutdownVmRequest
- 8, // 8: metald.v1.VmService.PauseVm:input_type -> metald.v1.PauseVmRequest
- 9, // 9: metald.v1.VmService.ResumeVm:input_type -> metald.v1.ResumeVmRequest
- 10, // 10: metald.v1.VmService.RebootVm:input_type -> metald.v1.RebootVmRequest
- 11, // 11: metald.v1.VmService.GetVmInfo:input_type -> metald.v1.GetVmInfoRequest
- 12, // 12: metald.v1.VmService.ListVms:input_type -> metald.v1.ListVmsRequest
- 13, // 13: metald.v1.VmService.CreateDeployment:output_type -> metald.v1.CreateDeploymentResponse
- 14, // 14: metald.v1.VmService.UpdateDeployment:output_type -> metald.v1.UpdateDeploymentResponse
- 15, // 15: metald.v1.VmService.DeleteDeployment:output_type -> metald.v1.DeleteDeploymentResponse
- 16, // 16: metald.v1.VmService.GetDeployment:output_type -> metald.v1.GetDeploymentResponse
- 17, // 17: metald.v1.VmService.CreateVm:output_type -> metald.v1.CreateVmResponse
- 18, // 18: metald.v1.VmService.DeleteVm:output_type -> metald.v1.DeleteVmResponse
- 19, // 19: metald.v1.VmService.BootVm:output_type -> metald.v1.BootVmResponse
- 20, // 20: metald.v1.VmService.ShutdownVm:output_type -> metald.v1.ShutdownVmResponse
- 21, // 21: metald.v1.VmService.PauseVm:output_type -> metald.v1.PauseVmResponse
- 22, // 22: metald.v1.VmService.ResumeVm:output_type -> metald.v1.ResumeVmResponse
- 23, // 23: metald.v1.VmService.RebootVm:output_type -> metald.v1.RebootVmResponse
- 24, // 24: metald.v1.VmService.GetVmInfo:output_type -> metald.v1.GetVmInfoResponse
- 25, // 25: metald.v1.VmService.ListVms:output_type -> metald.v1.ListVmsResponse
- 13, // [13:26] is the sub-list for method output_type
- 0, // [0:13] is the sub-list for method input_type
- 0, // [0:0] is the sub-list for extension type_name
- 0, // [0:0] is the sub-list for extension extendee
- 0, // [0:0] is the sub-list for field type_name
-}
-
-func init() { file_metald_v1_metald_proto_init() }
-func file_metald_v1_metald_proto_init() {
- if File_metald_v1_metald_proto != nil {
- return
- }
- file_metald_v1_deployment_proto_init()
- file_metald_v1_vm_proto_init()
- type x struct{}
- out := protoimpl.TypeBuilder{
- File: protoimpl.DescBuilder{
- GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
- RawDescriptor: unsafe.Slice(unsafe.StringData(file_metald_v1_metald_proto_rawDesc), len(file_metald_v1_metald_proto_rawDesc)),
- NumEnums: 0,
- NumMessages: 0,
- NumExtensions: 0,
- NumServices: 1,
- },
- GoTypes: file_metald_v1_metald_proto_goTypes,
- DependencyIndexes: file_metald_v1_metald_proto_depIdxs,
- }.Build()
- File_metald_v1_metald_proto = out.File
- file_metald_v1_metald_proto_goTypes = nil
- file_metald_v1_metald_proto_depIdxs = nil
-}
diff --git a/go/gen/proto/metald/v1/metaldv1connect/metald.connect.go b/go/gen/proto/metald/v1/metaldv1connect/metald.connect.go
deleted file mode 100644
index 2b3bc82710..0000000000
--- a/go/gen/proto/metald/v1/metaldv1connect/metald.connect.go
+++ /dev/null
@@ -1,473 +0,0 @@
-// Code generated by protoc-gen-connect-go. DO NOT EDIT.
-//
-// Source: metald/v1/metald.proto
-
-package metaldv1connect
-
-import (
- connect "connectrpc.com/connect"
- context "context"
- errors "errors"
- v1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1"
- http "net/http"
- strings "strings"
-)
-
-// This is a compile-time assertion to ensure that this generated file and the connect package are
-// compatible. If you get a compiler error that this constant is not defined, this code was
-// generated with a version of connect newer than the one compiled into your binary. You can fix the
-// problem by either regenerating this code with an older version of connect or updating the connect
-// version compiled into your binary.
-const _ = connect.IsAtLeastVersion1_13_0
-
-const (
- // VmServiceName is the fully-qualified name of the VmService service.
- VmServiceName = "metald.v1.VmService"
-)
-
-// These constants are the fully-qualified names of the RPCs defined in this package. They're
-// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
-//
-// Note that these are different from the fully-qualified method names used by
-// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
-// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
-// period.
-const (
- // VmServiceCreateDeploymentProcedure is the fully-qualified name of the VmService's
- // CreateDeployment RPC.
- VmServiceCreateDeploymentProcedure = "/metald.v1.VmService/CreateDeployment"
- // VmServiceUpdateDeploymentProcedure is the fully-qualified name of the VmService's
- // UpdateDeployment RPC.
- VmServiceUpdateDeploymentProcedure = "/metald.v1.VmService/UpdateDeployment"
- // VmServiceDeleteDeploymentProcedure is the fully-qualified name of the VmService's
- // DeleteDeployment RPC.
- VmServiceDeleteDeploymentProcedure = "/metald.v1.VmService/DeleteDeployment"
- // VmServiceGetDeploymentProcedure is the fully-qualified name of the VmService's GetDeployment RPC.
- VmServiceGetDeploymentProcedure = "/metald.v1.VmService/GetDeployment"
- // VmServiceCreateVmProcedure is the fully-qualified name of the VmService's CreateVm RPC.
- VmServiceCreateVmProcedure = "/metald.v1.VmService/CreateVm"
- // VmServiceDeleteVmProcedure is the fully-qualified name of the VmService's DeleteVm RPC.
- VmServiceDeleteVmProcedure = "/metald.v1.VmService/DeleteVm"
- // VmServiceBootVmProcedure is the fully-qualified name of the VmService's BootVm RPC.
- VmServiceBootVmProcedure = "/metald.v1.VmService/BootVm"
- // VmServiceShutdownVmProcedure is the fully-qualified name of the VmService's ShutdownVm RPC.
- VmServiceShutdownVmProcedure = "/metald.v1.VmService/ShutdownVm"
- // VmServicePauseVmProcedure is the fully-qualified name of the VmService's PauseVm RPC.
- VmServicePauseVmProcedure = "/metald.v1.VmService/PauseVm"
- // VmServiceResumeVmProcedure is the fully-qualified name of the VmService's ResumeVm RPC.
- VmServiceResumeVmProcedure = "/metald.v1.VmService/ResumeVm"
- // VmServiceRebootVmProcedure is the fully-qualified name of the VmService's RebootVm RPC.
- VmServiceRebootVmProcedure = "/metald.v1.VmService/RebootVm"
- // VmServiceGetVmInfoProcedure is the fully-qualified name of the VmService's GetVmInfo RPC.
- VmServiceGetVmInfoProcedure = "/metald.v1.VmService/GetVmInfo"
- // VmServiceListVmsProcedure is the fully-qualified name of the VmService's ListVms RPC.
- VmServiceListVmsProcedure = "/metald.v1.VmService/ListVms"
-)
-
-// VmServiceClient is a client for the metald.v1.VmService service.
-type VmServiceClient interface {
- // CreateDeployment
- CreateDeployment(context.Context, *connect.Request[v1.CreateDeploymentRequest]) (*connect.Response[v1.CreateDeploymentResponse], error)
- // UpdateDeployment
- UpdateDeployment(context.Context, *connect.Request[v1.UpdateDeploymentRequest]) (*connect.Response[v1.UpdateDeploymentResponse], error)
- // DeleteDeployment
- DeleteDeployment(context.Context, *connect.Request[v1.DeleteDeploymentRequest]) (*connect.Response[v1.DeleteDeploymentResponse], error)
- // GetDeployment
- GetDeployment(context.Context, *connect.Request[v1.GetDeploymentRequest]) (*connect.Response[v1.GetDeploymentResponse], error)
- // CreateVm creates a new virtual machine
- CreateVm(context.Context, *connect.Request[v1.CreateVmRequest]) (*connect.Response[v1.CreateVmResponse], error)
- // DeleteVm removes a virtual machine
- DeleteVm(context.Context, *connect.Request[v1.DeleteVmRequest]) (*connect.Response[v1.DeleteVmResponse], error)
- // BootVm starts a created virtual machine
- BootVm(context.Context, *connect.Request[v1.BootVmRequest]) (*connect.Response[v1.BootVmResponse], error)
- // ShutdownVm gracefully stops a running virtual machine
- ShutdownVm(context.Context, *connect.Request[v1.ShutdownVmRequest]) (*connect.Response[v1.ShutdownVmResponse], error)
- // PauseVm pauses a running virtual machine
- PauseVm(context.Context, *connect.Request[v1.PauseVmRequest]) (*connect.Response[v1.PauseVmResponse], error)
- // ResumeVm resumes a paused virtual machine
- ResumeVm(context.Context, *connect.Request[v1.ResumeVmRequest]) (*connect.Response[v1.ResumeVmResponse], error)
- // RebootVm restarts a running virtual machine
- RebootVm(context.Context, *connect.Request[v1.RebootVmRequest]) (*connect.Response[v1.RebootVmResponse], error)
- // GetVmInfo retrieves virtual machine status and configuration
- GetVmInfo(context.Context, *connect.Request[v1.GetVmInfoRequest]) (*connect.Response[v1.GetVmInfoResponse], error)
- // ListVms lists all virtual machines managed by this service
- ListVms(context.Context, *connect.Request[v1.ListVmsRequest]) (*connect.Response[v1.ListVmsResponse], error)
-}
-
-// NewVmServiceClient constructs a client for the metald.v1.VmService service. By default, it uses
-// the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends
-// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
-// connect.WithGRPCWeb() options.
-//
-// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
-// http://api.acme.com or https://acme.com/grpc).
-func NewVmServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) VmServiceClient {
- baseURL = strings.TrimRight(baseURL, "/")
- vmServiceMethods := v1.File_metald_v1_metald_proto.Services().ByName("VmService").Methods()
- return &vmServiceClient{
- createDeployment: connect.NewClient[v1.CreateDeploymentRequest, v1.CreateDeploymentResponse](
- httpClient,
- baseURL+VmServiceCreateDeploymentProcedure,
- connect.WithSchema(vmServiceMethods.ByName("CreateDeployment")),
- connect.WithClientOptions(opts...),
- ),
- updateDeployment: connect.NewClient[v1.UpdateDeploymentRequest, v1.UpdateDeploymentResponse](
- httpClient,
- baseURL+VmServiceUpdateDeploymentProcedure,
- connect.WithSchema(vmServiceMethods.ByName("UpdateDeployment")),
- connect.WithClientOptions(opts...),
- ),
- deleteDeployment: connect.NewClient[v1.DeleteDeploymentRequest, v1.DeleteDeploymentResponse](
- httpClient,
- baseURL+VmServiceDeleteDeploymentProcedure,
- connect.WithSchema(vmServiceMethods.ByName("DeleteDeployment")),
- connect.WithClientOptions(opts...),
- ),
- getDeployment: connect.NewClient[v1.GetDeploymentRequest, v1.GetDeploymentResponse](
- httpClient,
- baseURL+VmServiceGetDeploymentProcedure,
- connect.WithSchema(vmServiceMethods.ByName("GetDeployment")),
- connect.WithClientOptions(opts...),
- ),
- createVm: connect.NewClient[v1.CreateVmRequest, v1.CreateVmResponse](
- httpClient,
- baseURL+VmServiceCreateVmProcedure,
- connect.WithSchema(vmServiceMethods.ByName("CreateVm")),
- connect.WithClientOptions(opts...),
- ),
- deleteVm: connect.NewClient[v1.DeleteVmRequest, v1.DeleteVmResponse](
- httpClient,
- baseURL+VmServiceDeleteVmProcedure,
- connect.WithSchema(vmServiceMethods.ByName("DeleteVm")),
- connect.WithClientOptions(opts...),
- ),
- bootVm: connect.NewClient[v1.BootVmRequest, v1.BootVmResponse](
- httpClient,
- baseURL+VmServiceBootVmProcedure,
- connect.WithSchema(vmServiceMethods.ByName("BootVm")),
- connect.WithClientOptions(opts...),
- ),
- shutdownVm: connect.NewClient[v1.ShutdownVmRequest, v1.ShutdownVmResponse](
- httpClient,
- baseURL+VmServiceShutdownVmProcedure,
- connect.WithSchema(vmServiceMethods.ByName("ShutdownVm")),
- connect.WithClientOptions(opts...),
- ),
- pauseVm: connect.NewClient[v1.PauseVmRequest, v1.PauseVmResponse](
- httpClient,
- baseURL+VmServicePauseVmProcedure,
- connect.WithSchema(vmServiceMethods.ByName("PauseVm")),
- connect.WithClientOptions(opts...),
- ),
- resumeVm: connect.NewClient[v1.ResumeVmRequest, v1.ResumeVmResponse](
- httpClient,
- baseURL+VmServiceResumeVmProcedure,
- connect.WithSchema(vmServiceMethods.ByName("ResumeVm")),
- connect.WithClientOptions(opts...),
- ),
- rebootVm: connect.NewClient[v1.RebootVmRequest, v1.RebootVmResponse](
- httpClient,
- baseURL+VmServiceRebootVmProcedure,
- connect.WithSchema(vmServiceMethods.ByName("RebootVm")),
- connect.WithClientOptions(opts...),
- ),
- getVmInfo: connect.NewClient[v1.GetVmInfoRequest, v1.GetVmInfoResponse](
- httpClient,
- baseURL+VmServiceGetVmInfoProcedure,
- connect.WithSchema(vmServiceMethods.ByName("GetVmInfo")),
- connect.WithClientOptions(opts...),
- ),
- listVms: connect.NewClient[v1.ListVmsRequest, v1.ListVmsResponse](
- httpClient,
- baseURL+VmServiceListVmsProcedure,
- connect.WithSchema(vmServiceMethods.ByName("ListVms")),
- connect.WithClientOptions(opts...),
- ),
- }
-}
-
-// vmServiceClient implements VmServiceClient.
-type vmServiceClient struct {
- createDeployment *connect.Client[v1.CreateDeploymentRequest, v1.CreateDeploymentResponse]
- updateDeployment *connect.Client[v1.UpdateDeploymentRequest, v1.UpdateDeploymentResponse]
- deleteDeployment *connect.Client[v1.DeleteDeploymentRequest, v1.DeleteDeploymentResponse]
- getDeployment *connect.Client[v1.GetDeploymentRequest, v1.GetDeploymentResponse]
- createVm *connect.Client[v1.CreateVmRequest, v1.CreateVmResponse]
- deleteVm *connect.Client[v1.DeleteVmRequest, v1.DeleteVmResponse]
- bootVm *connect.Client[v1.BootVmRequest, v1.BootVmResponse]
- shutdownVm *connect.Client[v1.ShutdownVmRequest, v1.ShutdownVmResponse]
- pauseVm *connect.Client[v1.PauseVmRequest, v1.PauseVmResponse]
- resumeVm *connect.Client[v1.ResumeVmRequest, v1.ResumeVmResponse]
- rebootVm *connect.Client[v1.RebootVmRequest, v1.RebootVmResponse]
- getVmInfo *connect.Client[v1.GetVmInfoRequest, v1.GetVmInfoResponse]
- listVms *connect.Client[v1.ListVmsRequest, v1.ListVmsResponse]
-}
-
-// CreateDeployment calls metald.v1.VmService.CreateDeployment.
-func (c *vmServiceClient) CreateDeployment(ctx context.Context, req *connect.Request[v1.CreateDeploymentRequest]) (*connect.Response[v1.CreateDeploymentResponse], error) {
- return c.createDeployment.CallUnary(ctx, req)
-}
-
-// UpdateDeployment calls metald.v1.VmService.UpdateDeployment.
-func (c *vmServiceClient) UpdateDeployment(ctx context.Context, req *connect.Request[v1.UpdateDeploymentRequest]) (*connect.Response[v1.UpdateDeploymentResponse], error) {
- return c.updateDeployment.CallUnary(ctx, req)
-}
-
-// DeleteDeployment calls metald.v1.VmService.DeleteDeployment.
-func (c *vmServiceClient) DeleteDeployment(ctx context.Context, req *connect.Request[v1.DeleteDeploymentRequest]) (*connect.Response[v1.DeleteDeploymentResponse], error) {
- return c.deleteDeployment.CallUnary(ctx, req)
-}
-
-// GetDeployment calls metald.v1.VmService.GetDeployment.
-func (c *vmServiceClient) GetDeployment(ctx context.Context, req *connect.Request[v1.GetDeploymentRequest]) (*connect.Response[v1.GetDeploymentResponse], error) {
- return c.getDeployment.CallUnary(ctx, req)
-}
-
-// CreateVm calls metald.v1.VmService.CreateVm.
-func (c *vmServiceClient) CreateVm(ctx context.Context, req *connect.Request[v1.CreateVmRequest]) (*connect.Response[v1.CreateVmResponse], error) {
- return c.createVm.CallUnary(ctx, req)
-}
-
-// DeleteVm calls metald.v1.VmService.DeleteVm.
-func (c *vmServiceClient) DeleteVm(ctx context.Context, req *connect.Request[v1.DeleteVmRequest]) (*connect.Response[v1.DeleteVmResponse], error) {
- return c.deleteVm.CallUnary(ctx, req)
-}
-
-// BootVm calls metald.v1.VmService.BootVm.
-func (c *vmServiceClient) BootVm(ctx context.Context, req *connect.Request[v1.BootVmRequest]) (*connect.Response[v1.BootVmResponse], error) {
- return c.bootVm.CallUnary(ctx, req)
-}
-
-// ShutdownVm calls metald.v1.VmService.ShutdownVm.
-func (c *vmServiceClient) ShutdownVm(ctx context.Context, req *connect.Request[v1.ShutdownVmRequest]) (*connect.Response[v1.ShutdownVmResponse], error) {
- return c.shutdownVm.CallUnary(ctx, req)
-}
-
-// PauseVm calls metald.v1.VmService.PauseVm.
-func (c *vmServiceClient) PauseVm(ctx context.Context, req *connect.Request[v1.PauseVmRequest]) (*connect.Response[v1.PauseVmResponse], error) {
- return c.pauseVm.CallUnary(ctx, req)
-}
-
-// ResumeVm calls metald.v1.VmService.ResumeVm.
-func (c *vmServiceClient) ResumeVm(ctx context.Context, req *connect.Request[v1.ResumeVmRequest]) (*connect.Response[v1.ResumeVmResponse], error) {
- return c.resumeVm.CallUnary(ctx, req)
-}
-
-// RebootVm calls metald.v1.VmService.RebootVm.
-func (c *vmServiceClient) RebootVm(ctx context.Context, req *connect.Request[v1.RebootVmRequest]) (*connect.Response[v1.RebootVmResponse], error) {
- return c.rebootVm.CallUnary(ctx, req)
-}
-
-// GetVmInfo calls metald.v1.VmService.GetVmInfo.
-func (c *vmServiceClient) GetVmInfo(ctx context.Context, req *connect.Request[v1.GetVmInfoRequest]) (*connect.Response[v1.GetVmInfoResponse], error) {
- return c.getVmInfo.CallUnary(ctx, req)
-}
-
-// ListVms calls metald.v1.VmService.ListVms.
-func (c *vmServiceClient) ListVms(ctx context.Context, req *connect.Request[v1.ListVmsRequest]) (*connect.Response[v1.ListVmsResponse], error) {
- return c.listVms.CallUnary(ctx, req)
-}
-
-// VmServiceHandler is an implementation of the metald.v1.VmService service.
-type VmServiceHandler interface {
- // CreateDeployment
- CreateDeployment(context.Context, *connect.Request[v1.CreateDeploymentRequest]) (*connect.Response[v1.CreateDeploymentResponse], error)
- // UpdateDeployment
- UpdateDeployment(context.Context, *connect.Request[v1.UpdateDeploymentRequest]) (*connect.Response[v1.UpdateDeploymentResponse], error)
- // DeleteDeployment
- DeleteDeployment(context.Context, *connect.Request[v1.DeleteDeploymentRequest]) (*connect.Response[v1.DeleteDeploymentResponse], error)
- // GetDeployment
- GetDeployment(context.Context, *connect.Request[v1.GetDeploymentRequest]) (*connect.Response[v1.GetDeploymentResponse], error)
- // CreateVm creates a new virtual machine
- CreateVm(context.Context, *connect.Request[v1.CreateVmRequest]) (*connect.Response[v1.CreateVmResponse], error)
- // DeleteVm removes a virtual machine
- DeleteVm(context.Context, *connect.Request[v1.DeleteVmRequest]) (*connect.Response[v1.DeleteVmResponse], error)
- // BootVm starts a created virtual machine
- BootVm(context.Context, *connect.Request[v1.BootVmRequest]) (*connect.Response[v1.BootVmResponse], error)
- // ShutdownVm gracefully stops a running virtual machine
- ShutdownVm(context.Context, *connect.Request[v1.ShutdownVmRequest]) (*connect.Response[v1.ShutdownVmResponse], error)
- // PauseVm pauses a running virtual machine
- PauseVm(context.Context, *connect.Request[v1.PauseVmRequest]) (*connect.Response[v1.PauseVmResponse], error)
- // ResumeVm resumes a paused virtual machine
- ResumeVm(context.Context, *connect.Request[v1.ResumeVmRequest]) (*connect.Response[v1.ResumeVmResponse], error)
- // RebootVm restarts a running virtual machine
- RebootVm(context.Context, *connect.Request[v1.RebootVmRequest]) (*connect.Response[v1.RebootVmResponse], error)
- // GetVmInfo retrieves virtual machine status and configuration
- GetVmInfo(context.Context, *connect.Request[v1.GetVmInfoRequest]) (*connect.Response[v1.GetVmInfoResponse], error)
- // ListVms lists all virtual machines managed by this service
- ListVms(context.Context, *connect.Request[v1.ListVmsRequest]) (*connect.Response[v1.ListVmsResponse], error)
-}
-
-// NewVmServiceHandler builds an HTTP handler from the service implementation. It returns the path
-// on which to mount the handler and the handler itself.
-//
-// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
-// and JSON codecs. They also support gzip compression.
-func NewVmServiceHandler(svc VmServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
- vmServiceMethods := v1.File_metald_v1_metald_proto.Services().ByName("VmService").Methods()
- vmServiceCreateDeploymentHandler := connect.NewUnaryHandler(
- VmServiceCreateDeploymentProcedure,
- svc.CreateDeployment,
- connect.WithSchema(vmServiceMethods.ByName("CreateDeployment")),
- connect.WithHandlerOptions(opts...),
- )
- vmServiceUpdateDeploymentHandler := connect.NewUnaryHandler(
- VmServiceUpdateDeploymentProcedure,
- svc.UpdateDeployment,
- connect.WithSchema(vmServiceMethods.ByName("UpdateDeployment")),
- connect.WithHandlerOptions(opts...),
- )
- vmServiceDeleteDeploymentHandler := connect.NewUnaryHandler(
- VmServiceDeleteDeploymentProcedure,
- svc.DeleteDeployment,
- connect.WithSchema(vmServiceMethods.ByName("DeleteDeployment")),
- connect.WithHandlerOptions(opts...),
- )
- vmServiceGetDeploymentHandler := connect.NewUnaryHandler(
- VmServiceGetDeploymentProcedure,
- svc.GetDeployment,
- connect.WithSchema(vmServiceMethods.ByName("GetDeployment")),
- connect.WithHandlerOptions(opts...),
- )
- vmServiceCreateVmHandler := connect.NewUnaryHandler(
- VmServiceCreateVmProcedure,
- svc.CreateVm,
- connect.WithSchema(vmServiceMethods.ByName("CreateVm")),
- connect.WithHandlerOptions(opts...),
- )
- vmServiceDeleteVmHandler := connect.NewUnaryHandler(
- VmServiceDeleteVmProcedure,
- svc.DeleteVm,
- connect.WithSchema(vmServiceMethods.ByName("DeleteVm")),
- connect.WithHandlerOptions(opts...),
- )
- vmServiceBootVmHandler := connect.NewUnaryHandler(
- VmServiceBootVmProcedure,
- svc.BootVm,
- connect.WithSchema(vmServiceMethods.ByName("BootVm")),
- connect.WithHandlerOptions(opts...),
- )
- vmServiceShutdownVmHandler := connect.NewUnaryHandler(
- VmServiceShutdownVmProcedure,
- svc.ShutdownVm,
- connect.WithSchema(vmServiceMethods.ByName("ShutdownVm")),
- connect.WithHandlerOptions(opts...),
- )
- vmServicePauseVmHandler := connect.NewUnaryHandler(
- VmServicePauseVmProcedure,
- svc.PauseVm,
- connect.WithSchema(vmServiceMethods.ByName("PauseVm")),
- connect.WithHandlerOptions(opts...),
- )
- vmServiceResumeVmHandler := connect.NewUnaryHandler(
- VmServiceResumeVmProcedure,
- svc.ResumeVm,
- connect.WithSchema(vmServiceMethods.ByName("ResumeVm")),
- connect.WithHandlerOptions(opts...),
- )
- vmServiceRebootVmHandler := connect.NewUnaryHandler(
- VmServiceRebootVmProcedure,
- svc.RebootVm,
- connect.WithSchema(vmServiceMethods.ByName("RebootVm")),
- connect.WithHandlerOptions(opts...),
- )
- vmServiceGetVmInfoHandler := connect.NewUnaryHandler(
- VmServiceGetVmInfoProcedure,
- svc.GetVmInfo,
- connect.WithSchema(vmServiceMethods.ByName("GetVmInfo")),
- connect.WithHandlerOptions(opts...),
- )
- vmServiceListVmsHandler := connect.NewUnaryHandler(
- VmServiceListVmsProcedure,
- svc.ListVms,
- connect.WithSchema(vmServiceMethods.ByName("ListVms")),
- connect.WithHandlerOptions(opts...),
- )
- return "/metald.v1.VmService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- switch r.URL.Path {
- case VmServiceCreateDeploymentProcedure:
- vmServiceCreateDeploymentHandler.ServeHTTP(w, r)
- case VmServiceUpdateDeploymentProcedure:
- vmServiceUpdateDeploymentHandler.ServeHTTP(w, r)
- case VmServiceDeleteDeploymentProcedure:
- vmServiceDeleteDeploymentHandler.ServeHTTP(w, r)
- case VmServiceGetDeploymentProcedure:
- vmServiceGetDeploymentHandler.ServeHTTP(w, r)
- case VmServiceCreateVmProcedure:
- vmServiceCreateVmHandler.ServeHTTP(w, r)
- case VmServiceDeleteVmProcedure:
- vmServiceDeleteVmHandler.ServeHTTP(w, r)
- case VmServiceBootVmProcedure:
- vmServiceBootVmHandler.ServeHTTP(w, r)
- case VmServiceShutdownVmProcedure:
- vmServiceShutdownVmHandler.ServeHTTP(w, r)
- case VmServicePauseVmProcedure:
- vmServicePauseVmHandler.ServeHTTP(w, r)
- case VmServiceResumeVmProcedure:
- vmServiceResumeVmHandler.ServeHTTP(w, r)
- case VmServiceRebootVmProcedure:
- vmServiceRebootVmHandler.ServeHTTP(w, r)
- case VmServiceGetVmInfoProcedure:
- vmServiceGetVmInfoHandler.ServeHTTP(w, r)
- case VmServiceListVmsProcedure:
- vmServiceListVmsHandler.ServeHTTP(w, r)
- default:
- http.NotFound(w, r)
- }
- })
-}
-
-// UnimplementedVmServiceHandler returns CodeUnimplemented from all methods.
-type UnimplementedVmServiceHandler struct{}
-
-func (UnimplementedVmServiceHandler) CreateDeployment(context.Context, *connect.Request[v1.CreateDeploymentRequest]) (*connect.Response[v1.CreateDeploymentResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("metald.v1.VmService.CreateDeployment is not implemented"))
-}
-
-func (UnimplementedVmServiceHandler) UpdateDeployment(context.Context, *connect.Request[v1.UpdateDeploymentRequest]) (*connect.Response[v1.UpdateDeploymentResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("metald.v1.VmService.UpdateDeployment is not implemented"))
-}
-
-func (UnimplementedVmServiceHandler) DeleteDeployment(context.Context, *connect.Request[v1.DeleteDeploymentRequest]) (*connect.Response[v1.DeleteDeploymentResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("metald.v1.VmService.DeleteDeployment is not implemented"))
-}
-
-func (UnimplementedVmServiceHandler) GetDeployment(context.Context, *connect.Request[v1.GetDeploymentRequest]) (*connect.Response[v1.GetDeploymentResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("metald.v1.VmService.GetDeployment is not implemented"))
-}
-
-func (UnimplementedVmServiceHandler) CreateVm(context.Context, *connect.Request[v1.CreateVmRequest]) (*connect.Response[v1.CreateVmResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("metald.v1.VmService.CreateVm is not implemented"))
-}
-
-func (UnimplementedVmServiceHandler) DeleteVm(context.Context, *connect.Request[v1.DeleteVmRequest]) (*connect.Response[v1.DeleteVmResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("metald.v1.VmService.DeleteVm is not implemented"))
-}
-
-func (UnimplementedVmServiceHandler) BootVm(context.Context, *connect.Request[v1.BootVmRequest]) (*connect.Response[v1.BootVmResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("metald.v1.VmService.BootVm is not implemented"))
-}
-
-func (UnimplementedVmServiceHandler) ShutdownVm(context.Context, *connect.Request[v1.ShutdownVmRequest]) (*connect.Response[v1.ShutdownVmResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("metald.v1.VmService.ShutdownVm is not implemented"))
-}
-
-func (UnimplementedVmServiceHandler) PauseVm(context.Context, *connect.Request[v1.PauseVmRequest]) (*connect.Response[v1.PauseVmResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("metald.v1.VmService.PauseVm is not implemented"))
-}
-
-func (UnimplementedVmServiceHandler) ResumeVm(context.Context, *connect.Request[v1.ResumeVmRequest]) (*connect.Response[v1.ResumeVmResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("metald.v1.VmService.ResumeVm is not implemented"))
-}
-
-func (UnimplementedVmServiceHandler) RebootVm(context.Context, *connect.Request[v1.RebootVmRequest]) (*connect.Response[v1.RebootVmResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("metald.v1.VmService.RebootVm is not implemented"))
-}
-
-func (UnimplementedVmServiceHandler) GetVmInfo(context.Context, *connect.Request[v1.GetVmInfoRequest]) (*connect.Response[v1.GetVmInfoResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("metald.v1.VmService.GetVmInfo is not implemented"))
-}
-
-func (UnimplementedVmServiceHandler) ListVms(context.Context, *connect.Request[v1.ListVmsRequest]) (*connect.Response[v1.ListVmsResponse], error) {
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("metald.v1.VmService.ListVms is not implemented"))
-}
diff --git a/go/gen/proto/metald/v1/network.pb.go b/go/gen/proto/metald/v1/network.pb.go
deleted file mode 100644
index d58b156681..0000000000
--- a/go/gen/proto/metald/v1/network.pb.go
+++ /dev/null
@@ -1,620 +0,0 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// protoc-gen-go v1.36.8
-// protoc (unknown)
-// source: metald/v1/network.proto
-
-package metaldv1
-
-import (
- protoreflect "google.golang.org/protobuf/reflect/protoreflect"
- protoimpl "google.golang.org/protobuf/runtime/protoimpl"
- reflect "reflect"
- sync "sync"
- unsafe "unsafe"
-)
-
-const (
- // Verify that this generated code is sufficiently up-to-date.
- _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
- // Verify that runtime/protoimpl is sufficiently up-to-date.
- _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
-)
-
-// Network mode for the interface
-type NetworkMode int32
-
-const (
- NetworkMode_NETWORK_MODE_UNSPECIFIED NetworkMode = 0
- NetworkMode_NETWORK_MODE_DUAL_STACK NetworkMode = 1 // Both IPv4 and IPv6
- NetworkMode_NETWORK_MODE_IPV4_ONLY NetworkMode = 2 // IPv4 only
- NetworkMode_NETWORK_MODE_IPV6_ONLY NetworkMode = 3 // IPv6 only
-)
-
-// Enum value maps for NetworkMode.
-var (
- NetworkMode_name = map[int32]string{
- 0: "NETWORK_MODE_UNSPECIFIED",
- 1: "NETWORK_MODE_DUAL_STACK",
- 2: "NETWORK_MODE_IPV4_ONLY",
- 3: "NETWORK_MODE_IPV6_ONLY",
- }
- NetworkMode_value = map[string]int32{
- "NETWORK_MODE_UNSPECIFIED": 0,
- "NETWORK_MODE_DUAL_STACK": 1,
- "NETWORK_MODE_IPV4_ONLY": 2,
- "NETWORK_MODE_IPV6_ONLY": 3,
- }
-)
-
-func (x NetworkMode) Enum() *NetworkMode {
- p := new(NetworkMode)
- *p = x
- return p
-}
-
-func (x NetworkMode) String() string {
- return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
-}
-
-func (NetworkMode) Descriptor() protoreflect.EnumDescriptor {
- return file_metald_v1_network_proto_enumTypes[0].Descriptor()
-}
-
-func (NetworkMode) Type() protoreflect.EnumType {
- return &file_metald_v1_network_proto_enumTypes[0]
-}
-
-func (x NetworkMode) Number() protoreflect.EnumNumber {
- return protoreflect.EnumNumber(x)
-}
-
-// Deprecated: Use NetworkMode.Descriptor instead.
-func (NetworkMode) EnumDescriptor() ([]byte, []int) {
- return file_metald_v1_network_proto_rawDescGZIP(), []int{0}
-}
-
-type NetworkStats struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- BytesReceived int64 `protobuf:"varint,1,opt,name=bytes_received,json=bytesReceived,proto3" json:"bytes_received,omitempty"`
- BytesTransmitted int64 `protobuf:"varint,2,opt,name=bytes_transmitted,json=bytesTransmitted,proto3" json:"bytes_transmitted,omitempty"`
- PacketsReceived int64 `protobuf:"varint,3,opt,name=packets_received,json=packetsReceived,proto3" json:"packets_received,omitempty"`
- PacketsTransmitted int64 `protobuf:"varint,4,opt,name=packets_transmitted,json=packetsTransmitted,proto3" json:"packets_transmitted,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *NetworkStats) Reset() {
- *x = NetworkStats{}
- mi := &file_metald_v1_network_proto_msgTypes[0]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *NetworkStats) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*NetworkStats) ProtoMessage() {}
-
-func (x *NetworkStats) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_network_proto_msgTypes[0]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use NetworkStats.ProtoReflect.Descriptor instead.
-func (*NetworkStats) Descriptor() ([]byte, []int) {
- return file_metald_v1_network_proto_rawDescGZIP(), []int{0}
-}
-
-func (x *NetworkStats) GetBytesReceived() int64 {
- if x != nil {
- return x.BytesReceived
- }
- return 0
-}
-
-func (x *NetworkStats) GetBytesTransmitted() int64 {
- if x != nil {
- return x.BytesTransmitted
- }
- return 0
-}
-
-func (x *NetworkStats) GetPacketsReceived() int64 {
- if x != nil {
- return x.PacketsReceived
- }
- return 0
-}
-
-func (x *NetworkStats) GetPacketsTransmitted() int64 {
- if x != nil {
- return x.PacketsTransmitted
- }
- return 0
-}
-
-type NetworkInterface struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // Unique identifier for this network interface
- Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
- // MAC address (optional, will be generated if not provided)
- MacAddress string `protobuf:"bytes,2,opt,name=mac_address,json=macAddress,proto3" json:"mac_address,omitempty"`
- // Host-side TAP device name
- TapDevice string `protobuf:"bytes,3,opt,name=tap_device,json=tapDevice,proto3" json:"tap_device,omitempty"`
- // Network interface type (virtio-net, e1000, etc.)
- InterfaceType string `protobuf:"bytes,4,opt,name=interface_type,json=interfaceType,proto3" json:"interface_type,omitempty"`
- // Additional network options
- Options map[string]string `protobuf:"bytes,5,rep,name=options,proto3" json:"options,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- // IPv4 configuration (optional)
- Ipv4Config *IPv4Config `protobuf:"bytes,6,opt,name=ipv4_config,json=ipv4Config,proto3" json:"ipv4_config,omitempty"`
- // IPv6 configuration (optional)
- Ipv6Config *IPv6Config `protobuf:"bytes,7,opt,name=ipv6_config,json=ipv6Config,proto3" json:"ipv6_config,omitempty"`
- // Network mode
- Mode NetworkMode `protobuf:"varint,8,opt,name=mode,proto3,enum=metald.v1.NetworkMode" json:"mode,omitempty"`
- // Rate limiting
- RxRateLimit *RateLimit `protobuf:"bytes,10,opt,name=rx_rate_limit,json=rxRateLimit,proto3" json:"rx_rate_limit,omitempty"` // Receive rate limit
- TxRateLimit *RateLimit `protobuf:"bytes,11,opt,name=tx_rate_limit,json=txRateLimit,proto3" json:"tx_rate_limit,omitempty"` // Transmit rate limit
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *NetworkInterface) Reset() {
- *x = NetworkInterface{}
- mi := &file_metald_v1_network_proto_msgTypes[1]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *NetworkInterface) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*NetworkInterface) ProtoMessage() {}
-
-func (x *NetworkInterface) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_network_proto_msgTypes[1]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use NetworkInterface.ProtoReflect.Descriptor instead.
-func (*NetworkInterface) Descriptor() ([]byte, []int) {
- return file_metald_v1_network_proto_rawDescGZIP(), []int{1}
-}
-
-func (x *NetworkInterface) GetId() string {
- if x != nil {
- return x.Id
- }
- return ""
-}
-
-func (x *NetworkInterface) GetMacAddress() string {
- if x != nil {
- return x.MacAddress
- }
- return ""
-}
-
-func (x *NetworkInterface) GetTapDevice() string {
- if x != nil {
- return x.TapDevice
- }
- return ""
-}
-
-func (x *NetworkInterface) GetInterfaceType() string {
- if x != nil {
- return x.InterfaceType
- }
- return ""
-}
-
-func (x *NetworkInterface) GetOptions() map[string]string {
- if x != nil {
- return x.Options
- }
- return nil
-}
-
-func (x *NetworkInterface) GetIpv4Config() *IPv4Config {
- if x != nil {
- return x.Ipv4Config
- }
- return nil
-}
-
-func (x *NetworkInterface) GetIpv6Config() *IPv6Config {
- if x != nil {
- return x.Ipv6Config
- }
- return nil
-}
-
-func (x *NetworkInterface) GetMode() NetworkMode {
- if x != nil {
- return x.Mode
- }
- return NetworkMode_NETWORK_MODE_UNSPECIFIED
-}
-
-func (x *NetworkInterface) GetRxRateLimit() *RateLimit {
- if x != nil {
- return x.RxRateLimit
- }
- return nil
-}
-
-func (x *NetworkInterface) GetTxRateLimit() *RateLimit {
- if x != nil {
- return x.TxRateLimit
- }
- return nil
-}
-
-// IPv4 network configuration
-type IPv4Config struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` // IPv4 address (e.g., "10.100.1.2")
- Netmask string `protobuf:"bytes,2,opt,name=netmask,proto3" json:"netmask,omitempty"` // Network mask (e.g., "255.255.255.0")
- Gateway string `protobuf:"bytes,3,opt,name=gateway,proto3" json:"gateway,omitempty"` // Default gateway
- DnsServers []string `protobuf:"bytes,4,rep,name=dns_servers,json=dnsServers,proto3" json:"dns_servers,omitempty"` // DNS servers
- Dhcp bool `protobuf:"varint,5,opt,name=dhcp,proto3" json:"dhcp,omitempty"` // Use DHCP instead of static config
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *IPv4Config) Reset() {
- *x = IPv4Config{}
- mi := &file_metald_v1_network_proto_msgTypes[2]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *IPv4Config) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*IPv4Config) ProtoMessage() {}
-
-func (x *IPv4Config) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_network_proto_msgTypes[2]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use IPv4Config.ProtoReflect.Descriptor instead.
-func (*IPv4Config) Descriptor() ([]byte, []int) {
- return file_metald_v1_network_proto_rawDescGZIP(), []int{2}
-}
-
-func (x *IPv4Config) GetAddress() string {
- if x != nil {
- return x.Address
- }
- return ""
-}
-
-func (x *IPv4Config) GetNetmask() string {
- if x != nil {
- return x.Netmask
- }
- return ""
-}
-
-func (x *IPv4Config) GetGateway() string {
- if x != nil {
- return x.Gateway
- }
- return ""
-}
-
-func (x *IPv4Config) GetDnsServers() []string {
- if x != nil {
- return x.DnsServers
- }
- return nil
-}
-
-func (x *IPv4Config) GetDhcp() bool {
- if x != nil {
- return x.Dhcp
- }
- return false
-}
-
-// IPv6 network configuration
-type IPv6Config struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` // IPv6 address (e.g., "fd00::1:2")
- PrefixLength int32 `protobuf:"varint,2,opt,name=prefix_length,json=prefixLength,proto3" json:"prefix_length,omitempty"` // Prefix length (e.g., 64)
- Gateway string `protobuf:"bytes,3,opt,name=gateway,proto3" json:"gateway,omitempty"` // Default gateway
- DnsServers []string `protobuf:"bytes,4,rep,name=dns_servers,json=dnsServers,proto3" json:"dns_servers,omitempty"` // DNS servers (IPv6 addresses)
- Slaac bool `protobuf:"varint,5,opt,name=slaac,proto3" json:"slaac,omitempty"` // Use SLAAC (Stateless Address Autoconfiguration)
- PrivacyExtensions bool `protobuf:"varint,6,opt,name=privacy_extensions,json=privacyExtensions,proto3" json:"privacy_extensions,omitempty"` // Enable privacy extensions
- LinkLocal string `protobuf:"bytes,7,opt,name=link_local,json=linkLocal,proto3" json:"link_local,omitempty"` // Link-local address (auto-generated if empty)
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *IPv6Config) Reset() {
- *x = IPv6Config{}
- mi := &file_metald_v1_network_proto_msgTypes[3]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *IPv6Config) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*IPv6Config) ProtoMessage() {}
-
-func (x *IPv6Config) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_network_proto_msgTypes[3]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use IPv6Config.ProtoReflect.Descriptor instead.
-func (*IPv6Config) Descriptor() ([]byte, []int) {
- return file_metald_v1_network_proto_rawDescGZIP(), []int{3}
-}
-
-func (x *IPv6Config) GetAddress() string {
- if x != nil {
- return x.Address
- }
- return ""
-}
-
-func (x *IPv6Config) GetPrefixLength() int32 {
- if x != nil {
- return x.PrefixLength
- }
- return 0
-}
-
-func (x *IPv6Config) GetGateway() string {
- if x != nil {
- return x.Gateway
- }
- return ""
-}
-
-func (x *IPv6Config) GetDnsServers() []string {
- if x != nil {
- return x.DnsServers
- }
- return nil
-}
-
-func (x *IPv6Config) GetSlaac() bool {
- if x != nil {
- return x.Slaac
- }
- return false
-}
-
-func (x *IPv6Config) GetPrivacyExtensions() bool {
- if x != nil {
- return x.PrivacyExtensions
- }
- return false
-}
-
-func (x *IPv6Config) GetLinkLocal() string {
- if x != nil {
- return x.LinkLocal
- }
- return ""
-}
-
-// Rate limiting configuration
-type RateLimit struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Bandwidth int64 `protobuf:"varint,1,opt,name=bandwidth,proto3" json:"bandwidth,omitempty"` // Bandwidth in bytes/second
- RefillTime int64 `protobuf:"varint,2,opt,name=refill_time,json=refillTime,proto3" json:"refill_time,omitempty"` // Token bucket refill time in milliseconds
- Burst int64 `protobuf:"varint,3,opt,name=burst,proto3" json:"burst,omitempty"` // Burst size in bytes
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *RateLimit) Reset() {
- *x = RateLimit{}
- mi := &file_metald_v1_network_proto_msgTypes[4]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *RateLimit) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*RateLimit) ProtoMessage() {}
-
-func (x *RateLimit) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_network_proto_msgTypes[4]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use RateLimit.ProtoReflect.Descriptor instead.
-func (*RateLimit) Descriptor() ([]byte, []int) {
- return file_metald_v1_network_proto_rawDescGZIP(), []int{4}
-}
-
-func (x *RateLimit) GetBandwidth() int64 {
- if x != nil {
- return x.Bandwidth
- }
- return 0
-}
-
-func (x *RateLimit) GetRefillTime() int64 {
- if x != nil {
- return x.RefillTime
- }
- return 0
-}
-
-func (x *RateLimit) GetBurst() int64 {
- if x != nil {
- return x.Burst
- }
- return 0
-}
-
-var File_metald_v1_network_proto protoreflect.FileDescriptor
-
-const file_metald_v1_network_proto_rawDesc = "" +
- "\n" +
- "\x17metald/v1/network.proto\x12\tmetald.v1\"\xbe\x01\n" +
- "\fNetworkStats\x12%\n" +
- "\x0ebytes_received\x18\x01 \x01(\x03R\rbytesReceived\x12+\n" +
- "\x11bytes_transmitted\x18\x02 \x01(\x03R\x10bytesTransmitted\x12)\n" +
- "\x10packets_received\x18\x03 \x01(\x03R\x0fpacketsReceived\x12/\n" +
- "\x13packets_transmitted\x18\x04 \x01(\x03R\x12packetsTransmitted\"\x99\x04\n" +
- "\x10NetworkInterface\x12\x0e\n" +
- "\x02id\x18\x01 \x01(\tR\x02id\x12\x1f\n" +
- "\vmac_address\x18\x02 \x01(\tR\n" +
- "macAddress\x12\x1d\n" +
- "\n" +
- "tap_device\x18\x03 \x01(\tR\ttapDevice\x12%\n" +
- "\x0einterface_type\x18\x04 \x01(\tR\rinterfaceType\x12B\n" +
- "\aoptions\x18\x05 \x03(\v2(.metald.v1.NetworkInterface.OptionsEntryR\aoptions\x126\n" +
- "\vipv4_config\x18\x06 \x01(\v2\x15.metald.v1.IPv4ConfigR\n" +
- "ipv4Config\x126\n" +
- "\vipv6_config\x18\a \x01(\v2\x15.metald.v1.IPv6ConfigR\n" +
- "ipv6Config\x12*\n" +
- "\x04mode\x18\b \x01(\x0e2\x16.metald.v1.NetworkModeR\x04mode\x128\n" +
- "\rrx_rate_limit\x18\n" +
- " \x01(\v2\x14.metald.v1.RateLimitR\vrxRateLimit\x128\n" +
- "\rtx_rate_limit\x18\v \x01(\v2\x14.metald.v1.RateLimitR\vtxRateLimit\x1a:\n" +
- "\fOptionsEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x8f\x01\n" +
- "\n" +
- "IPv4Config\x12\x18\n" +
- "\aaddress\x18\x01 \x01(\tR\aaddress\x12\x18\n" +
- "\anetmask\x18\x02 \x01(\tR\anetmask\x12\x18\n" +
- "\agateway\x18\x03 \x01(\tR\agateway\x12\x1f\n" +
- "\vdns_servers\x18\x04 \x03(\tR\n" +
- "dnsServers\x12\x12\n" +
- "\x04dhcp\x18\x05 \x01(\bR\x04dhcp\"\xea\x01\n" +
- "\n" +
- "IPv6Config\x12\x18\n" +
- "\aaddress\x18\x01 \x01(\tR\aaddress\x12#\n" +
- "\rprefix_length\x18\x02 \x01(\x05R\fprefixLength\x12\x18\n" +
- "\agateway\x18\x03 \x01(\tR\agateway\x12\x1f\n" +
- "\vdns_servers\x18\x04 \x03(\tR\n" +
- "dnsServers\x12\x14\n" +
- "\x05slaac\x18\x05 \x01(\bR\x05slaac\x12-\n" +
- "\x12privacy_extensions\x18\x06 \x01(\bR\x11privacyExtensions\x12\x1d\n" +
- "\n" +
- "link_local\x18\a \x01(\tR\tlinkLocal\"`\n" +
- "\tRateLimit\x12\x1c\n" +
- "\tbandwidth\x18\x01 \x01(\x03R\tbandwidth\x12\x1f\n" +
- "\vrefill_time\x18\x02 \x01(\x03R\n" +
- "refillTime\x12\x14\n" +
- "\x05burst\x18\x03 \x01(\x03R\x05burst*\x80\x01\n" +
- "\vNetworkMode\x12\x1c\n" +
- "\x18NETWORK_MODE_UNSPECIFIED\x10\x00\x12\x1b\n" +
- "\x17NETWORK_MODE_DUAL_STACK\x10\x01\x12\x1a\n" +
- "\x16NETWORK_MODE_IPV4_ONLY\x10\x02\x12\x1a\n" +
- "\x16NETWORK_MODE_IPV6_ONLY\x10\x03B:Z8github.com/unkeyed/unkey/go/gen/proto/metald/v1;metaldv1b\x06proto3"
-
-var (
- file_metald_v1_network_proto_rawDescOnce sync.Once
- file_metald_v1_network_proto_rawDescData []byte
-)
-
-func file_metald_v1_network_proto_rawDescGZIP() []byte {
- file_metald_v1_network_proto_rawDescOnce.Do(func() {
- file_metald_v1_network_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_metald_v1_network_proto_rawDesc), len(file_metald_v1_network_proto_rawDesc)))
- })
- return file_metald_v1_network_proto_rawDescData
-}
-
-var file_metald_v1_network_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_metald_v1_network_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
-var file_metald_v1_network_proto_goTypes = []any{
- (NetworkMode)(0), // 0: metald.v1.NetworkMode
- (*NetworkStats)(nil), // 1: metald.v1.NetworkStats
- (*NetworkInterface)(nil), // 2: metald.v1.NetworkInterface
- (*IPv4Config)(nil), // 3: metald.v1.IPv4Config
- (*IPv6Config)(nil), // 4: metald.v1.IPv6Config
- (*RateLimit)(nil), // 5: metald.v1.RateLimit
- nil, // 6: metald.v1.NetworkInterface.OptionsEntry
-}
-var file_metald_v1_network_proto_depIdxs = []int32{
- 6, // 0: metald.v1.NetworkInterface.options:type_name -> metald.v1.NetworkInterface.OptionsEntry
- 3, // 1: metald.v1.NetworkInterface.ipv4_config:type_name -> metald.v1.IPv4Config
- 4, // 2: metald.v1.NetworkInterface.ipv6_config:type_name -> metald.v1.IPv6Config
- 0, // 3: metald.v1.NetworkInterface.mode:type_name -> metald.v1.NetworkMode
- 5, // 4: metald.v1.NetworkInterface.rx_rate_limit:type_name -> metald.v1.RateLimit
- 5, // 5: metald.v1.NetworkInterface.tx_rate_limit:type_name -> metald.v1.RateLimit
- 6, // [6:6] is the sub-list for method output_type
- 6, // [6:6] is the sub-list for method input_type
- 6, // [6:6] is the sub-list for extension type_name
- 6, // [6:6] is the sub-list for extension extendee
- 0, // [0:6] is the sub-list for field type_name
-}
-
-func init() { file_metald_v1_network_proto_init() }
-func file_metald_v1_network_proto_init() {
- if File_metald_v1_network_proto != nil {
- return
- }
- type x struct{}
- out := protoimpl.TypeBuilder{
- File: protoimpl.DescBuilder{
- GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
- RawDescriptor: unsafe.Slice(unsafe.StringData(file_metald_v1_network_proto_rawDesc), len(file_metald_v1_network_proto_rawDesc)),
- NumEnums: 1,
- NumMessages: 6,
- NumExtensions: 0,
- NumServices: 0,
- },
- GoTypes: file_metald_v1_network_proto_goTypes,
- DependencyIndexes: file_metald_v1_network_proto_depIdxs,
- EnumInfos: file_metald_v1_network_proto_enumTypes,
- MessageInfos: file_metald_v1_network_proto_msgTypes,
- }.Build()
- File_metald_v1_network_proto = out.File
- file_metald_v1_network_proto_goTypes = nil
- file_metald_v1_network_proto_depIdxs = nil
-}
diff --git a/go/gen/proto/metald/v1/storage.pb.go b/go/gen/proto/metald/v1/storage.pb.go
deleted file mode 100644
index 5b5110656d..0000000000
--- a/go/gen/proto/metald/v1/storage.pb.go
+++ /dev/null
@@ -1,253 +0,0 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// protoc-gen-go v1.36.8
-// protoc (unknown)
-// source: metald/v1/storage.proto
-
-package metaldv1
-
-import (
- protoreflect "google.golang.org/protobuf/reflect/protoreflect"
- protoimpl "google.golang.org/protobuf/runtime/protoimpl"
- reflect "reflect"
- sync "sync"
- unsafe "unsafe"
-)
-
-const (
- // Verify that this generated code is sufficiently up-to-date.
- _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
- // Verify that runtime/protoimpl is sufficiently up-to-date.
- _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
-)
-
-type StorageStats struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- BytesRead int64 `protobuf:"varint,1,opt,name=bytes_read,json=bytesRead,proto3" json:"bytes_read,omitempty"`
- BytesWritten int64 `protobuf:"varint,2,opt,name=bytes_written,json=bytesWritten,proto3" json:"bytes_written,omitempty"`
- ReadOperations int64 `protobuf:"varint,3,opt,name=read_operations,json=readOperations,proto3" json:"read_operations,omitempty"`
- WriteOperations int64 `protobuf:"varint,4,opt,name=write_operations,json=writeOperations,proto3" json:"write_operations,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *StorageStats) Reset() {
- *x = StorageStats{}
- mi := &file_metald_v1_storage_proto_msgTypes[0]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *StorageStats) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*StorageStats) ProtoMessage() {}
-
-func (x *StorageStats) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_storage_proto_msgTypes[0]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use StorageStats.ProtoReflect.Descriptor instead.
-func (*StorageStats) Descriptor() ([]byte, []int) {
- return file_metald_v1_storage_proto_rawDescGZIP(), []int{0}
-}
-
-func (x *StorageStats) GetBytesRead() int64 {
- if x != nil {
- return x.BytesRead
- }
- return 0
-}
-
-func (x *StorageStats) GetBytesWritten() int64 {
- if x != nil {
- return x.BytesWritten
- }
- return 0
-}
-
-func (x *StorageStats) GetReadOperations() int64 {
- if x != nil {
- return x.ReadOperations
- }
- return 0
-}
-
-func (x *StorageStats) GetWriteOperations() int64 {
- if x != nil {
- return x.WriteOperations
- }
- return 0
-}
-
-type StorageDevice struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // Unique identifier for this storage device
- Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
- // Path to the backing file or block device
- Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
- // Whether this device is read-only
- ReadOnly bool `protobuf:"varint,3,opt,name=read_only,json=readOnly,proto3" json:"read_only,omitempty"`
- // Whether this is the root/boot device
- IsRootDevice bool `protobuf:"varint,4,opt,name=is_root_device,json=isRootDevice,proto3" json:"is_root_device,omitempty"`
- // Storage interface type (virtio-blk, nvme, etc.)
- InterfaceType string `protobuf:"bytes,5,opt,name=interface_type,json=interfaceType,proto3" json:"interface_type,omitempty"`
- // Additional storage options
- Options map[string]string `protobuf:"bytes,6,rep,name=options,proto3" json:"options,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *StorageDevice) Reset() {
- *x = StorageDevice{}
- mi := &file_metald_v1_storage_proto_msgTypes[1]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *StorageDevice) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*StorageDevice) ProtoMessage() {}
-
-func (x *StorageDevice) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_storage_proto_msgTypes[1]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use StorageDevice.ProtoReflect.Descriptor instead.
-func (*StorageDevice) Descriptor() ([]byte, []int) {
- return file_metald_v1_storage_proto_rawDescGZIP(), []int{1}
-}
-
-func (x *StorageDevice) GetId() string {
- if x != nil {
- return x.Id
- }
- return ""
-}
-
-func (x *StorageDevice) GetPath() string {
- if x != nil {
- return x.Path
- }
- return ""
-}
-
-func (x *StorageDevice) GetReadOnly() bool {
- if x != nil {
- return x.ReadOnly
- }
- return false
-}
-
-func (x *StorageDevice) GetIsRootDevice() bool {
- if x != nil {
- return x.IsRootDevice
- }
- return false
-}
-
-func (x *StorageDevice) GetInterfaceType() string {
- if x != nil {
- return x.InterfaceType
- }
- return ""
-}
-
-func (x *StorageDevice) GetOptions() map[string]string {
- if x != nil {
- return x.Options
- }
- return nil
-}
-
-var File_metald_v1_storage_proto protoreflect.FileDescriptor
-
-const file_metald_v1_storage_proto_rawDesc = "" +
- "\n" +
- "\x17metald/v1/storage.proto\x12\tmetald.v1\"\xa6\x01\n" +
- "\fStorageStats\x12\x1d\n" +
- "\n" +
- "bytes_read\x18\x01 \x01(\x03R\tbytesRead\x12#\n" +
- "\rbytes_written\x18\x02 \x01(\x03R\fbytesWritten\x12'\n" +
- "\x0fread_operations\x18\x03 \x01(\x03R\x0ereadOperations\x12)\n" +
- "\x10write_operations\x18\x04 \x01(\x03R\x0fwriteOperations\"\x9a\x02\n" +
- "\rStorageDevice\x12\x0e\n" +
- "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
- "\x04path\x18\x02 \x01(\tR\x04path\x12\x1b\n" +
- "\tread_only\x18\x03 \x01(\bR\breadOnly\x12$\n" +
- "\x0eis_root_device\x18\x04 \x01(\bR\fisRootDevice\x12%\n" +
- "\x0einterface_type\x18\x05 \x01(\tR\rinterfaceType\x12?\n" +
- "\aoptions\x18\x06 \x03(\v2%.metald.v1.StorageDevice.OptionsEntryR\aoptions\x1a:\n" +
- "\fOptionsEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B:Z8github.com/unkeyed/unkey/go/gen/proto/metald/v1;metaldv1b\x06proto3"
-
-var (
- file_metald_v1_storage_proto_rawDescOnce sync.Once
- file_metald_v1_storage_proto_rawDescData []byte
-)
-
-func file_metald_v1_storage_proto_rawDescGZIP() []byte {
- file_metald_v1_storage_proto_rawDescOnce.Do(func() {
- file_metald_v1_storage_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_metald_v1_storage_proto_rawDesc), len(file_metald_v1_storage_proto_rawDesc)))
- })
- return file_metald_v1_storage_proto_rawDescData
-}
-
-var file_metald_v1_storage_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
-var file_metald_v1_storage_proto_goTypes = []any{
- (*StorageStats)(nil), // 0: metald.v1.StorageStats
- (*StorageDevice)(nil), // 1: metald.v1.StorageDevice
- nil, // 2: metald.v1.StorageDevice.OptionsEntry
-}
-var file_metald_v1_storage_proto_depIdxs = []int32{
- 2, // 0: metald.v1.StorageDevice.options:type_name -> metald.v1.StorageDevice.OptionsEntry
- 1, // [1:1] is the sub-list for method output_type
- 1, // [1:1] is the sub-list for method input_type
- 1, // [1:1] is the sub-list for extension type_name
- 1, // [1:1] is the sub-list for extension extendee
- 0, // [0:1] is the sub-list for field type_name
-}
-
-func init() { file_metald_v1_storage_proto_init() }
-func file_metald_v1_storage_proto_init() {
- if File_metald_v1_storage_proto != nil {
- return
- }
- type x struct{}
- out := protoimpl.TypeBuilder{
- File: protoimpl.DescBuilder{
- GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
- RawDescriptor: unsafe.Slice(unsafe.StringData(file_metald_v1_storage_proto_rawDesc), len(file_metald_v1_storage_proto_rawDesc)),
- NumEnums: 0,
- NumMessages: 3,
- NumExtensions: 0,
- NumServices: 0,
- },
- GoTypes: file_metald_v1_storage_proto_goTypes,
- DependencyIndexes: file_metald_v1_storage_proto_depIdxs,
- MessageInfos: file_metald_v1_storage_proto_msgTypes,
- }.Build()
- File_metald_v1_storage_proto = out.File
- file_metald_v1_storage_proto_goTypes = nil
- file_metald_v1_storage_proto_depIdxs = nil
-}
diff --git a/go/gen/proto/metald/v1/vm.pb.go b/go/gen/proto/metald/v1/vm.pb.go
deleted file mode 100644
index eed8b3d933..0000000000
--- a/go/gen/proto/metald/v1/vm.pb.go
+++ /dev/null
@@ -1,1925 +0,0 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// protoc-gen-go v1.36.8
-// protoc (unknown)
-// source: metald/v1/vm.proto
-
-package metaldv1
-
-import (
- protoreflect "google.golang.org/protobuf/reflect/protoreflect"
- protoimpl "google.golang.org/protobuf/runtime/protoimpl"
- timestamppb "google.golang.org/protobuf/types/known/timestamppb"
- reflect "reflect"
- sync "sync"
- unsafe "unsafe"
-)
-
-const (
- // Verify that this generated code is sufficiently up-to-date.
- _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
- // Verify that runtime/protoimpl is sufficiently up-to-date.
- _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
-)
-
-// VM lifecycle states
-type VmState int32
-
-const (
- VmState_VM_STATE_UNSPECIFIED VmState = 0
- VmState_VM_STATE_CREATED VmState = 1
- VmState_VM_STATE_RUNNING VmState = 2
- VmState_VM_STATE_PAUSED VmState = 3
- VmState_VM_STATE_SHUTDOWN VmState = 4
-)
-
-// Enum value maps for VmState.
-var (
- VmState_name = map[int32]string{
- 0: "VM_STATE_UNSPECIFIED",
- 1: "VM_STATE_CREATED",
- 2: "VM_STATE_RUNNING",
- 3: "VM_STATE_PAUSED",
- 4: "VM_STATE_SHUTDOWN",
- }
- VmState_value = map[string]int32{
- "VM_STATE_UNSPECIFIED": 0,
- "VM_STATE_CREATED": 1,
- "VM_STATE_RUNNING": 2,
- "VM_STATE_PAUSED": 3,
- "VM_STATE_SHUTDOWN": 4,
- }
-)
-
-func (x VmState) Enum() *VmState {
- p := new(VmState)
- *p = x
- return p
-}
-
-func (x VmState) String() string {
- return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
-}
-
-func (VmState) Descriptor() protoreflect.EnumDescriptor {
- return file_metald_v1_vm_proto_enumTypes[0].Descriptor()
-}
-
-func (VmState) Type() protoreflect.EnumType {
- return &file_metald_v1_vm_proto_enumTypes[0]
-}
-
-func (x VmState) Number() protoreflect.EnumNumber {
- return protoreflect.EnumNumber(x)
-}
-
-// Deprecated: Use VmState.Descriptor instead.
-func (VmState) EnumDescriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{0}
-}
-
-// Unified VM configuration that works across different hypervisors
-type VmConfig struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // CPU configuration
- VcpuCount uint32 `protobuf:"varint,1,opt,name=vcpu_count,json=vcpuCount,proto3" json:"vcpu_count,omitempty"`
- // Memory configuration
- MemorySizeMib uint64 `protobuf:"varint,2,opt,name=memory_size_mib,json=memorySizeMib,proto3" json:"memory_size_mib,omitempty"`
- // Boot configuration
- Boot string `protobuf:"bytes,3,opt,name=boot,proto3" json:"boot,omitempty"`
- // Network configuration
- NetworkConfig string `protobuf:"bytes,4,opt,name=network_config,json=networkConfig,proto3" json:"network_config,omitempty"`
- // Console configuration
- Console *ConsoleConfig `protobuf:"bytes,5,opt,name=console,proto3" json:"console,omitempty"`
- // Storage configuration
- Storage *StorageDevice `protobuf:"bytes,6,opt,name=storage,proto3" json:"storage,omitempty"`
- // VM Identifier
- Id string `protobuf:"bytes,7,opt,name=id,proto3" json:"id,omitempty"`
- // Metadata and labels
- Metadata map[string]string `protobuf:"bytes,8,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *VmConfig) Reset() {
- *x = VmConfig{}
- mi := &file_metald_v1_vm_proto_msgTypes[0]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *VmConfig) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*VmConfig) ProtoMessage() {}
-
-func (x *VmConfig) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[0]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use VmConfig.ProtoReflect.Descriptor instead.
-func (*VmConfig) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{0}
-}
-
-func (x *VmConfig) GetVcpuCount() uint32 {
- if x != nil {
- return x.VcpuCount
- }
- return 0
-}
-
-func (x *VmConfig) GetMemorySizeMib() uint64 {
- if x != nil {
- return x.MemorySizeMib
- }
- return 0
-}
-
-func (x *VmConfig) GetBoot() string {
- if x != nil {
- return x.Boot
- }
- return ""
-}
-
-func (x *VmConfig) GetNetworkConfig() string {
- if x != nil {
- return x.NetworkConfig
- }
- return ""
-}
-
-func (x *VmConfig) GetConsole() *ConsoleConfig {
- if x != nil {
- return x.Console
- }
- return nil
-}
-
-func (x *VmConfig) GetStorage() *StorageDevice {
- if x != nil {
- return x.Storage
- }
- return nil
-}
-
-func (x *VmConfig) GetId() string {
- if x != nil {
- return x.Id
- }
- return ""
-}
-
-func (x *VmConfig) GetMetadata() map[string]string {
- if x != nil {
- return x.Metadata
- }
- return nil
-}
-
-type ListVmsRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // Optional filter by state
- StateFilter []VmState `protobuf:"varint,1,rep,packed,name=state_filter,json=stateFilter,proto3,enum=metald.v1.VmState" json:"state_filter,omitempty"`
- // Pagination
- PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
- PageToken string `protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *ListVmsRequest) Reset() {
- *x = ListVmsRequest{}
- mi := &file_metald_v1_vm_proto_msgTypes[1]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *ListVmsRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*ListVmsRequest) ProtoMessage() {}
-
-func (x *ListVmsRequest) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[1]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use ListVmsRequest.ProtoReflect.Descriptor instead.
-func (*ListVmsRequest) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{1}
-}
-
-func (x *ListVmsRequest) GetStateFilter() []VmState {
- if x != nil {
- return x.StateFilter
- }
- return nil
-}
-
-func (x *ListVmsRequest) GetPageSize() int32 {
- if x != nil {
- return x.PageSize
- }
- return 0
-}
-
-func (x *ListVmsRequest) GetPageToken() string {
- if x != nil {
- return x.PageToken
- }
- return ""
-}
-
-type ListVmsResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Vms []*VmInfo `protobuf:"bytes,1,rep,name=vms,proto3" json:"vms,omitempty"`
- NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"`
- TotalCount uint64 `protobuf:"varint,3,opt,name=total_count,json=totalCount,proto3" json:"total_count,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *ListVmsResponse) Reset() {
- *x = ListVmsResponse{}
- mi := &file_metald_v1_vm_proto_msgTypes[2]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *ListVmsResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*ListVmsResponse) ProtoMessage() {}
-
-func (x *ListVmsResponse) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[2]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use ListVmsResponse.ProtoReflect.Descriptor instead.
-func (*ListVmsResponse) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{2}
-}
-
-func (x *ListVmsResponse) GetVms() []*VmInfo {
- if x != nil {
- return x.Vms
- }
- return nil
-}
-
-func (x *ListVmsResponse) GetNextPageToken() string {
- if x != nil {
- return x.NextPageToken
- }
- return ""
-}
-
-func (x *ListVmsResponse) GetTotalCount() uint64 {
- if x != nil {
- return x.TotalCount
- }
- return 0
-}
-
-// Request/Response messages
-type CreateVmRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // Generated unique identifier for the VM
- VmId string `protobuf:"bytes,1,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"`
- // VM configuration
- Config *VmConfig `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *CreateVmRequest) Reset() {
- *x = CreateVmRequest{}
- mi := &file_metald_v1_vm_proto_msgTypes[3]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *CreateVmRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*CreateVmRequest) ProtoMessage() {}
-
-func (x *CreateVmRequest) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[3]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use CreateVmRequest.ProtoReflect.Descriptor instead.
-func (*CreateVmRequest) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{3}
-}
-
-func (x *CreateVmRequest) GetVmId() string {
- if x != nil {
- return x.VmId
- }
- return ""
-}
-
-func (x *CreateVmRequest) GetConfig() *VmConfig {
- if x != nil {
- return x.Config
- }
- return nil
-}
-
-type Endpoint struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
- Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *Endpoint) Reset() {
- *x = Endpoint{}
- mi := &file_metald_v1_vm_proto_msgTypes[4]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *Endpoint) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*Endpoint) ProtoMessage() {}
-
-func (x *Endpoint) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[4]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use Endpoint.ProtoReflect.Descriptor instead.
-func (*Endpoint) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{4}
-}
-
-func (x *Endpoint) GetHost() string {
- if x != nil {
- return x.Host
- }
- return ""
-}
-
-func (x *Endpoint) GetPort() uint32 {
- if x != nil {
- return x.Port
- }
- return 0
-}
-
-type CreateVmResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // Current VM state after creation
- State VmState `protobuf:"varint,1,opt,name=state,proto3,enum=metald.v1.VmState" json:"state,omitempty"`
- // Endpoint is the host:port pair
- Endpoint *Endpoint `protobuf:"bytes,2,opt,name=endpoint,proto3" json:"endpoint,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *CreateVmResponse) Reset() {
- *x = CreateVmResponse{}
- mi := &file_metald_v1_vm_proto_msgTypes[5]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *CreateVmResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*CreateVmResponse) ProtoMessage() {}
-
-func (x *CreateVmResponse) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[5]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use CreateVmResponse.ProtoReflect.Descriptor instead.
-func (*CreateVmResponse) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{5}
-}
-
-func (x *CreateVmResponse) GetState() VmState {
- if x != nil {
- return x.State
- }
- return VmState_VM_STATE_UNSPECIFIED
-}
-
-func (x *CreateVmResponse) GetEndpoint() *Endpoint {
- if x != nil {
- return x.Endpoint
- }
- return nil
-}
-
-type DeleteVmRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- VmId string `protobuf:"bytes,1,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"`
- // Whether to force deletion even if VM is running
- Force bool `protobuf:"varint,2,opt,name=force,proto3" json:"force,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *DeleteVmRequest) Reset() {
- *x = DeleteVmRequest{}
- mi := &file_metald_v1_vm_proto_msgTypes[6]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *DeleteVmRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*DeleteVmRequest) ProtoMessage() {}
-
-func (x *DeleteVmRequest) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[6]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use DeleteVmRequest.ProtoReflect.Descriptor instead.
-func (*DeleteVmRequest) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{6}
-}
-
-func (x *DeleteVmRequest) GetVmId() string {
- if x != nil {
- return x.VmId
- }
- return ""
-}
-
-func (x *DeleteVmRequest) GetForce() bool {
- if x != nil {
- return x.Force
- }
- return false
-}
-
-type DeleteVmResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *DeleteVmResponse) Reset() {
- *x = DeleteVmResponse{}
- mi := &file_metald_v1_vm_proto_msgTypes[7]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *DeleteVmResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*DeleteVmResponse) ProtoMessage() {}
-
-func (x *DeleteVmResponse) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[7]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use DeleteVmResponse.ProtoReflect.Descriptor instead.
-func (*DeleteVmResponse) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{7}
-}
-
-func (x *DeleteVmResponse) GetSuccess() bool {
- if x != nil {
- return x.Success
- }
- return false
-}
-
-type BootVmRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- VmId string `protobuf:"bytes,1,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *BootVmRequest) Reset() {
- *x = BootVmRequest{}
- mi := &file_metald_v1_vm_proto_msgTypes[8]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *BootVmRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*BootVmRequest) ProtoMessage() {}
-
-func (x *BootVmRequest) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[8]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use BootVmRequest.ProtoReflect.Descriptor instead.
-func (*BootVmRequest) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{8}
-}
-
-func (x *BootVmRequest) GetVmId() string {
- if x != nil {
- return x.VmId
- }
- return ""
-}
-
-type BootVmResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- State VmState `protobuf:"varint,2,opt,name=state,proto3,enum=metald.v1.VmState" json:"state,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *BootVmResponse) Reset() {
- *x = BootVmResponse{}
- mi := &file_metald_v1_vm_proto_msgTypes[9]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *BootVmResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*BootVmResponse) ProtoMessage() {}
-
-func (x *BootVmResponse) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[9]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use BootVmResponse.ProtoReflect.Descriptor instead.
-func (*BootVmResponse) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{9}
-}
-
-func (x *BootVmResponse) GetState() VmState {
- if x != nil {
- return x.State
- }
- return VmState_VM_STATE_UNSPECIFIED
-}
-
-type ShutdownVmRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- VmId string `protobuf:"bytes,1,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"`
- // Whether to force shutdown (vs graceful)
- Force bool `protobuf:"varint,2,opt,name=force,proto3" json:"force,omitempty"`
- // Timeout for graceful shutdown (seconds)
- TimeoutSeconds int32 `protobuf:"varint,3,opt,name=timeout_seconds,json=timeoutSeconds,proto3" json:"timeout_seconds,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *ShutdownVmRequest) Reset() {
- *x = ShutdownVmRequest{}
- mi := &file_metald_v1_vm_proto_msgTypes[10]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *ShutdownVmRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*ShutdownVmRequest) ProtoMessage() {}
-
-func (x *ShutdownVmRequest) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[10]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use ShutdownVmRequest.ProtoReflect.Descriptor instead.
-func (*ShutdownVmRequest) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{10}
-}
-
-func (x *ShutdownVmRequest) GetVmId() string {
- if x != nil {
- return x.VmId
- }
- return ""
-}
-
-func (x *ShutdownVmRequest) GetForce() bool {
- if x != nil {
- return x.Force
- }
- return false
-}
-
-func (x *ShutdownVmRequest) GetTimeoutSeconds() int32 {
- if x != nil {
- return x.TimeoutSeconds
- }
- return 0
-}
-
-type ShutdownVmResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- State VmState `protobuf:"varint,2,opt,name=state,proto3,enum=metald.v1.VmState" json:"state,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *ShutdownVmResponse) Reset() {
- *x = ShutdownVmResponse{}
- mi := &file_metald_v1_vm_proto_msgTypes[11]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *ShutdownVmResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*ShutdownVmResponse) ProtoMessage() {}
-
-func (x *ShutdownVmResponse) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[11]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use ShutdownVmResponse.ProtoReflect.Descriptor instead.
-func (*ShutdownVmResponse) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{11}
-}
-
-func (x *ShutdownVmResponse) GetState() VmState {
- if x != nil {
- return x.State
- }
- return VmState_VM_STATE_UNSPECIFIED
-}
-
-type PauseVmRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- VmId string `protobuf:"bytes,1,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *PauseVmRequest) Reset() {
- *x = PauseVmRequest{}
- mi := &file_metald_v1_vm_proto_msgTypes[12]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *PauseVmRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*PauseVmRequest) ProtoMessage() {}
-
-func (x *PauseVmRequest) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[12]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use PauseVmRequest.ProtoReflect.Descriptor instead.
-func (*PauseVmRequest) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{12}
-}
-
-func (x *PauseVmRequest) GetVmId() string {
- if x != nil {
- return x.VmId
- }
- return ""
-}
-
-type PauseVmResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- State VmState `protobuf:"varint,2,opt,name=state,proto3,enum=metald.v1.VmState" json:"state,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *PauseVmResponse) Reset() {
- *x = PauseVmResponse{}
- mi := &file_metald_v1_vm_proto_msgTypes[13]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *PauseVmResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*PauseVmResponse) ProtoMessage() {}
-
-func (x *PauseVmResponse) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[13]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use PauseVmResponse.ProtoReflect.Descriptor instead.
-func (*PauseVmResponse) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{13}
-}
-
-func (x *PauseVmResponse) GetState() VmState {
- if x != nil {
- return x.State
- }
- return VmState_VM_STATE_UNSPECIFIED
-}
-
-type ResumeVmRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- VmId string `protobuf:"bytes,1,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *ResumeVmRequest) Reset() {
- *x = ResumeVmRequest{}
- mi := &file_metald_v1_vm_proto_msgTypes[14]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *ResumeVmRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*ResumeVmRequest) ProtoMessage() {}
-
-func (x *ResumeVmRequest) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[14]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use ResumeVmRequest.ProtoReflect.Descriptor instead.
-func (*ResumeVmRequest) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{14}
-}
-
-func (x *ResumeVmRequest) GetVmId() string {
- if x != nil {
- return x.VmId
- }
- return ""
-}
-
-type ResumeVmResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- State VmState `protobuf:"varint,2,opt,name=state,proto3,enum=metald.v1.VmState" json:"state,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *ResumeVmResponse) Reset() {
- *x = ResumeVmResponse{}
- mi := &file_metald_v1_vm_proto_msgTypes[15]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *ResumeVmResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*ResumeVmResponse) ProtoMessage() {}
-
-func (x *ResumeVmResponse) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[15]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use ResumeVmResponse.ProtoReflect.Descriptor instead.
-func (*ResumeVmResponse) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{15}
-}
-
-func (x *ResumeVmResponse) GetState() VmState {
- if x != nil {
- return x.State
- }
- return VmState_VM_STATE_UNSPECIFIED
-}
-
-type RebootVmRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- VmId string `protobuf:"bytes,1,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"`
- // Whether to force reboot (vs graceful)
- Force bool `protobuf:"varint,2,opt,name=force,proto3" json:"force,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *RebootVmRequest) Reset() {
- *x = RebootVmRequest{}
- mi := &file_metald_v1_vm_proto_msgTypes[16]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *RebootVmRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*RebootVmRequest) ProtoMessage() {}
-
-func (x *RebootVmRequest) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[16]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use RebootVmRequest.ProtoReflect.Descriptor instead.
-func (*RebootVmRequest) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{16}
-}
-
-func (x *RebootVmRequest) GetVmId() string {
- if x != nil {
- return x.VmId
- }
- return ""
-}
-
-func (x *RebootVmRequest) GetForce() bool {
- if x != nil {
- return x.Force
- }
- return false
-}
-
-type RebootVmResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- State VmState `protobuf:"varint,2,opt,name=state,proto3,enum=metald.v1.VmState" json:"state,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *RebootVmResponse) Reset() {
- *x = RebootVmResponse{}
- mi := &file_metald_v1_vm_proto_msgTypes[17]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *RebootVmResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*RebootVmResponse) ProtoMessage() {}
-
-func (x *RebootVmResponse) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[17]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use RebootVmResponse.ProtoReflect.Descriptor instead.
-func (*RebootVmResponse) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{17}
-}
-
-func (x *RebootVmResponse) GetState() VmState {
- if x != nil {
- return x.State
- }
- return VmState_VM_STATE_UNSPECIFIED
-}
-
-type GetVmInfoRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- VmId string `protobuf:"bytes,1,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *GetVmInfoRequest) Reset() {
- *x = GetVmInfoRequest{}
- mi := &file_metald_v1_vm_proto_msgTypes[18]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *GetVmInfoRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*GetVmInfoRequest) ProtoMessage() {}
-
-func (x *GetVmInfoRequest) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[18]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use GetVmInfoRequest.ProtoReflect.Descriptor instead.
-func (*GetVmInfoRequest) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{18}
-}
-
-func (x *GetVmInfoRequest) GetVmId() string {
- if x != nil {
- return x.VmId
- }
- return ""
-}
-
-type GetVmInfoResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- VmId string `protobuf:"bytes,1,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"`
- Config *VmConfig `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"`
- State VmState `protobuf:"varint,3,opt,name=state,proto3,enum=metald.v1.VmState" json:"state,omitempty"`
- Metrics *VmMetrics `protobuf:"bytes,4,opt,name=metrics,proto3" json:"metrics,omitempty"`
- // Backend-specific information
- BackendInfo map[string]string `protobuf:"bytes,5,rep,name=backend_info,json=backendInfo,proto3" json:"backend_info,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *GetVmInfoResponse) Reset() {
- *x = GetVmInfoResponse{}
- mi := &file_metald_v1_vm_proto_msgTypes[19]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *GetVmInfoResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*GetVmInfoResponse) ProtoMessage() {}
-
-func (x *GetVmInfoResponse) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[19]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use GetVmInfoResponse.ProtoReflect.Descriptor instead.
-func (*GetVmInfoResponse) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{19}
-}
-
-func (x *GetVmInfoResponse) GetVmId() string {
- if x != nil {
- return x.VmId
- }
- return ""
-}
-
-func (x *GetVmInfoResponse) GetConfig() *VmConfig {
- if x != nil {
- return x.Config
- }
- return nil
-}
-
-func (x *GetVmInfoResponse) GetState() VmState {
- if x != nil {
- return x.State
- }
- return VmState_VM_STATE_UNSPECIFIED
-}
-
-func (x *GetVmInfoResponse) GetMetrics() *VmMetrics {
- if x != nil {
- return x.Metrics
- }
- return nil
-}
-
-func (x *GetVmInfoResponse) GetBackendInfo() map[string]string {
- if x != nil {
- return x.BackendInfo
- }
- return nil
-}
-
-type VmMetrics struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // CPU usage percentage (0-100)
- CpuUsagePercent float64 `protobuf:"fixed64,1,opt,name=cpu_usage_percent,json=cpuUsagePercent,proto3" json:"cpu_usage_percent,omitempty"`
- // Memory usage in MiB
- MemoryUsageMib uint64 `protobuf:"varint,2,opt,name=memory_usage_mib,json=memoryUsageMib,proto3" json:"memory_usage_mib,omitempty"`
- // Network I/O statistics
- NetworkStats *NetworkStats `protobuf:"bytes,3,opt,name=network_stats,json=networkStats,proto3" json:"network_stats,omitempty"`
- // Storage I/O statistics
- StorageStats *StorageStats `protobuf:"bytes,4,opt,name=storage_stats,json=storageStats,proto3" json:"storage_stats,omitempty"`
- // VM uptime in seconds
- UptimeSeconds int64 `protobuf:"varint,5,opt,name=uptime_seconds,json=uptimeSeconds,proto3" json:"uptime_seconds,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *VmMetrics) Reset() {
- *x = VmMetrics{}
- mi := &file_metald_v1_vm_proto_msgTypes[20]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *VmMetrics) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*VmMetrics) ProtoMessage() {}
-
-func (x *VmMetrics) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[20]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use VmMetrics.ProtoReflect.Descriptor instead.
-func (*VmMetrics) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{20}
-}
-
-func (x *VmMetrics) GetCpuUsagePercent() float64 {
- if x != nil {
- return x.CpuUsagePercent
- }
- return 0
-}
-
-func (x *VmMetrics) GetMemoryUsageMib() uint64 {
- if x != nil {
- return x.MemoryUsageMib
- }
- return 0
-}
-
-func (x *VmMetrics) GetNetworkStats() *NetworkStats {
- if x != nil {
- return x.NetworkStats
- }
- return nil
-}
-
-func (x *VmMetrics) GetStorageStats() *StorageStats {
- if x != nil {
- return x.StorageStats
- }
- return nil
-}
-
-func (x *VmMetrics) GetUptimeSeconds() int64 {
- if x != nil {
- return x.UptimeSeconds
- }
- return 0
-}
-
-type VmInfo struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- VmId string `protobuf:"bytes,1,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"`
- State VmState `protobuf:"varint,2,opt,name=state,proto3,enum=metald.v1.VmState" json:"state,omitempty"`
- // Basic config info (subset of full config)
- VcpuCount int32 `protobuf:"varint,3,opt,name=vcpu_count,json=vcpuCount,proto3" json:"vcpu_count,omitempty"`
- MemorySizeMib uint64 `protobuf:"varint,4,opt,name=memory_size_mib,json=memorySizeMib,proto3" json:"memory_size_mib,omitempty"`
- // Creation and modification timestamps
- CreatedTimestamp *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=created_timestamp,json=createdTimestamp,proto3" json:"created_timestamp,omitempty"`
- ModifiedTimestamp *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=modified_timestamp,json=modifiedTimestamp,proto3" json:"modified_timestamp,omitempty"`
- // Metadata
- Metadata map[string]string `protobuf:"bytes,7,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- // deployment_id vm is attached to
- DeploymentId string `protobuf:"bytes,8,opt,name=deployment_id,json=deploymentId,proto3" json:"deployment_id,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *VmInfo) Reset() {
- *x = VmInfo{}
- mi := &file_metald_v1_vm_proto_msgTypes[21]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *VmInfo) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*VmInfo) ProtoMessage() {}
-
-func (x *VmInfo) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[21]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use VmInfo.ProtoReflect.Descriptor instead.
-func (*VmInfo) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{21}
-}
-
-func (x *VmInfo) GetVmId() string {
- if x != nil {
- return x.VmId
- }
- return ""
-}
-
-func (x *VmInfo) GetState() VmState {
- if x != nil {
- return x.State
- }
- return VmState_VM_STATE_UNSPECIFIED
-}
-
-func (x *VmInfo) GetVcpuCount() int32 {
- if x != nil {
- return x.VcpuCount
- }
- return 0
-}
-
-func (x *VmInfo) GetMemorySizeMib() uint64 {
- if x != nil {
- return x.MemorySizeMib
- }
- return 0
-}
-
-func (x *VmInfo) GetCreatedTimestamp() *timestamppb.Timestamp {
- if x != nil {
- return x.CreatedTimestamp
- }
- return nil
-}
-
-func (x *VmInfo) GetModifiedTimestamp() *timestamppb.Timestamp {
- if x != nil {
- return x.ModifiedTimestamp
- }
- return nil
-}
-
-func (x *VmInfo) GetMetadata() map[string]string {
- if x != nil {
- return x.Metadata
- }
- return nil
-}
-
-func (x *VmInfo) GetDeploymentId() string {
- if x != nil {
- return x.DeploymentId
- }
- return ""
-}
-
-type CpuConfig struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // Number of virtual CPUs to allocate at boot
- VcpuCount int32 `protobuf:"varint,1,opt,name=vcpu_count,json=vcpuCount,proto3" json:"vcpu_count,omitempty"`
- // Maximum number of virtual CPUs (for hotplug)
- MaxVcpuCount int32 `protobuf:"varint,2,opt,name=max_vcpu_count,json=maxVcpuCount,proto3" json:"max_vcpu_count,omitempty"`
- // CPU topology (optional)
- Topology *CpuTopology `protobuf:"bytes,3,opt,name=topology,proto3" json:"topology,omitempty"`
- // CPU features and model (backend-specific)
- Features map[string]string `protobuf:"bytes,4,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *CpuConfig) Reset() {
- *x = CpuConfig{}
- mi := &file_metald_v1_vm_proto_msgTypes[22]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *CpuConfig) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*CpuConfig) ProtoMessage() {}
-
-func (x *CpuConfig) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[22]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use CpuConfig.ProtoReflect.Descriptor instead.
-func (*CpuConfig) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{22}
-}
-
-func (x *CpuConfig) GetVcpuCount() int32 {
- if x != nil {
- return x.VcpuCount
- }
- return 0
-}
-
-func (x *CpuConfig) GetMaxVcpuCount() int32 {
- if x != nil {
- return x.MaxVcpuCount
- }
- return 0
-}
-
-func (x *CpuConfig) GetTopology() *CpuTopology {
- if x != nil {
- return x.Topology
- }
- return nil
-}
-
-func (x *CpuConfig) GetFeatures() map[string]string {
- if x != nil {
- return x.Features
- }
- return nil
-}
-
-type CpuTopology struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Sockets int32 `protobuf:"varint,1,opt,name=sockets,proto3" json:"sockets,omitempty"`
- CoresPerSocket int32 `protobuf:"varint,2,opt,name=cores_per_socket,json=coresPerSocket,proto3" json:"cores_per_socket,omitempty"`
- ThreadsPerCore int32 `protobuf:"varint,3,opt,name=threads_per_core,json=threadsPerCore,proto3" json:"threads_per_core,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *CpuTopology) Reset() {
- *x = CpuTopology{}
- mi := &file_metald_v1_vm_proto_msgTypes[23]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *CpuTopology) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*CpuTopology) ProtoMessage() {}
-
-func (x *CpuTopology) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[23]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use CpuTopology.ProtoReflect.Descriptor instead.
-func (*CpuTopology) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{23}
-}
-
-func (x *CpuTopology) GetSockets() int32 {
- if x != nil {
- return x.Sockets
- }
- return 0
-}
-
-func (x *CpuTopology) GetCoresPerSocket() int32 {
- if x != nil {
- return x.CoresPerSocket
- }
- return 0
-}
-
-func (x *CpuTopology) GetThreadsPerCore() int32 {
- if x != nil {
- return x.ThreadsPerCore
- }
- return 0
-}
-
-type MemoryConfig struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // Memory size in MiB
- MemorySizeMib int64 `protobuf:"varint,1,opt,name=memory_size_mib,json=memorySizeMib,proto3" json:"memory_size_mib,omitempty"`
- // Memory backing options (hugepages, etc.)
- Backing map[string]string `protobuf:"bytes,2,rep,name=backing,proto3" json:"backing,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *MemoryConfig) Reset() {
- *x = MemoryConfig{}
- mi := &file_metald_v1_vm_proto_msgTypes[24]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *MemoryConfig) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*MemoryConfig) ProtoMessage() {}
-
-func (x *MemoryConfig) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[24]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use MemoryConfig.ProtoReflect.Descriptor instead.
-func (*MemoryConfig) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{24}
-}
-
-func (x *MemoryConfig) GetMemorySizeMib() int64 {
- if x != nil {
- return x.MemorySizeMib
- }
- return 0
-}
-
-func (x *MemoryConfig) GetBacking() map[string]string {
- if x != nil {
- return x.Backing
- }
- return nil
-}
-
-type BootConfig struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // Path to kernel image
- KernelPath string `protobuf:"bytes,1,opt,name=kernel_path,json=kernelPath,proto3" json:"kernel_path,omitempty"`
- // Path to initial ramdisk (optional)
- InitrdPath string `protobuf:"bytes,2,opt,name=initrd_path,json=initrdPath,proto3" json:"initrd_path,omitempty"`
- // Kernel command line arguments
- KernelArgs string `protobuf:"bytes,3,opt,name=kernel_args,json=kernelArgs,proto3" json:"kernel_args,omitempty"`
- // Boot order and options
- BootOptions map[string]string `protobuf:"bytes,4,rep,name=boot_options,json=bootOptions,proto3" json:"boot_options,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *BootConfig) Reset() {
- *x = BootConfig{}
- mi := &file_metald_v1_vm_proto_msgTypes[25]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *BootConfig) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*BootConfig) ProtoMessage() {}
-
-func (x *BootConfig) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[25]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use BootConfig.ProtoReflect.Descriptor instead.
-func (*BootConfig) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{25}
-}
-
-func (x *BootConfig) GetKernelPath() string {
- if x != nil {
- return x.KernelPath
- }
- return ""
-}
-
-func (x *BootConfig) GetInitrdPath() string {
- if x != nil {
- return x.InitrdPath
- }
- return ""
-}
-
-func (x *BootConfig) GetKernelArgs() string {
- if x != nil {
- return x.KernelArgs
- }
- return ""
-}
-
-func (x *BootConfig) GetBootOptions() map[string]string {
- if x != nil {
- return x.BootOptions
- }
- return nil
-}
-
-type ConsoleConfig struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // Whether console is enabled
- Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
- // Console output destination (file path, pty, etc.)
- Output string `protobuf:"bytes,2,opt,name=output,proto3" json:"output,omitempty"`
- // Console input source (optional)
- Input string `protobuf:"bytes,3,opt,name=input,proto3" json:"input,omitempty"`
- // Console type (serial, virtio-console, etc.)
- ConsoleType string `protobuf:"bytes,4,opt,name=console_type,json=consoleType,proto3" json:"console_type,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *ConsoleConfig) Reset() {
- *x = ConsoleConfig{}
- mi := &file_metald_v1_vm_proto_msgTypes[26]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *ConsoleConfig) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*ConsoleConfig) ProtoMessage() {}
-
-func (x *ConsoleConfig) ProtoReflect() protoreflect.Message {
- mi := &file_metald_v1_vm_proto_msgTypes[26]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use ConsoleConfig.ProtoReflect.Descriptor instead.
-func (*ConsoleConfig) Descriptor() ([]byte, []int) {
- return file_metald_v1_vm_proto_rawDescGZIP(), []int{26}
-}
-
-func (x *ConsoleConfig) GetEnabled() bool {
- if x != nil {
- return x.Enabled
- }
- return false
-}
-
-func (x *ConsoleConfig) GetOutput() string {
- if x != nil {
- return x.Output
- }
- return ""
-}
-
-func (x *ConsoleConfig) GetInput() string {
- if x != nil {
- return x.Input
- }
- return ""
-}
-
-func (x *ConsoleConfig) GetConsoleType() string {
- if x != nil {
- return x.ConsoleType
- }
- return ""
-}
-
-var File_metald_v1_vm_proto protoreflect.FileDescriptor
-
-const file_metald_v1_vm_proto_rawDesc = "" +
- "\n" +
- "\x12metald/v1/vm.proto\x12\tmetald.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x17metald/v1/network.proto\x1a\x17metald/v1/storage.proto\"\x80\x03\n" +
- "\bVmConfig\x12\x1d\n" +
- "\n" +
- "vcpu_count\x18\x01 \x01(\rR\tvcpuCount\x12&\n" +
- "\x0fmemory_size_mib\x18\x02 \x01(\x04R\rmemorySizeMib\x12\x12\n" +
- "\x04boot\x18\x03 \x01(\tR\x04boot\x12%\n" +
- "\x0enetwork_config\x18\x04 \x01(\tR\rnetworkConfig\x122\n" +
- "\aconsole\x18\x05 \x01(\v2\x18.metald.v1.ConsoleConfigR\aconsole\x122\n" +
- "\astorage\x18\x06 \x01(\v2\x18.metald.v1.StorageDeviceR\astorage\x12\x0e\n" +
- "\x02id\x18\a \x01(\tR\x02id\x12=\n" +
- "\bmetadata\x18\b \x03(\v2!.metald.v1.VmConfig.MetadataEntryR\bmetadata\x1a;\n" +
- "\rMetadataEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x83\x01\n" +
- "\x0eListVmsRequest\x125\n" +
- "\fstate_filter\x18\x01 \x03(\x0e2\x12.metald.v1.VmStateR\vstateFilter\x12\x1b\n" +
- "\tpage_size\x18\x02 \x01(\x05R\bpageSize\x12\x1d\n" +
- "\n" +
- "page_token\x18\x03 \x01(\tR\tpageToken\"\x7f\n" +
- "\x0fListVmsResponse\x12#\n" +
- "\x03vms\x18\x01 \x03(\v2\x11.metald.v1.VmInfoR\x03vms\x12&\n" +
- "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\x12\x1f\n" +
- "\vtotal_count\x18\x03 \x01(\x04R\n" +
- "totalCount\"S\n" +
- "\x0fCreateVmRequest\x12\x13\n" +
- "\x05vm_id\x18\x01 \x01(\tR\x04vmId\x12+\n" +
- "\x06config\x18\x02 \x01(\v2\x13.metald.v1.VmConfigR\x06config\"2\n" +
- "\bEndpoint\x12\x12\n" +
- "\x04host\x18\x01 \x01(\tR\x04host\x12\x12\n" +
- "\x04port\x18\x02 \x01(\rR\x04port\"m\n" +
- "\x10CreateVmResponse\x12(\n" +
- "\x05state\x18\x01 \x01(\x0e2\x12.metald.v1.VmStateR\x05state\x12/\n" +
- "\bendpoint\x18\x02 \x01(\v2\x13.metald.v1.EndpointR\bendpoint\"<\n" +
- "\x0fDeleteVmRequest\x12\x13\n" +
- "\x05vm_id\x18\x01 \x01(\tR\x04vmId\x12\x14\n" +
- "\x05force\x18\x02 \x01(\bR\x05force\",\n" +
- "\x10DeleteVmResponse\x12\x18\n" +
- "\asuccess\x18\x01 \x01(\bR\asuccess\"$\n" +
- "\rBootVmRequest\x12\x13\n" +
- "\x05vm_id\x18\x01 \x01(\tR\x04vmId\":\n" +
- "\x0eBootVmResponse\x12(\n" +
- "\x05state\x18\x02 \x01(\x0e2\x12.metald.v1.VmStateR\x05state\"g\n" +
- "\x11ShutdownVmRequest\x12\x13\n" +
- "\x05vm_id\x18\x01 \x01(\tR\x04vmId\x12\x14\n" +
- "\x05force\x18\x02 \x01(\bR\x05force\x12'\n" +
- "\x0ftimeout_seconds\x18\x03 \x01(\x05R\x0etimeoutSeconds\">\n" +
- "\x12ShutdownVmResponse\x12(\n" +
- "\x05state\x18\x02 \x01(\x0e2\x12.metald.v1.VmStateR\x05state\"%\n" +
- "\x0ePauseVmRequest\x12\x13\n" +
- "\x05vm_id\x18\x01 \x01(\tR\x04vmId\";\n" +
- "\x0fPauseVmResponse\x12(\n" +
- "\x05state\x18\x02 \x01(\x0e2\x12.metald.v1.VmStateR\x05state\"&\n" +
- "\x0fResumeVmRequest\x12\x13\n" +
- "\x05vm_id\x18\x01 \x01(\tR\x04vmId\"<\n" +
- "\x10ResumeVmResponse\x12(\n" +
- "\x05state\x18\x02 \x01(\x0e2\x12.metald.v1.VmStateR\x05state\"<\n" +
- "\x0fRebootVmRequest\x12\x13\n" +
- "\x05vm_id\x18\x01 \x01(\tR\x04vmId\x12\x14\n" +
- "\x05force\x18\x02 \x01(\bR\x05force\"<\n" +
- "\x10RebootVmResponse\x12(\n" +
- "\x05state\x18\x02 \x01(\x0e2\x12.metald.v1.VmStateR\x05state\"'\n" +
- "\x10GetVmInfoRequest\x12\x13\n" +
- "\x05vm_id\x18\x01 \x01(\tR\x04vmId\"\xc1\x02\n" +
- "\x11GetVmInfoResponse\x12\x13\n" +
- "\x05vm_id\x18\x01 \x01(\tR\x04vmId\x12+\n" +
- "\x06config\x18\x02 \x01(\v2\x13.metald.v1.VmConfigR\x06config\x12(\n" +
- "\x05state\x18\x03 \x01(\x0e2\x12.metald.v1.VmStateR\x05state\x12.\n" +
- "\ametrics\x18\x04 \x01(\v2\x14.metald.v1.VmMetricsR\ametrics\x12P\n" +
- "\fbackend_info\x18\x05 \x03(\v2-.metald.v1.GetVmInfoResponse.BackendInfoEntryR\vbackendInfo\x1a>\n" +
- "\x10BackendInfoEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x84\x02\n" +
- "\tVmMetrics\x12*\n" +
- "\x11cpu_usage_percent\x18\x01 \x01(\x01R\x0fcpuUsagePercent\x12(\n" +
- "\x10memory_usage_mib\x18\x02 \x01(\x04R\x0ememoryUsageMib\x12<\n" +
- "\rnetwork_stats\x18\x03 \x01(\v2\x17.metald.v1.NetworkStatsR\fnetworkStats\x12<\n" +
- "\rstorage_stats\x18\x04 \x01(\v2\x17.metald.v1.StorageStatsR\fstorageStats\x12%\n" +
- "\x0euptime_seconds\x18\x05 \x01(\x03R\ruptimeSeconds\"\xc1\x03\n" +
- "\x06VmInfo\x12\x13\n" +
- "\x05vm_id\x18\x01 \x01(\tR\x04vmId\x12(\n" +
- "\x05state\x18\x02 \x01(\x0e2\x12.metald.v1.VmStateR\x05state\x12\x1d\n" +
- "\n" +
- "vcpu_count\x18\x03 \x01(\x05R\tvcpuCount\x12&\n" +
- "\x0fmemory_size_mib\x18\x04 \x01(\x04R\rmemorySizeMib\x12G\n" +
- "\x11created_timestamp\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\x10createdTimestamp\x12I\n" +
- "\x12modified_timestamp\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\x11modifiedTimestamp\x12;\n" +
- "\bmetadata\x18\a \x03(\v2\x1f.metald.v1.VmInfo.MetadataEntryR\bmetadata\x12#\n" +
- "\rdeployment_id\x18\b \x01(\tR\fdeploymentId\x1a;\n" +
- "\rMetadataEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x81\x02\n" +
- "\tCpuConfig\x12\x1d\n" +
- "\n" +
- "vcpu_count\x18\x01 \x01(\x05R\tvcpuCount\x12$\n" +
- "\x0emax_vcpu_count\x18\x02 \x01(\x05R\fmaxVcpuCount\x122\n" +
- "\btopology\x18\x03 \x01(\v2\x16.metald.v1.CpuTopologyR\btopology\x12>\n" +
- "\bfeatures\x18\x04 \x03(\v2\".metald.v1.CpuConfig.FeaturesEntryR\bfeatures\x1a;\n" +
- "\rFeaturesEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"{\n" +
- "\vCpuTopology\x12\x18\n" +
- "\asockets\x18\x01 \x01(\x05R\asockets\x12(\n" +
- "\x10cores_per_socket\x18\x02 \x01(\x05R\x0ecoresPerSocket\x12(\n" +
- "\x10threads_per_core\x18\x03 \x01(\x05R\x0ethreadsPerCore\"\xb2\x01\n" +
- "\fMemoryConfig\x12&\n" +
- "\x0fmemory_size_mib\x18\x01 \x01(\x03R\rmemorySizeMib\x12>\n" +
- "\abacking\x18\x02 \x03(\v2$.metald.v1.MemoryConfig.BackingEntryR\abacking\x1a:\n" +
- "\fBackingEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xfa\x01\n" +
- "\n" +
- "BootConfig\x12\x1f\n" +
- "\vkernel_path\x18\x01 \x01(\tR\n" +
- "kernelPath\x12\x1f\n" +
- "\vinitrd_path\x18\x02 \x01(\tR\n" +
- "initrdPath\x12\x1f\n" +
- "\vkernel_args\x18\x03 \x01(\tR\n" +
- "kernelArgs\x12I\n" +
- "\fboot_options\x18\x04 \x03(\v2&.metald.v1.BootConfig.BootOptionsEntryR\vbootOptions\x1a>\n" +
- "\x10BootOptionsEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"z\n" +
- "\rConsoleConfig\x12\x18\n" +
- "\aenabled\x18\x01 \x01(\bR\aenabled\x12\x16\n" +
- "\x06output\x18\x02 \x01(\tR\x06output\x12\x14\n" +
- "\x05input\x18\x03 \x01(\tR\x05input\x12!\n" +
- "\fconsole_type\x18\x04 \x01(\tR\vconsoleType*{\n" +
- "\aVmState\x12\x18\n" +
- "\x14VM_STATE_UNSPECIFIED\x10\x00\x12\x14\n" +
- "\x10VM_STATE_CREATED\x10\x01\x12\x14\n" +
- "\x10VM_STATE_RUNNING\x10\x02\x12\x13\n" +
- "\x0fVM_STATE_PAUSED\x10\x03\x12\x15\n" +
- "\x11VM_STATE_SHUTDOWN\x10\x04B:Z8github.com/unkeyed/unkey/go/gen/proto/metald/v1;metaldv1b\x06proto3"
-
-var (
- file_metald_v1_vm_proto_rawDescOnce sync.Once
- file_metald_v1_vm_proto_rawDescData []byte
-)
-
-func file_metald_v1_vm_proto_rawDescGZIP() []byte {
- file_metald_v1_vm_proto_rawDescOnce.Do(func() {
- file_metald_v1_vm_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_metald_v1_vm_proto_rawDesc), len(file_metald_v1_vm_proto_rawDesc)))
- })
- return file_metald_v1_vm_proto_rawDescData
-}
-
-var file_metald_v1_vm_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_metald_v1_vm_proto_msgTypes = make([]protoimpl.MessageInfo, 33)
-var file_metald_v1_vm_proto_goTypes = []any{
- (VmState)(0), // 0: metald.v1.VmState
- (*VmConfig)(nil), // 1: metald.v1.VmConfig
- (*ListVmsRequest)(nil), // 2: metald.v1.ListVmsRequest
- (*ListVmsResponse)(nil), // 3: metald.v1.ListVmsResponse
- (*CreateVmRequest)(nil), // 4: metald.v1.CreateVmRequest
- (*Endpoint)(nil), // 5: metald.v1.Endpoint
- (*CreateVmResponse)(nil), // 6: metald.v1.CreateVmResponse
- (*DeleteVmRequest)(nil), // 7: metald.v1.DeleteVmRequest
- (*DeleteVmResponse)(nil), // 8: metald.v1.DeleteVmResponse
- (*BootVmRequest)(nil), // 9: metald.v1.BootVmRequest
- (*BootVmResponse)(nil), // 10: metald.v1.BootVmResponse
- (*ShutdownVmRequest)(nil), // 11: metald.v1.ShutdownVmRequest
- (*ShutdownVmResponse)(nil), // 12: metald.v1.ShutdownVmResponse
- (*PauseVmRequest)(nil), // 13: metald.v1.PauseVmRequest
- (*PauseVmResponse)(nil), // 14: metald.v1.PauseVmResponse
- (*ResumeVmRequest)(nil), // 15: metald.v1.ResumeVmRequest
- (*ResumeVmResponse)(nil), // 16: metald.v1.ResumeVmResponse
- (*RebootVmRequest)(nil), // 17: metald.v1.RebootVmRequest
- (*RebootVmResponse)(nil), // 18: metald.v1.RebootVmResponse
- (*GetVmInfoRequest)(nil), // 19: metald.v1.GetVmInfoRequest
- (*GetVmInfoResponse)(nil), // 20: metald.v1.GetVmInfoResponse
- (*VmMetrics)(nil), // 21: metald.v1.VmMetrics
- (*VmInfo)(nil), // 22: metald.v1.VmInfo
- (*CpuConfig)(nil), // 23: metald.v1.CpuConfig
- (*CpuTopology)(nil), // 24: metald.v1.CpuTopology
- (*MemoryConfig)(nil), // 25: metald.v1.MemoryConfig
- (*BootConfig)(nil), // 26: metald.v1.BootConfig
- (*ConsoleConfig)(nil), // 27: metald.v1.ConsoleConfig
- nil, // 28: metald.v1.VmConfig.MetadataEntry
- nil, // 29: metald.v1.GetVmInfoResponse.BackendInfoEntry
- nil, // 30: metald.v1.VmInfo.MetadataEntry
- nil, // 31: metald.v1.CpuConfig.FeaturesEntry
- nil, // 32: metald.v1.MemoryConfig.BackingEntry
- nil, // 33: metald.v1.BootConfig.BootOptionsEntry
- (*StorageDevice)(nil), // 34: metald.v1.StorageDevice
- (*NetworkStats)(nil), // 35: metald.v1.NetworkStats
- (*StorageStats)(nil), // 36: metald.v1.StorageStats
- (*timestamppb.Timestamp)(nil), // 37: google.protobuf.Timestamp
-}
-var file_metald_v1_vm_proto_depIdxs = []int32{
- 27, // 0: metald.v1.VmConfig.console:type_name -> metald.v1.ConsoleConfig
- 34, // 1: metald.v1.VmConfig.storage:type_name -> metald.v1.StorageDevice
- 28, // 2: metald.v1.VmConfig.metadata:type_name -> metald.v1.VmConfig.MetadataEntry
- 0, // 3: metald.v1.ListVmsRequest.state_filter:type_name -> metald.v1.VmState
- 22, // 4: metald.v1.ListVmsResponse.vms:type_name -> metald.v1.VmInfo
- 1, // 5: metald.v1.CreateVmRequest.config:type_name -> metald.v1.VmConfig
- 0, // 6: metald.v1.CreateVmResponse.state:type_name -> metald.v1.VmState
- 5, // 7: metald.v1.CreateVmResponse.endpoint:type_name -> metald.v1.Endpoint
- 0, // 8: metald.v1.BootVmResponse.state:type_name -> metald.v1.VmState
- 0, // 9: metald.v1.ShutdownVmResponse.state:type_name -> metald.v1.VmState
- 0, // 10: metald.v1.PauseVmResponse.state:type_name -> metald.v1.VmState
- 0, // 11: metald.v1.ResumeVmResponse.state:type_name -> metald.v1.VmState
- 0, // 12: metald.v1.RebootVmResponse.state:type_name -> metald.v1.VmState
- 1, // 13: metald.v1.GetVmInfoResponse.config:type_name -> metald.v1.VmConfig
- 0, // 14: metald.v1.GetVmInfoResponse.state:type_name -> metald.v1.VmState
- 21, // 15: metald.v1.GetVmInfoResponse.metrics:type_name -> metald.v1.VmMetrics
- 29, // 16: metald.v1.GetVmInfoResponse.backend_info:type_name -> metald.v1.GetVmInfoResponse.BackendInfoEntry
- 35, // 17: metald.v1.VmMetrics.network_stats:type_name -> metald.v1.NetworkStats
- 36, // 18: metald.v1.VmMetrics.storage_stats:type_name -> metald.v1.StorageStats
- 0, // 19: metald.v1.VmInfo.state:type_name -> metald.v1.VmState
- 37, // 20: metald.v1.VmInfo.created_timestamp:type_name -> google.protobuf.Timestamp
- 37, // 21: metald.v1.VmInfo.modified_timestamp:type_name -> google.protobuf.Timestamp
- 30, // 22: metald.v1.VmInfo.metadata:type_name -> metald.v1.VmInfo.MetadataEntry
- 24, // 23: metald.v1.CpuConfig.topology:type_name -> metald.v1.CpuTopology
- 31, // 24: metald.v1.CpuConfig.features:type_name -> metald.v1.CpuConfig.FeaturesEntry
- 32, // 25: metald.v1.MemoryConfig.backing:type_name -> metald.v1.MemoryConfig.BackingEntry
- 33, // 26: metald.v1.BootConfig.boot_options:type_name -> metald.v1.BootConfig.BootOptionsEntry
- 27, // [27:27] is the sub-list for method output_type
- 27, // [27:27] is the sub-list for method input_type
- 27, // [27:27] is the sub-list for extension type_name
- 27, // [27:27] is the sub-list for extension extendee
- 0, // [0:27] is the sub-list for field type_name
-}
-
-func init() { file_metald_v1_vm_proto_init() }
-func file_metald_v1_vm_proto_init() {
- if File_metald_v1_vm_proto != nil {
- return
- }
- file_metald_v1_network_proto_init()
- file_metald_v1_storage_proto_init()
- type x struct{}
- out := protoimpl.TypeBuilder{
- File: protoimpl.DescBuilder{
- GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
- RawDescriptor: unsafe.Slice(unsafe.StringData(file_metald_v1_vm_proto_rawDesc), len(file_metald_v1_vm_proto_rawDesc)),
- NumEnums: 1,
- NumMessages: 33,
- NumExtensions: 0,
- NumServices: 0,
- },
- GoTypes: file_metald_v1_vm_proto_goTypes,
- DependencyIndexes: file_metald_v1_vm_proto_depIdxs,
- EnumInfos: file_metald_v1_vm_proto_enumTypes,
- MessageInfos: file_metald_v1_vm_proto_msgTypes,
- }.Build()
- File_metald_v1_vm_proto = out.File
- file_metald_v1_vm_proto_goTypes = nil
- file_metald_v1_vm_proto_depIdxs = nil
-}
diff --git a/go/go.mod b/go/go.mod
index eb91f81e39..cf3e8b2538 100644
--- a/go/go.mod
+++ b/go/go.mod
@@ -13,10 +13,8 @@ require (
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.1
github.com/btcsuite/btcutil v1.0.2
github.com/bufbuild/buf v1.57.0
- github.com/caarlos0/env/v11 v11.3.1
github.com/docker/docker v28.4.0+incompatible
github.com/docker/go-connections v0.6.0
- github.com/firecracker-microvm/firecracker-go-sdk v1.0.0
github.com/getkin/kin-openapi v0.133.0
github.com/go-acme/lego/v4 v4.25.2
github.com/go-redis/redis/v8 v8.11.5
@@ -27,45 +25,35 @@ require (
github.com/oapi-codegen/nullable v1.1.0
github.com/oapi-codegen/oapi-codegen/v2 v2.5.0
github.com/oasdiff/oasdiff v1.11.4
- github.com/oklog/ulid/v2 v2.1.1
github.com/pb33f/libopenapi v0.22.2
github.com/pb33f/libopenapi-validator v0.4.6
github.com/pressly/goose/v3 v3.25.0
github.com/prometheus/client_golang v1.23.2
github.com/redis/go-redis/v9 v9.9.0
github.com/shirou/gopsutil/v4 v4.25.7
+ github.com/spiffe/go-spiffe/v2 v2.6.0
github.com/sqlc-dev/plugin-sdk-go v1.23.0
github.com/sqlc-dev/sqlc v1.29.0
github.com/stretchr/testify v1.11.1
- github.com/unkeyed/unkey/go/deploy/pkg/health v0.0.0-20250912110941-10f390e49abc
- github.com/unkeyed/unkey/go/deploy/pkg/observability/interceptors v0.0.0-20250912110941-10f390e49abc
- github.com/unkeyed/unkey/go/deploy/pkg/tls v0.0.0-20250907150353-7f609cd7c284
- github.com/vishvananda/netlink v1.3.1
+ github.com/unkeyed/unkey/go/deploy/pkg/spiffe v0.0.0-20250926122029-55c3dd1334e0
go.opentelemetry.io/contrib/bridges/otelslog v0.11.0
go.opentelemetry.io/contrib/bridges/prometheus v0.61.0
- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
go.opentelemetry.io/contrib/processors/minsev v0.9.0
go.opentelemetry.io/otel v1.38.0
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0
- go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0
- go.opentelemetry.io/otel/exporters/prometheus v0.60.0
go.opentelemetry.io/otel/metric v1.38.0
go.opentelemetry.io/otel/sdk v1.38.0
go.opentelemetry.io/otel/sdk/log v0.12.2
go.opentelemetry.io/otel/sdk/metric v1.38.0
go.opentelemetry.io/otel/trace v1.38.0
golang.org/x/net v0.43.0
- golang.org/x/sync v0.17.0
- golang.org/x/sys v0.36.0
golang.org/x/text v0.29.0
- golang.org/x/time v0.12.0
google.golang.org/protobuf v1.36.8
k8s.io/api v0.34.1
k8s.io/apimachinery v0.34.1
k8s.io/client-go v0.34.1
- modernc.org/sqlite v1.39.0
)
require (
@@ -113,7 +101,6 @@ require (
github.com/alingse/nilnesserr v0.2.0 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
- github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/ashanbrown/forbidigo/v2 v2.1.0 // indirect
github.com/ashanbrown/makezero/v2 v2.0.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
@@ -159,10 +146,7 @@ require (
github.com/coder/websocket v1.8.12 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
- github.com/containerd/fifo v1.0.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.17.0 // indirect
- github.com/containernetworking/cni v1.0.1 // indirect
- github.com/containernetworking/plugins v1.0.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/cubicdaiya/gonp v1.0.4 // indirect
github.com/curioswitch/go-reassign v0.3.0 // indirect
@@ -178,7 +162,7 @@ require (
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dolthub/maphash v0.1.0 // indirect
- github.com/dprotaso/go-yit v0.0.0-20250513224043-18a80f8f6df4 // indirect
+ github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.8.4 // indirect
github.com/elastic/go-sysinfo v1.15.4 // indirect
@@ -192,7 +176,7 @@ require (
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/fzipp/gocyclo v0.6.0 // indirect
- github.com/gammazero/deque v1.0.0 // indirect
+ github.com/gammazero/deque v0.2.1 // indirect
github.com/ghostiam/protogetter v0.3.15 // indirect
github.com/go-chi/chi/v5 v5.2.2 // indirect
github.com/go-critic/go-critic v0.13.0 // indirect
@@ -201,17 +185,10 @@ require (
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
- github.com/go-ole/go-ole v1.3.0 // indirect
- github.com/go-openapi/analysis v0.21.2 // indirect
- github.com/go-openapi/errors v0.20.2 // indirect
+ github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
- github.com/go-openapi/loads v0.21.1 // indirect
- github.com/go-openapi/runtime v0.24.0 // indirect
- github.com/go-openapi/spec v0.20.4 // indirect
- github.com/go-openapi/strfmt v0.21.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
- github.com/go-openapi/validate v0.22.0 // indirect
github.com/go-toolsmith/astcast v1.1.0 // indirect
github.com/go-toolsmith/astcopy v1.1.0 // indirect
github.com/go-toolsmith/astequal v1.2.0 // indirect
@@ -246,11 +223,8 @@ require (
github.com/gostaticanalysis/comment v1.5.0 // indirect
github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
- github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
- github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect
- github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
@@ -284,7 +258,7 @@ require (
github.com/ldez/usetesting v0.5.0 // indirect
github.com/leonklingele/grouper v1.1.2 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
- github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
+ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/macabu/inamedparam v0.2.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/manuelarte/embeddedstructfieldcheck v0.3.0 // indirect
@@ -301,7 +275,6 @@ require (
github.com/microsoft/go-mssqldb v1.9.2 // indirect
github.com/miekg/dns v1.1.67 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
- github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@@ -318,10 +291,8 @@ require (
github.com/nunnatsa/ginkgolinter v0.20.0 // indirect
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
- github.com/oklog/ulid v1.3.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
- github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/paulmach/orb v0.11.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
@@ -335,11 +306,10 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/polyfloyd/go-errorlint v1.8.0 // indirect
- github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
+ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
- github.com/prometheus/otlptranslator v0.0.2 // indirect
- github.com/prometheus/procfs v0.17.0 // indirect
+ github.com/prometheus/procfs v0.16.1 // indirect
github.com/quasilyte/go-ruleguard v0.4.4 // indirect
github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect
github.com/quasilyte/gogrep v0.5.0 // indirect
@@ -378,7 +348,6 @@ require (
github.com/spf13/cobra v1.9.1 // indirect
github.com/spf13/pflag v1.0.7 // indirect
github.com/spf13/viper v1.20.1 // indirect
- github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect
github.com/stoewer/go-strcase v1.3.1 // indirect
@@ -398,16 +367,12 @@ require (
github.com/tomarrell/wrapcheck/v2 v2.11.0 // indirect
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d // indirect
- github.com/ugorji/go/codec v1.3.0 // indirect
github.com/ultraware/funlen v0.2.0 // indirect
github.com/ultraware/whitespace v0.2.0 // indirect
- github.com/unkeyed/unkey/go/deploy/pkg/spiffe v0.0.0-20250907150353-7f609cd7c284 // indirect
- github.com/unkeyed/unkey/go/deploy/pkg/tracing v0.0.0-20250907150353-7f609cd7c284 // indirect
github.com/uudashr/gocognit v1.2.0 // indirect
github.com/uudashr/iface v1.4.1 // indirect
github.com/vbatts/tar-split v0.12.1 // indirect
github.com/vertica/vertica-sql-go v1.3.3 // indirect
- github.com/vishvananda/netns v0.0.5 // indirect
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
github.com/wI2L/jsondiff v0.6.1 // indirect
github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 // indirect
@@ -434,10 +399,11 @@ require (
go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 // indirect
go.lsp.dev/protocol v0.12.0 // indirect
go.lsp.dev/uri v0.3.0 // indirect
- go.mongodb.org/mongo-driver v1.13.1 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
go.opentelemetry.io/otel/log v0.12.2 // indirect
- go.opentelemetry.io/proto/otlp v1.8.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/mock v0.6.0 // indirect
@@ -450,7 +416,10 @@ require (
golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
+ golang.org/x/sync v0.17.0 // indirect
+ golang.org/x/sys v0.35.0 // indirect
golang.org/x/term v0.34.0 // indirect
+ golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.36.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect
@@ -460,7 +429,6 @@ require (
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
- gotest.tools/v3 v3.5.2 // indirect
honnef.co/go/tools v0.6.1 // indirect
howett.net/plist v1.0.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
@@ -469,6 +437,7 @@ require (
modernc.org/libc v1.66.3 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
+ modernc.org/sqlite v1.38.2 // indirect
mvdan.cc/gofumpt v0.8.0 // indirect
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 // indirect
pluginrpc.com/pluginrpc v0.5.0 // indirect
@@ -478,8 +447,4 @@ require (
sigs.k8s.io/yaml v1.6.0 // indirect
)
-replace github.com/unkeyed/unkey/go/deploy/pkg/tls => ./deploy/pkg/tls
-
-replace github.com/unkeyed/unkey/go/deploy/pkg/spiffe => ./deploy/pkg/spiffe
-
tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen
diff --git a/go/go.sum b/go/go.sum
index 49241ed3af..213268846e 100644
--- a/go/go.sum
+++ b/go/go.sum
@@ -2,7 +2,6 @@
4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY=
4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU=
4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0=
-bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.8-20250718181942-e35f9b667443.1 h1:QQnbRXSal1cgFahMNsuYdMwxowDG8rQDGpUdnkLnN9c=
buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.8-20250718181942-e35f9b667443.1/go.mod h1:KcGAVqU99myIx6C8fGGsvhwYbVV0o/vVWjb6mmOnlfo=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.8-20250717185734-6c6e0d3c608e.1 h1:sjY1k5uszbIZfv11HO2keV4SLhNA47SabPO886v7Rvo=
@@ -31,28 +30,8 @@ cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
-cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
-cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
-cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
-cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
-cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
-cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
-cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.121.2 h1:v2qQpN6Dx9x2NmwrqlesOt3Ys4ol5/lFZ6Mg1B7OJCg=
cloud.google.com/go v0.121.2/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
-cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
-cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
-cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
-cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
-cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
-cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
-cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
-cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
-cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY=
codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ=
connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw=
@@ -63,7 +42,6 @@ dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKf
dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI=
dev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo=
dev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8=
@@ -78,7 +56,6 @@ github.com/Antonboom/nilnil v1.1.0 h1:jGxJxjgYS3VUUtOTNk8Z1icwT5ESpLH/426fjmQG+n
github.com/Antonboom/nilnil v1.1.0/go.mod h1:b7sAlogQjFa1wV8jUW3o4PMzDVFLbTux+xnQdvzdcIE=
github.com/Antonboom/testifylint v1.6.1 h1:6ZSytkFWatT8mwZlmRCHkWz1gPi+q6UBSbieji2Gj/o=
github.com/Antonboom/testifylint v1.6.1/go.mod h1:k+nEkathI2NFjKO6HvwmSrbzUcQ6FAnbZV+ZRrnXPLI=
-github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 h1:Wc1ml6QlJs2BHQ/9Bqu1jiyggbsSjramq2oUmp5WeIo=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
@@ -90,25 +67,13 @@ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZb
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww=
-github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
-github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
-github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
-github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
-github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
-github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
-github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
-github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
-github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
-github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
-github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ClickHouse/ch-go v0.67.0 h1:18MQF6vZHj+4/hTRaK7JbS/TIzn4I55wC+QzO24uiqc=
github.com/ClickHouse/ch-go v0.67.0/go.mod h1:2MSAeyVmgt+9a2k2SQPPG1b4qbTPzdGDpf1+bcHh+18=
github.com/ClickHouse/clickhouse-go/v2 v2.40.1 h1:PbwsHBgqXRydU7jKULD1C8CHmifczffvQqmFvltM2W4=
@@ -117,33 +82,10 @@ github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rW
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
-github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
-github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
-github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
-github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
-github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
-github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
-github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
-github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
-github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
-github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
-github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ=
-github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8=
-github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg=
-github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00=
-github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600=
-github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4=
-github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU=
-github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY=
-github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
-github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4=
github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo=
-github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
-github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
-github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/TwiN/go-color v1.4.1 h1:mqG0P/KBgHKVqmtL5ye7K0/Gr4l6hTksPgTgMk3mUzc=
github.com/TwiN/go-color v1.4.1/go.mod h1:WcPf/jtiW95WBIsEeY1Lc/b8aaWoiqQpu5cf8WFxu+s=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
@@ -155,12 +97,6 @@ github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsr
github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E=
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
-github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk=
github.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ=
github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q=
github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw=
@@ -176,16 +112,10 @@ github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUS
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
-github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
-github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
-github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
-github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
-github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/ashanbrown/forbidigo/v2 v2.1.0 h1:NAxZrWqNUQiDz19FKScQ/xvwzmij6BiOw3S0+QUQ+Hs=
github.com/ashanbrown/forbidigo/v2 v2.1.0/go.mod h1:0zZfdNAuZIL7rSComLGthgc/9/n2FqspBOH90xlCHdA=
github.com/ashanbrown/makezero/v2 v2.0.1 h1:r8GtKetWOgoJ4sLyUx97UTwyt2dO7WkGFHizn/Lo8TY=
github.com/ashanbrown/makezero/v2 v2.0.1/go.mod h1:kKU4IMxmYW1M4fiEHMb2vc5SFoPzXvgbMR9gIp5pjSw=
-github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
github.com/aws/aws-sdk-go-v2 v1.36.6 h1:zJqGjVbRdTPojeCGWn5IR5pbJwSQSBh5RWFTQcEQGdU=
github.com/aws/aws-sdk-go-v2 v1.36.6/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA=
@@ -227,20 +157,12 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
-github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
-github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w=
github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo=
-github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
-github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M=
github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k=
-github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ=
github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg=
github.com/bombsimon/wsl/v5 v5.1.1 h1:cQg5KJf9FlctAH4cpL9vLKnziYknoCMCdqXl0wjl72Q=
@@ -249,7 +171,6 @@ github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE=
github.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE=
github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg=
github.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s=
-github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
@@ -270,18 +191,12 @@ github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
github.com/bufbuild/protoplugin v0.0.0-20250218205857-750e09ce93e1 h1:V1xulAoqLqVg44rY97xOR+mQpD2N+GzhMHVwJ030WEU=
github.com/bufbuild/protoplugin v0.0.0-20250218205857-750e09ce93e1/go.mod h1:c5D8gWRIZ2HLWO3gXYTtUfw/hbJyD8xikv2ooPxnklQ=
-github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
-github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
-github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
-github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/butuzov/ireturn v0.4.0 h1:+s76bF/PfeKEdbG8b54aCocxXmi0wvYdOVsWxVO7n8E=
github.com/butuzov/ireturn v0.4.0/go.mod h1:ghI0FrCmap8pDWZwfPisFD1vEc56VKH4NpQUxDHta70=
github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc=
github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI=
-github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
-github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
github.com/catenacyber/perfsprint v0.9.1 h1:5LlTp4RwTooQjJCvGEFV6XksZvWE7wCOUvjD2z0vls0=
github.com/catenacyber/perfsprint v0.9.1/go.mod h1:q//VWC2fWbcdSLEY1R3l8n0zQCDPdE4IjZwyY1HMunM=
github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc=
@@ -291,7 +206,6 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -307,14 +221,9 @@ github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0G
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
-github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
-github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
-github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
-github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs=
github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
@@ -324,125 +233,19 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
-github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
-github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
-github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
-github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
-github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E=
-github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss=
-github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss=
-github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI=
-github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
-github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
-github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
-github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
-github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=
-github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU=
-github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
-github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
-github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
-github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
-github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
-github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
-github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
-github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
-github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
-github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
-github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
-github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
-github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
-github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ=
-github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU=
-github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI=
-github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s=
-github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=
-github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
-github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
-github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
-github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo=
-github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y=
-github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ=
-github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
-github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
-github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
-github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
-github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
-github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4=
-github.com/containerd/fifo v1.0.0 h1:6PirWBr9/L7GDamKr+XM0IeUFXu5mf3M/BPpH9gaLBU=
-github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4=
-github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU=
-github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk=
-github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
-github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
-github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g=
-github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok=
-github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok=
-github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0=
-github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA=
-github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow=
-github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
-github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c=
-github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY=
-github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY=
github.com/containerd/stargz-snapshotter/estargz v0.17.0 h1:+TyQIsR/zSFI1Rm31EQBwpAA1ovYgIKHy7kctL3sLcE=
github.com/containerd/stargz-snapshotter/estargz v0.17.0/go.mod h1:s06tWAiJcXQo9/8AReBCIo/QxcXFZ2n4qfsRnpl71SM=
-github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
-github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
-github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8=
-github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
-github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
-github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
-github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk=
-github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg=
-github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s=
-github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw=
-github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y=
-github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY=
-github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY=
-github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY=
-github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
-github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
-github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
-github.com/containernetworking/cni v1.0.1 h1:9OIL/sZmMYDBe+G8svzILAlulUpaDTUjeAbtH/JNLBo=
-github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y=
-github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM=
-github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8=
-github.com/containernetworking/plugins v1.0.1 h1:wwCfYbTCj5FC0EJgyzyjTXmqysOiJE9r712Z+2KVZAk=
-github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE=
-github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc=
-github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4=
-github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY=
-github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
-github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
-github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
-github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
-github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
-github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
-github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
-github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
-github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
-github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
-github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
-github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
@@ -450,11 +253,6 @@ github.com/cubicdaiya/gonp v1.0.4 h1:ky2uIAJh81WiLcGKBVD5R7KsM/36W6IqqTy6Bo6rGws
github.com/cubicdaiya/gonp v1.0.4/go.mod h1:iWGuP/7+JVTn02OWhRemVbMmG1DOUnmrGTYYACpOI0I=
github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs=
github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88=
-github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
-github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
-github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
-github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
-github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I=
github.com/daixiang0/gci v0.13.7 h1:+0bG5eK9vlI08J+J/NWGbWPTNiXPG4WhNLJOkSxWITQ=
github.com/daixiang0/gci v0.13.7/go.mod h1:812WVN6JLFY9S6Tv76twqmNqevN0pa3SX3nih0brVzQ=
github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY=
@@ -468,22 +266,14 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8=
github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY=
-github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
-github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
-github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
-github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/cli v28.3.3+incompatible h1:fp9ZHAr1WWPGdIWBM1b3zLtgCF+83gRdVMTJsUeiyAo=
github.com/docker/cli v28.3.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
-github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
-github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
-github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
@@ -492,23 +282,13 @@ github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqI
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
-github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
-github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
-github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
-github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
-github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
-github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
-github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58=
-github.com/dprotaso/go-yit v0.0.0-20250513224043-18a80f8f6df4 h1:JzpdVajvTuXQXL10D0vId1ZcW9alSJ3H0CnZczzz4ec=
-github.com/dprotaso/go-yit v0.0.0-20250513224043-18a80f8f6df4/go.mod h1:lHwJo6jMevQL9tNpW6vLyhkK13bYHBcoh9tUakMhbnE=
-github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w=
+github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
@@ -519,9 +299,6 @@ github.com/elastic/go-sysinfo v1.15.4/go.mod h1:ZBVXmqS368dOn/jvijV/zHLfakWTYHBZ
github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU=
github.com/elastic/go-windows v1.0.2 h1:yoLLsAsV5cfg9FLhZ9EXZ2n2sQFKeDYrHenkcivY4vI=
github.com/elastic/go-windows v1.0.2/go.mod h1:bGcDpBzXgYSqM0Gx3DM4+UxFj300SZLixie9u9ixLM8=
-github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
-github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
-github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -532,37 +309,28 @@ github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q=
github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A=
-github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
-github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
-github.com/firecracker-microvm/firecracker-go-sdk v1.0.0 h1:HTnxnX9pvQkQOHjv+TppzUyi2BNFL/7aegSlqIK/usY=
-github.com/firecracker-microvm/firecracker-go-sdk v1.0.0/go.mod h1:iXd7gqdwzvhB4VbNVMb70g/IY04fOuQbbBGM+PQEkgo=
github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E=
github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo=
-github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
-github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
-github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
-github.com/gammazero/deque v1.0.0 h1:LTmimT8H7bXkkCy6gZX7zNLtkbz4NdS2z8LZuor3j34=
-github.com/gammazero/deque v1.0.0/go.mod h1:iflpYvtGfM3U8S8j+sZEKIak3SAKYpA5/SQewgfXDKo=
-github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
+github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0=
+github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU=
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
-github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghostiam/protogetter v0.3.15 h1:1KF5sXel0HE48zh1/vn0Loiw25A9ApyseLzQuif1mLY=
github.com/ghostiam/protogetter v0.3.15/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA=
@@ -576,74 +344,29 @@ github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
-github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
-github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
-github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
-github.com/go-openapi/analysis v0.21.2 h1:hXFrOYFHUAMQdu6zwAiKKJHJQ8kqZs1ux/ru1P1wLJU=
-github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY=
-github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
-github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
-github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8=
-github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
-github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
-github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
-github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
-github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
-github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
-github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
-github.com/go-openapi/loads v0.21.1 h1:Wb3nVZpdEzDTcly8S4HMkey6fjARRzb7iEaySimlDW0=
-github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g=
-github.com/go-openapi/runtime v0.24.0 h1:vTgDijpGLCgJOJTdAp5kG+O+nRsVCbH417YQ3O0iZo0=
-github.com/go-openapi/runtime v0.24.0/go.mod h1:AKurw9fNre+h3ELZfk6ILsfvPN+bvvlaU/M9q/r9hpk=
-github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
-github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
-github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
-github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg=
-github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
-github.com/go-openapi/strfmt v0.21.2 h1:5NDNgadiX1Vhemth/TH4gCGopWSTdDjxl60H3B7f+os=
-github.com/go-openapi/strfmt v0.21.2/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
-github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
-github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
-github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg=
-github.com/go-openapi/validate v0.22.0 h1:b0QecH6VslW/TxtpKgzpO1SNG7GU2FsaqKdP1E2T50Y=
-github.com/go-openapi/validate v0.22.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg=
-github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 h1:dhy9OQKGBh4zVXbjwbxxHjRxMJtLXj3zfgpBYQaR4Q4=
-github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
@@ -673,46 +396,10 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY=
github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
-github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
-github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
-github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
-github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
-github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
-github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
-github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
-github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
-github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
-github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
-github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
-github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
-github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
-github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
-github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
-github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
-github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
-github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
-github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
-github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
-github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
-github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
-github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
-github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
-github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
-github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
-github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
-github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
-github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU=
-github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
-github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
-github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
@@ -724,22 +411,10 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2V
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
-github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@@ -773,8 +448,6 @@ github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2b
github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s=
github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM=
github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI=
github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
@@ -789,41 +462,20 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU=
github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a h1://KbezygeMJZCSHH+HgUZiTeSoiuFspbMg1ge+eFj18=
github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s=
github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
-github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
-github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
-github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
-github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=
github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc=
github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado=
@@ -837,50 +489,24 @@ github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW
github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M=
github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8=
github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs=
-github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 h1:cLN4IBkmkYZNnk7EAJ0BHIethd+J6LqxFNw5mSiI2bM=
-github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
-github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
-github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
-github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo=
github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw=
-github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
-github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
-github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
-github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
-github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
-github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
-github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
-github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
-github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
-github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
-github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
-github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@@ -903,35 +529,20 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8=
github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU=
-github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
-github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
-github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
-github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
-github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ=
github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY=
github.com/karamaru-alpha/copyloopvar v1.2.1 h1:wmZaZYIjnJ0b5UoKDjUHrikcV0zuPyyxI4SVplLd2CI=
github.com/karamaru-alpha/copyloopvar v1.2.1/go.mod h1:nFmMlFNlClC2BPvNaHMdkirmTJxVCY0lhxBtlfOypMM=
-github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
-github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
-github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
-github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M=
github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8=
@@ -939,24 +550,16 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE=
github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
-github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
-github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
@@ -984,15 +587,10 @@ github.com/lmittmann/tint v1.1.1 h1:xmmGuinUsCSxWdwH1OqMUQ4tzQsq3BdjJLAAmVKJ9Dw=
github.com/lmittmann/tint v1.1.1/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
-github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc=
-github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE=
github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U=
-github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
-github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
@@ -1004,30 +602,18 @@ github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s
github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE=
github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04=
github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc=
-github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
-github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
-github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4=
github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
-github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
-github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
-github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
-github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/maypok86/otter v1.2.4 h1:HhW1Pq6VdJkmWwcZZq19BlEQkHtI8xgsQzBVXJU0nfc=
github.com/maypok86/otter v1.2.4/go.mod h1:mKLfoI7v1HOmQMwFgX4QkRk23mX6ge3RDvjdHOWG4R4=
-github.com/mdlayher/socket v0.2.0/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
-github.com/mdlayher/vsock v1.1.1/go.mod h1:Y43jzcy7KM3QB+/FK15pfqGxDMCMzUXWegEfIbSM18U=
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
github.com/mfridman/xflag v0.1.0 h1:TWZrZwG1QklFX5S4j1vxfF1sZbZeZSGofMwPMLAF29M=
@@ -1038,35 +624,19 @@ github.com/microsoft/go-mssqldb v1.9.2 h1:nY8TmFMQOHpm2qVWo6y4I2mAmVdZqlGiMGAYt6
github.com/microsoft/go-mssqldb v1.9.2/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLYN3iFL90lQ+PA=
github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0=
github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
-github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
-github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
-github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
-github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
-github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
-github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
-github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
-github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
@@ -1077,20 +647,14 @@ github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKH
github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
-github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
-github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U=
github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
-github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg=
github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs=
github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk=
@@ -1098,9 +662,8 @@ github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3L
github.com/nunnatsa/ginkgolinter v0.20.0 h1:OmWLkAFO2HUTYcU6mprnKud1Ey5pVdiVNYGO5HVicx8=
github.com/nunnatsa/ginkgolinter v0.20.0/go.mod h1:dCIuFlTPfQerXgGUju3VygfAFPdC5aE1mdacCDKDJcQ=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
-github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
-github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/3KntMs=
github.com/oapi-codegen/nullable v1.1.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY=
github.com/oapi-codegen/oapi-codegen/v2 v2.5.0 h1:iJvF8SdB/3/+eGOXEpsWkD8FQAHj6mqkb6Fnsoc8MFU=
@@ -1111,61 +674,28 @@ github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//J
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
-github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
-github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
-github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
-github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
-github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
-github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
-github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
-github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
-github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
+github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
-github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
-github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
-github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
-github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
-github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
-github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
-github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
-github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
-github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
-github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
-github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0=
-github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
-github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE=
-github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
-github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
-github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
@@ -1180,15 +710,10 @@ github.com/pb33f/libopenapi v0.22.2 h1:ChXG911vrr24KE7wzIib3eL8Td73ANFCNSpWf1C9h
github.com/pb33f/libopenapi v0.22.2/go.mod h1:utT5sD2/mnN7YK68FfZT5yEPbI1wwRBpSS4Hi0oOrBU=
github.com/pb33f/libopenapi-validator v0.4.6 h1:ESkSxqFnb3LwLyDShOYe0PlGEM+pXXMI0271+Ib/pFE=
github.com/pb33f/libopenapi-validator v0.4.6/go.mod h1:NJaqqPxX2SX6kn+YTu+i588es/qIjP0vfGwK2NWg2Pw=
-github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
-github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
-github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
-github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pganalyze/pg_query_go/v6 v6.1.0 h1:jG5ZLhcVgL1FAw4C/0VNQaVmX1SUJx71wBGdtTtBvls=
github.com/pganalyze/pg_query_go/v6 v6.1.0/go.mod h1:nvTHIuoud6e1SfrUaFwHqT0i4b5Nr+1rPWVds3B5+50=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
@@ -1204,8 +729,6 @@ github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0 h1:W3rpAI3
github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0/go.mod h1:+8feuexTKcXHZF/dkDfvCwEyBAmgb4paFc3/WeYV2eE=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -1214,53 +737,22 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/polyfloyd/go-errorlint v1.8.0 h1:DL4RestQqRLr8U4LygLw8g2DX6RN1eBJOpa2mzsrl1Q=
github.com/polyfloyd/go-errorlint v1.8.0/go.mod h1:G2W0Q5roxbLCt0ZQbdoxQxXktTjwNyDbEaj3n7jvl4s=
-github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
-github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
-github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/pressly/goose/v3 v3.25.0 h1:6WeYhMWGRCzpyd89SpODFnCBCKz41KrVbRT58nVjGng=
github.com/pressly/goose/v3 v3.25.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY=
-github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
-github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
-github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
-github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
-github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
-github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
-github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
-github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ=
-github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI=
-github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
-github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
-github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
-github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
-github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
-github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
+github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/quasilyte/go-ruleguard v0.4.4 h1:53DncefIeLX3qEpjzlS1lyUmQoUEeOWPFWqaTJq9eAQ=
github.com/quasilyte/go-ruleguard v0.4.4/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE=
github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE=
@@ -1288,24 +780,17 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/riza-io/grpc-go v0.2.0 h1:2HxQKFVE7VuYstcJ8zqpN84VnAoJ4dCL6YFhJewNcHQ=
github.com/riza-io/grpc-go v0.2.0/go.mod h1:2bDvR9KkKC3KhtlSHfR3dAXjUMT86kg4UfWFyVGWqi8=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
-github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
-github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g=
github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I=
github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU=
github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ=
-github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
-github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0=
@@ -1316,9 +801,6 @@ github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tM
github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ=
github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ=
github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8=
-github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
-github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
-github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
github.com/securego/gosec/v2 v2.22.7 h1:8/9P+oTYI4yIpAzccQKVsg1/90Po+JzGtAhqoHImDeM=
github.com/securego/gosec/v2 v2.22.7/go.mod h1:510TFNDMrIPytokyHQAVLvPeDr41Yihn2ak8P+XQfNE=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
@@ -1336,56 +818,30 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
-github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
-github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
-github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
-github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
-github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
-github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE=
github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sonatard/noctx v0.4.0 h1:7MC/5Gg4SQ4lhLYR6mvOP6mQVSxCrdyiExo7atBs27o=
github.com/sonatard/noctx v0.4.0/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0=
github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=
-github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/speakeasy-api/jsonpath v0.6.2 h1:Mys71yd6u8kuowNCR0gCVPlVAHCmKtoGXYoAtcEbqXQ=
github.com/speakeasy-api/jsonpath v0.6.2/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw=
github.com/speakeasy-api/openapi-overlay v0.10.2 h1:VOdQ03eGKeiHnpb1boZCGm7x8Haj6gST0P3SGTX95GU=
github.com/speakeasy-api/openapi-overlay v0.10.2/go.mod h1:n0iOU7AqKpNFfEt6tq7qYITC4f0yzVVdFw0S7hukemg=
-github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
-github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
-github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
-github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
-github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
-github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
-github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
@@ -1398,19 +854,13 @@ github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YE
github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I=
github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4=
github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk=
-github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs=
github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
-github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
-github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@@ -1423,10 +873,6 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
-github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
-github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
-github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
-github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
github.com/tdakkota/asciicheck v0.4.1 h1:bm0tbcmi0jezRA2b5kg4ozmMuGAFotKI3RZfrhfovg8=
github.com/tdakkota/asciicheck v0.4.1/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8=
github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA=
@@ -1456,31 +902,20 @@ github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8O
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tomarrell/wrapcheck/v2 v2.11.0 h1:BJSt36snX9+4WTIXeJ7nvHBQBcm1h2SjQMSlmQ6aFSU=
github.com/tomarrell/wrapcheck/v2 v2.11.0/go.mod h1:wFL9pDWDAbXhhPZZt+nG8Fu+h29TtnZ2MW6Lx4BRXIU=
github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw=
github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d h1:dOMI4+zEbDI37KGb0TI44GUAwxHF9cMsIoDTJ7UmgfU=
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d/go.mod h1:l8xTsYB90uaVdMHXMCxKKLSgw5wLYBwBKKefNIUnm9s=
-github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
-github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
-github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
+github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
+github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI=
github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA=
github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g=
github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8=
-github.com/unkeyed/unkey/go/deploy/pkg/health v0.0.0-20250912110941-10f390e49abc h1:nSd7AMYoMLfTmO/sazmEbf63+9wZhuDEcl/VfZVnzsQ=
-github.com/unkeyed/unkey/go/deploy/pkg/health v0.0.0-20250912110941-10f390e49abc/go.mod h1:BUbSiLt6SORfOZILxBVFYzpzjMNhcp3SQbsxJJi+VnI=
-github.com/unkeyed/unkey/go/deploy/pkg/observability/interceptors v0.0.0-20250912110941-10f390e49abc h1:xPsj9Wq9yMlzSKqIVqtfJJaLuMsGJgmCEQnR3z/MGgo=
-github.com/unkeyed/unkey/go/deploy/pkg/observability/interceptors v0.0.0-20250912110941-10f390e49abc/go.mod h1:fMCBIGlww5sagSjSE/3K9Lt9fdw8g3UTgSWo0l9csN4=
-github.com/unkeyed/unkey/go/deploy/pkg/tracing v0.0.0-20250907150353-7f609cd7c284 h1:HQt4poG7OMv/GInpMYXWjFs0OxIYxxgTlPrhf5zW1D4=
-github.com/unkeyed/unkey/go/deploy/pkg/tracing v0.0.0-20250907150353-7f609cd7c284/go.mod h1:b5CBPBADyus6nHK0xfwNQPZwJxj/69OMlN/Uqkv5AzI=
-github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
-github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
-github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
-github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/unkeyed/unkey/go/deploy/pkg/spiffe v0.0.0-20250926122029-55c3dd1334e0 h1:nU2gzQBcBi/zKzRdZ4HLyNgybJ/1PNGy8v6DHv+gU6Q=
+github.com/unkeyed/unkey/go/deploy/pkg/spiffe v0.0.0-20250926122029-55c3dd1334e0/go.mod h1:fvqbcz5BljbvrZ1p8LWkNAty8vRsTo1aPLw1sOmLXEI=
github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA=
github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU=
github.com/uudashr/iface v1.4.1 h1:J16Xl1wyNX9ofhpHmQ9h9gk5rnv2A6lX/2+APLTo0zU=
@@ -1489,18 +924,6 @@ github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnn
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
github.com/vertica/vertica-sql-go v1.3.3 h1:fL+FKEAEy5ONmsvya2WH5T8bhkvY27y/Ik3ReR2T+Qw=
github.com/vertica/vertica-sql-go v1.3.3/go.mod h1:jnn2GFuv+O2Jcjktb7zyc4Utlbu9YVqpHH/lx63+1M4=
-github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
-github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
-github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
-github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
-github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
-github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
-github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
-github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
-github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
-github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
-github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
-github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk=
github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ=
github.com/wI2L/jsondiff v0.6.1 h1:ISZb9oNWbP64LHnu4AUhsMF5W0FIj5Ok3Krip9Shqpw=
@@ -1509,8 +932,6 @@ github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 h1:mJdDDPblDfP
github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07/go.mod h1:Ak17IJ037caFp4jpCw/iQQ7/W74Sqpb1YuKJU6HTKfM=
github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 h1:OvLBa8SqJnZ6P+mjlzc2K7PM22rRUPE1x32G9DTPrC4=
github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY=
-github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
-github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd h1:dLuIF2kX9c+KknGJUdJi1Il1SDiTSK158/BB9kdgAew=
github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd/go.mod h1:DbzwytT4g/odXquuOCqroKvtxxldI4nb3nuesHF/Exo=
github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0=
@@ -1518,21 +939,12 @@ github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4Z
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
-github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
-github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
-github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
-github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
-github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
-github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
-github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM=
github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
-github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM=
@@ -1557,9 +969,6 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
-github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
-github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
-github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo=
@@ -1574,10 +983,6 @@ go.augendre.info/arangolint v0.2.0 h1:2NP/XudpPmfBhQKX4rMk+zDYIj//qbt4hfZmSSTcpj
go.augendre.info/arangolint v0.2.0/go.mod h1:Vx4KSJwu48tkE+8uxuf0cbBnAPgnt8O1KWiT7bljq7w=
go.augendre.info/fatcontext v0.8.0 h1:2dfk6CQbDGeu1YocF59Za5Pia7ULeAM6friJ3LP7lmk=
go.augendre.info/fatcontext v0.8.0/go.mod h1:oVJfMgwngMsHO+KB2MdgzcO+RvtNdiCEOlWvSFtax/s=
-go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
-go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg=
go.lsp.dev/jsonrpc2 v0.10.0 h1:Pr/YcXJoEOTMc/b6OTmcR1DPJ3mSWl/SWiU1Cct6VmI=
go.lsp.dev/jsonrpc2 v0.10.0/go.mod h1:fmEzIdXPi/rf6d4uFcayi8HpFP1nBF99ERP1htC72Ac=
go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 h1:hCzQgh6UcwbKgNSRurYWSqh8MufqRRPODRBblutn4TE=
@@ -1586,25 +991,15 @@ go.lsp.dev/protocol v0.12.0 h1:tNprUI9klQW5FAFVM4Sa+AbPFuVQByWhP1ttNUAjIWg=
go.lsp.dev/protocol v0.12.0/go.mod h1:Qb11/HgZQ72qQbeyPfJbu3hZBH23s1sr4st8czGeDMQ=
go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo=
go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I=
-go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg=
-go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
-go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
-go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk=
-go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
-go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
-go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
-go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/bridges/otelslog v0.11.0 h1:EMIiYTms4Z4m3bBuKp1VmMNRLZcl6j4YbvOPL1IhlWo=
go.opentelemetry.io/contrib/bridges/otelslog v0.11.0/go.mod h1:DIEZmUR7tzuOOVUTDKvkGWtYWSHFV18Qg8+GMb8wPJw=
go.opentelemetry.io/contrib/bridges/prometheus v0.61.0 h1:RyrtJzu5MAmIcbRrwg75b+w3RlZCP0vJByDVzcpAe3M=
go.opentelemetry.io/contrib/bridges/prometheus v0.61.0/go.mod h1:tirr4p9NXbzjlbruiRGp53IzlYrDk5CO2fdHj0sSSaY=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
go.opentelemetry.io/contrib/processors/minsev v0.9.0 h1:eKlDcNp+GSygGk6PMJJyEdej+E1HteUy+KsY2YzaLbM=
go.opentelemetry.io/contrib/processors/minsev v0.9.0/go.mod h1:p8UCIy0r8hjrVD1Hb/4IUDSIpiZmlJl5DhCZOYgMWc4=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
@@ -1617,8 +1012,6 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZF
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
-go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo=
-go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk=
go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc=
go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
@@ -1634,10 +1027,8 @@ go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689Cbtr
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
-go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
-go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=
-go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
+go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@@ -1650,12 +1041,10 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
-go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
@@ -1664,23 +1053,10 @@ go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
@@ -1688,39 +1064,16 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
-golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b h1:KdrhdYPDUvJTvrDK9gdjfFd6JTk8vA1WJoldYSi0kHo=
golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b/go.mod h1:LKZHyeOpPuZcMgxeHjJp4p5yvxrCX1xDvH10zYHhjjQ=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
-golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -1735,47 +1088,22 @@ golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
@@ -1784,22 +1112,16 @@ golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1808,98 +1130,40 @@ golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
-golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -1908,69 +1172,26 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
-golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
-golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
-golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -2000,65 +1221,21 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
-google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
-google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
-google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
-google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
-google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
-google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1 h1:APHvLLYBhtZvsbnpkfknDZ7NyH4z5+ub/I0u8L3Oz6g=
google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1/go.mod h1:xUjFWUnWDpZ/C0Gu0qloASKFb6f8/QXiiXhSPFsD668=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
-google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
-google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
-google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
@@ -2071,7 +1248,6 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
@@ -2080,101 +1256,54 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
-gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
-gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
-gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
-gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
-gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
-gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
-gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
+gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
-gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
-gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI=
honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
-k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo=
-k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ=
-k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8=
k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM=
k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk=
-k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
-k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
-k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc=
k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
-k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU=
-k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM=
-k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q=
-k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y=
-k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k=
-k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0=
k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY=
k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8=
-k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk=
-k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI=
-k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM=
-k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM=
-k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI=
-k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI=
-k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc=
-k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
-k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
-k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
-k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
-k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
-k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM=
@@ -2197,8 +1326,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
-modernc.org/sqlite v1.39.0 h1:6bwu9Ooim0yVYA7IZn9demiQk/Ejp0BtTjBWFLymSeY=
-modernc.org/sqlite v1.39.0/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
+modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
+modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
@@ -2209,20 +1338,11 @@ mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 h1:WjUu4yQoT5BHT1w8Zu56SP836
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4/go.mod h1:rthT7OuvRbaGcd5ginj6dA2oLE7YNlta9qhBNNdCaLE=
pluginrpc.com/pluginrpc v0.5.0 h1:tOQj2D35hOmvHyPu8e7ohW2/QvAnEtKscy2IJYWQ2yo=
pluginrpc.com/pluginrpc v0.5.0/go.mod h1:UNWZ941hcVAoOZUn8YZsMmOZBzbUjQa3XMns8RQLp9o=
-rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
-rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
-rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
-sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
-sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
-sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
-sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
-sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
diff --git a/go/k8s/manifests/ctrl.yaml b/go/k8s/manifests/ctrl.yaml
index 72055d1cd0..5f19b426c6 100644
--- a/go/k8s/manifests/ctrl.yaml
+++ b/go/k8s/manifests/ctrl.yaml
@@ -51,8 +51,8 @@ spec:
# Control Plane Specific
- name: UNKEY_AUTH_TOKEN
value: "dev-auth-token"
- - name: UNKEY_METALD_ADDRESS
- value: "http://metald-placeholder:8080"
+ - name: UNKEY_KRANE_ADDRESS
+ value: "http://krane:8080"
- name: UNKEY_SPIFFE_SOCKET_PATH
value: "/var/lib/spire/agent/agent.sock"
# Vault Configuration (required)
@@ -69,8 +69,7 @@ spec:
# Additional Configuration
- name: UNKEY_ACME_ENABLED
value: "false"
- - name: UNKEY_METALD_ADDRESS
- value: "http://metald:8080"
+
- name: UNKEY_DEFAULT_DOMAIN
value: "unkey.local"
command: ["/unkey", "run", "ctrl"]
diff --git a/go/k8s/manifests/krane.yaml b/go/k8s/manifests/krane.yaml
new file mode 100644
index 0000000000..7861440edd
--- /dev/null
+++ b/go/k8s/manifests/krane.yaml
@@ -0,0 +1,72 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: krane
+ namespace: unkey
+ labels:
+ app: krane
+ component: krane
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: krane
+ template:
+ metadata:
+ labels:
+ app: krane
+ component: krane
+ # Add gateway label for potential service discovery
+ unkey.component: gateway
+ spec:
+ serviceAccountName: unkey-serviceaccount
+ containers:
+ - name: krane
+ image: unkey-krane:latest
+ imagePullPolicy: Never # Use local images
+ command: ["/unkey", "run", "krane"]
+ ports:
+ - containerPort: 8080
+ name: http
+ protocol: TCP
+ env:
+ # Server configuration
+ - name: UNKEY_HTTP_PORT
+ value: "8080"
+ - name: UNKEY_DEPLOYMENT_EVICTION_TTL
+ value: "2h"
+
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: krane
+ namespace: unkey
+ labels:
+ app: krane
+ component: krane
+spec:
+ type: ClusterIP
+ ports:
+ - port: 8080
+ targetPort: 8080
+ name: http
+ protocol: TCP
+ selector:
+ app: krane
+
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: krane-data-pvc
+ namespace: unkey
+ labels:
+ app: krane
+ component: krane
+spec:
+ accessModes:
+ - ReadWriteOnce
+ resources:
+ requests:
+ storage: 1Gi
diff --git a/go/k8s/manifests/metald.yaml b/go/k8s/manifests/metald.yaml
deleted file mode 100644
index dea6e4ac34..0000000000
--- a/go/k8s/manifests/metald.yaml
+++ /dev/null
@@ -1,112 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: metald
- namespace: unkey
- labels:
- app: metald
- component: metald
-spec:
- replicas: 1
- selector:
- matchLabels:
- app: metald
- template:
- metadata:
- labels:
- app: metald
- component: metald
- # Add gateway label for potential service discovery
- unkey.component: gateway
- spec:
- serviceAccountName: unkey-serviceaccount
- containers:
- - name: metald
- image: unkey-metald:latest
- imagePullPolicy: Never # Use local images
- command: ["/unkey", "run", "metald"]
- ports:
- - containerPort: 8080
- name: http
- protocol: TCP
- env:
- # Server configuration
- - name: UNKEY_METALD_ADDRESS
- value: "0.0.0.0"
- - name: UNKEY_METALD_PORT
- value: "8080"
-
- # Backend configuration - use Kubernetes backend for K8s deployment
- - name: UNKEY_METALD_BACKEND
- value: "k8s"
-
- # Network configuration - use K8s service CIDR for OrbStack
- - name: UNKEY_METALD_NETWORK_CIDR
- value: "192.168.194.128/25" # OrbStack's default service CIDR
-
- - name: UNKEY_METALD_SUBNET_PREFIX
- value: "29" # Smaller subnets (8 IPs each) to fit in /25 range
-
- # Database configuration (metald needs its own database for VM state)
- - name: UNKEY_METALD_DATABASE_DIR
- value: "/var/lib/metald"
-
- # Asset manager configuration (disabled for local development)
- - name: UNKEY_METALD_ASSETMANAGER_ENABLED
- value: "false"
-
- # Billing configuration (disabled for local development)
- - name: UNKEY_METALD_BILLING_ENABLED
- value: "false"
-
- # TLS configuration (disabled for local development)
- - name: UNKEY_METALD_TLS_MODE
- value: "disabled"
-
- # OpenTelemetry configuration
- - name: UNKEY_METALD_OTEL_ENABLED
- value: "false"
-
- volumeMounts:
- - name: metald-data
- mountPath: /var/lib/metald
-
- volumes:
- - name: metald-data
- persistentVolumeClaim:
- claimName: metald-data-pvc
-
----
-apiVersion: v1
-kind: Service
-metadata:
- name: metald
- namespace: unkey
- labels:
- app: metald
- component: metald
-spec:
- type: ClusterIP
- ports:
- - port: 8080
- targetPort: 8080
- name: http
- protocol: TCP
- selector:
- app: metald
-
----
-apiVersion: v1
-kind: PersistentVolumeClaim
-metadata:
- name: metald-data-pvc
- namespace: unkey
- labels:
- app: metald
- component: metald
-spec:
- accessModes:
- - ReadWriteOnce
- resources:
- requests:
- storage: 1Gi
diff --git a/go/k8s/manifests/rbac.yaml b/go/k8s/manifests/rbac.yaml
index 884ec06cea..4626aca689 100644
--- a/go/k8s/manifests/rbac.yaml
+++ b/go/k8s/manifests/rbac.yaml
@@ -6,16 +6,16 @@ metadata:
namespace: unkey
labels:
app: unkey
- component: metald
+ component: krane
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
- name: metald-vm-manager
+ name: krane-vm-manager
labels:
app: unkey
- component: metald
+ component: krane
rules:
# Pod management for VMs
- apiGroups: [""]
@@ -28,11 +28,10 @@ rules:
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# Job management for VM workloads
- - apiGroups: ["batch"]
- resources: ["jobs"]
+ - apiGroups: ["apps"]
+ resources: ["statefulsets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
-
# Events access for debugging
- apiGroups: [""]
resources: ["events"]
@@ -42,15 +41,15 @@ rules:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
- name: metald-vm-manager-binding
+ name: krane-vm-manager-binding
labels:
app: unkey
- component: metald
+ component: krane
subjects:
- kind: ServiceAccount
name: unkey-serviceaccount
namespace: unkey
roleRef:
kind: ClusterRole
- name: metald-vm-manager
+ name: krane-vm-manager
apiGroup: rbac.authorization.k8s.io
diff --git a/go/pkg/cli/command.go b/go/pkg/cli/command.go
index e0f528cafb..c9e6a629f5 100644
--- a/go/pkg/cli/command.go
+++ b/go/pkg/cli/command.go
@@ -8,6 +8,7 @@ import (
"fmt"
"os"
"strings"
+ "time"
)
var (
@@ -71,6 +72,33 @@ func (c *Command) RequireString(name string) string {
return sf.Value()
}
+// Duration returns the value of a duration flag by name
+// Returns 0 if flag doesn't exist or isn't a DurationFlag
+func (c *Command) Duration(name string) time.Duration {
+ if flag, ok := c.flagMap[name]; ok {
+ if sf, ok := flag.(*DurationFlag); ok {
+ return sf.Value()
+ }
+ }
+ return time.Duration(0)
+}
+
+// RequireDuration returns the value of a duration flag by name
+// Panics if flag doesn't exist or isn't a DurationFlag
+func (c *Command) RequireDuration(name string) time.Duration {
+ flag, ok := c.flagMap[name]
+ if !ok {
+ panic(c.newFlagNotFoundError(name))
+ }
+
+ sf, ok := flag.(*DurationFlag)
+ if !ok {
+ panic(c.newWrongFlagTypeError(name, flag, "DurationFlag"))
+ }
+
+ return sf.Value()
+}
+
// Bool returns the value of a boolean flag by name
// Returns false if flag doesn't exist or isn't a BoolFlag
func (c *Command) Bool(name string) bool {
diff --git a/go/pkg/cli/docs.go b/go/pkg/cli/docs.go
index e59888591c..dfc48230dc 100644
--- a/go/pkg/cli/docs.go
+++ b/go/pkg/cli/docs.go
@@ -336,6 +336,8 @@ func (c *Command) getTypeString(flag Flag) string {
switch flag.(type) {
case *StringFlag:
return "string"
+ case *DurationFlag:
+ return "duration"
case *BoolFlag:
return "boolean"
case *IntFlag:
diff --git a/go/pkg/cli/flag.go b/go/pkg/cli/flag.go
index 269f94a452..16956a75b9 100644
--- a/go/pkg/cli/flag.go
+++ b/go/pkg/cli/flag.go
@@ -6,6 +6,7 @@ import (
"os"
"strconv"
"strings"
+ "time"
)
var (
@@ -79,6 +80,36 @@ func (f *StringFlag) Value() string { return f.value }
// HasValue returns true if the flag has any non-empty value or came from environment
func (f *StringFlag) HasValue() bool { return f.value != "" || f.hasEnvValue }
+// DurationFlag represents a duration command line flag
+type DurationFlag struct {
+ baseFlag
+ value time.Duration // Current value
+ hasEnvValue bool // Track if value came from environment
+}
+
+// Parse sets the flag value from a string
+func (f *DurationFlag) Parse(value string) error {
+ // Run validation if provided
+ if f.validate != nil {
+ if err := f.validate(value); err != nil {
+ return newValidationError(f.name, err)
+ }
+ }
+ var err error
+ f.value, err = time.ParseDuration(value)
+ if err != nil {
+ return newValidationError(f.name, err)
+ }
+ f.set = true
+ return nil
+}
+
+// Value returns the current string value
+func (f *DurationFlag) Value() time.Duration { return f.value }
+
+// HasValue returns true if the flag has any non-empty value or came from environment
+func (f *DurationFlag) HasValue() bool { return f.value != 0 || f.hasEnvValue }
+
// BoolFlag represents a boolean command line flag
type BoolFlag struct {
baseFlag
@@ -407,6 +438,46 @@ func String(name, usage string, opts ...FlagOption) *StringFlag {
return flag
}
+// Duration creates a new string flag with optional configuration
+func Duration(name, usage string, opts ...FlagOption) *DurationFlag {
+ flag := &DurationFlag{
+ baseFlag: baseFlag{
+ name: name,
+ usage: usage,
+ required: false, // Default to not required
+ },
+ value: 0, // Default to empty string
+ }
+
+ // Apply options
+ for _, opt := range opts {
+ opt(flag)
+ }
+
+ // Check environment variable for default value if specified
+ if flag.envVar != "" {
+ if envValue := os.Getenv(flag.envVar); envValue != "" {
+ // Apply validation to environment variable values
+ if flag.validate != nil {
+ if err := flag.validate(envValue); err != nil {
+ Exit(fmt.Sprintf("Environment variable error: validation failed for %s=%q: %v",
+ flag.envVar, envValue, err), 1)
+ }
+ }
+ var err error
+ flag.value, err = time.ParseDuration(envValue)
+ if err != nil {
+ Exit(fmt.Sprintf("Environment variable error: parsing duration failed for %s=%q: %v",
+ flag.envVar, envValue, err), 1)
+ }
+ flag.hasEnvValue = true
+ // Don't mark as explicitly set - this is from environment
+ }
+ }
+
+ return flag
+}
+
// Bool creates a new boolean flag with optional configuration
func Bool(name, usage string, opts ...FlagOption) *BoolFlag {
flag := &BoolFlag{
diff --git a/go/deploy/pkg/spiffe/client.go b/go/pkg/spiffe/client.go
similarity index 100%
rename from go/deploy/pkg/spiffe/client.go
rename to go/pkg/spiffe/client.go
diff --git a/go/proto/assetmanagerd/v1/asset.proto b/go/proto/assetmanagerd/v1/asset.proto
deleted file mode 100644
index 0e1ed8b043..0000000000
--- a/go/proto/assetmanagerd/v1/asset.proto
+++ /dev/null
@@ -1,279 +0,0 @@
-syntax = "proto3";
-
-package assetmanagerd.v1;
-
-option go_package = "github.com/unkeyed/unkey/go/gen/proto/assetmanagerd/v1;assetmanagerdv1";
-
-// AssetManagerService manages VM assets (kernels, rootfs images) across the
-// infrastructure
-service AssetManagerService {
- // Upload and register an asset in one operation
- rpc UploadAsset(stream UploadAssetRequest) returns (UploadAssetResponse);
-
- // Register a new asset (called by builderd after creating images)
- rpc RegisterAsset(RegisterAssetRequest) returns (RegisterAssetResponse);
-
- // Get asset location and metadata
- rpc GetAsset(GetAssetRequest) returns (GetAssetResponse);
-
- // List available assets with filtering
- rpc ListAssets(ListAssetsRequest) returns (ListAssetsResponse);
-
- // Mark asset as in-use (reference counting for GC)
- rpc AcquireAsset(AcquireAssetRequest) returns (AcquireAssetResponse);
-
- // Release asset reference (decrements ref count)
- rpc ReleaseAsset(ReleaseAssetRequest) returns (ReleaseAssetResponse);
-
- // Delete an asset (only if ref count is 0)
- rpc DeleteAsset(DeleteAssetRequest) returns (DeleteAssetResponse);
-
- // Trigger garbage collection of unused assets
- rpc GarbageCollect(GarbageCollectRequest) returns (GarbageCollectResponse);
-
- // Pre-stage assets for a specific host/jailer
- rpc PrepareAssets(PrepareAssetsRequest) returns (PrepareAssetsResponse);
-
- // Query assets with automatic build triggering if not found
- // This is the enhanced version of ListAssets that supports automatic asset creation
- rpc QueryAssets(QueryAssetsRequest) returns (QueryAssetsResponse);
-}
-
-enum AssetType {
- ASSET_TYPE_UNSPECIFIED = 0;
- ASSET_TYPE_KERNEL = 1;
- ASSET_TYPE_ROOTFS = 2;
- ASSET_TYPE_INITRD = 3;
- ASSET_TYPE_DISK_IMAGE = 4;
-}
-
-enum AssetStatus {
- ASSET_STATUS_UNSPECIFIED = 0;
- ASSET_STATUS_UPLOADING = 1;
- ASSET_STATUS_AVAILABLE = 2;
- ASSET_STATUS_DELETING = 3;
- ASSET_STATUS_ERROR = 4;
-}
-
-enum StorageBackend {
- STORAGE_BACKEND_UNSPECIFIED = 0;
- STORAGE_BACKEND_LOCAL = 1;
- STORAGE_BACKEND_S3 = 2;
- STORAGE_BACKEND_HTTP = 3;
- STORAGE_BACKEND_NFS = 4;
-}
-
-message Asset {
- string id = 1;
- string name = 2;
- AssetType type = 3;
- AssetStatus status = 4;
-
- // Storage information
- StorageBackend backend = 5;
- string location = 6; // Path or URL depending on backend
- int64 size_bytes = 7;
- string checksum = 8; // SHA256
-
- // Metadata
- map labels = 9;
- string created_by = 10; // e.g., "builderd", "manual"
- int64 created_at = 11; // Unix timestamp
- int64 last_accessed_at = 12;
-
- // Reference counting for GC
- int32 reference_count = 13;
-
- // Build information (if created by builderd)
- string build_id = 14;
- string source_image = 15;
-}
-
-message UploadAssetRequest {
- oneof data {
- UploadAssetMetadata metadata = 1;
- bytes chunk = 2;
- }
-}
-
-message UploadAssetMetadata {
- string name = 1;
- AssetType type = 2;
- int64 size_bytes = 3;
- map labels = 4;
- string created_by = 5;
- string build_id = 6;
- string source_image = 7;
- string id = 8; // Optional: specific asset ID to use
-}
-
-message UploadAssetResponse {
- Asset asset = 1;
-}
-
-message RegisterAssetRequest {
- string name = 1;
- AssetType type = 2;
- StorageBackend backend = 3;
- string location = 4;
- int64 size_bytes = 5;
- string checksum = 6;
- map labels = 7;
- string created_by = 8;
-
- // Optional build information
- string build_id = 9;
- string source_image = 10;
-
- // Optional: specific asset ID to use (if not provided, one will be generated)
- string id = 11;
-}
-
-message RegisterAssetResponse {
- Asset asset = 1;
-}
-
-message GetAssetRequest {
- string id = 1;
-
- // If true, ensures asset is available locally (downloads if needed)
- bool ensure_local = 2;
-}
-
-message GetAssetResponse {
- Asset asset = 1;
-
- // Local path if ensure_local was true
- string local_path = 2;
-}
-
-message ListAssetsRequest {
- // Filter by type
- AssetType type = 1;
-
- // Filter by status
- AssetStatus status = 2;
-
- // Filter by labels (all must match)
- map label_selector = 3;
-
- // Pagination
- int32 page_size = 4;
- string page_token = 5;
-}
-
-message ListAssetsResponse {
- repeated Asset assets = 1;
- string next_page_token = 2;
-}
-
-message AcquireAssetRequest {
- string asset_id = 1;
- string acquired_by = 2; // e.g., "vm-123"
- int64 ttl_seconds = 3; // Optional auto-release after TTL
-}
-
-message AcquireAssetResponse {
- Asset asset = 1;
- string lease_id = 2; // Use this for release
-}
-
-message ReleaseAssetRequest {
- string lease_id = 1;
-}
-
-message ReleaseAssetResponse {
- Asset asset = 1;
-}
-
-message DeleteAssetRequest {
- string id = 1;
- bool force = 2; // Delete even if ref count > 0
-}
-
-message DeleteAssetResponse {
- bool deleted = 1;
- string message = 2;
-}
-
-message GarbageCollectRequest {
- // Delete assets not accessed in this many seconds
- int64 max_age_seconds = 1;
-
- // Delete assets with 0 references
- bool delete_unreferenced = 2;
-
- // Dry run - just return what would be deleted
- bool dry_run = 3;
-}
-
-message GarbageCollectResponse {
- repeated Asset deleted_assets = 1;
- int64 bytes_freed = 2;
-}
-
-message PrepareAssetsRequest {
- repeated string asset_ids = 1;
- string target_path = 2; // e.g., jailer chroot path
- string prepared_for = 3; // e.g., "vm-123"
-}
-
-message PrepareAssetsResponse {
- map asset_paths = 1; // asset_id -> local path
-}
-
-// QueryAssetsRequest is similar to ListAssetsRequest but with build options
-message QueryAssetsRequest {
- // Filter by type
- AssetType type = 1;
-
- // Filter by status
- AssetStatus status = 2;
-
- // Filter by labels (all must match)
- map label_selector = 3;
-
- // Pagination
- int32 page_size = 4;
- string page_token = 5;
-
- // Build options - if asset not found and these are set, trigger build
- BuildOptions build_options = 6;
-}
-
-// BuildOptions controls automatic asset creation
-message BuildOptions {
- // Enable automatic building if assets don't exist
- bool enable_auto_build = 1;
-
- // Wait for build completion before returning
- bool wait_for_completion = 2;
-
- // Timeout for build operation (seconds)
- int32 build_timeout_seconds = 3;
-
- // Additional labels to add to the built asset
- map build_labels = 4;
-
- // Suggested asset ID to use when registering the built asset
- // This allows the caller to know the asset ID before it's built
- string suggested_asset_id = 5;
-}
-
-// QueryAssetsResponse includes build information if builds were triggered
-message QueryAssetsResponse {
- repeated Asset assets = 1;
- string next_page_token = 2;
-
- // Information about any builds that were triggered
- repeated BuildInfo triggered_builds = 3;
-}
-
-// BuildInfo provides information about triggered builds
-message BuildInfo {
- string build_id = 1;
- string docker_image = 2;
- string status = 3; // "pending", "building", "completed", "failed"
- string error_message = 4;
- string asset_id = 5; // Asset ID if build completed and asset was registered
-}
diff --git a/go/proto/billaged/v1/billing.proto b/go/proto/billaged/v1/billing.proto
deleted file mode 100644
index 190da66781..0000000000
--- a/go/proto/billaged/v1/billing.proto
+++ /dev/null
@@ -1,74 +0,0 @@
-syntax = "proto3";
-
-package billaged.v1;
-
-import "google/protobuf/timestamp.proto";
-
-option go_package = "github.com/unkeyed/unkey/go/gen/proto/billaged/v1;billagedv1";
-
-service BillingService {
- rpc SendMetricsBatch(SendMetricsBatchRequest) returns (SendMetricsBatchResponse);
- rpc SendHeartbeat(SendHeartbeatRequest) returns (SendHeartbeatResponse);
- rpc NotifyVmStarted(NotifyVmStartedRequest) returns (NotifyVmStartedResponse);
- rpc NotifyVmStopped(NotifyVmStoppedRequest) returns (NotifyVmStoppedResponse);
- rpc NotifyPossibleGap(NotifyPossibleGapRequest) returns (NotifyPossibleGapResponse);
-}
-
-message VMMetrics {
- google.protobuf.Timestamp timestamp = 1;
- int64 cpu_time_nanos = 2;
- int64 memory_usage_bytes = 3;
- int64 disk_read_bytes = 4;
- int64 disk_write_bytes = 5;
- int64 network_rx_bytes = 6;
- int64 network_tx_bytes = 7;
-}
-
-message SendMetricsBatchRequest {
- string vm_id = 1;
- string customer_id = 2;
- repeated VMMetrics metrics = 3;
-}
-
-message SendMetricsBatchResponse {
- bool success = 1;
- string message = 2;
-}
-
-message SendHeartbeatRequest {
- string instance_id = 1;
- repeated string active_vms = 2;
-}
-
-message SendHeartbeatResponse {
- bool success = 1;
-}
-
-message NotifyVmStartedRequest {
- string vm_id = 1;
- string customer_id = 2;
- int64 start_time = 3;
-}
-
-message NotifyVmStartedResponse {
- bool success = 1;
-}
-
-message NotifyVmStoppedRequest {
- string vm_id = 1;
- int64 stop_time = 2;
-}
-
-message NotifyVmStoppedResponse {
- bool success = 1;
-}
-
-message NotifyPossibleGapRequest {
- string vm_id = 1;
- int64 last_sent = 2;
- int64 resume_time = 3;
-}
-
-message NotifyPossibleGapResponse {
- bool success = 1;
-}
diff --git a/go/proto/builderd/v1/builder.proto b/go/proto/builderd/v1/builder.proto
deleted file mode 100644
index ff4efd07f8..0000000000
--- a/go/proto/builderd/v1/builder.proto
+++ /dev/null
@@ -1,364 +0,0 @@
-syntax = "proto3";
-
-package builderd.v1;
-
-import "google/protobuf/timestamp.proto";
-
-option go_package = "github.com/unkeyed/unkey/go/gen/proto/builderd/v1;builderdv1";
-
-// BuilderService provides multi-tenant build execution for various source types
-service BuilderService {
- // Create a new build job
- rpc CreateBuild(CreateBuildRequest) returns (CreateBuildResponse);
-
- // Get build status and progress
- rpc GetBuild(GetBuildRequest) returns (GetBuildResponse);
-
- // List builds with filtering (tenant-scoped)
- rpc ListBuilds(ListBuildsRequest) returns (ListBuildsResponse);
-
- // Cancel a running build
- rpc CancelBuild(CancelBuildRequest) returns (CancelBuildResponse);
-
- // Delete a build and its artifacts
- rpc DeleteBuild(DeleteBuildRequest) returns (DeleteBuildResponse);
-
- // Stream build logs in real-time
- rpc StreamBuildLogs(StreamBuildLogsRequest) returns (stream StreamBuildLogsResponse);
-
- // Get build statistics
- rpc GetBuildStats(GetBuildStatsRequest) returns (GetBuildStatsResponse);
-}
-
-// Build job lifecycle states
-enum BuildState {
- BUILD_STATE_UNSPECIFIED = 0;
- BUILD_STATE_PENDING = 1; // Job queued
- BUILD_STATE_PULLING = 2; // Pulling Docker image or source
- BUILD_STATE_EXTRACTING = 3; // Extracting/preparing source
- BUILD_STATE_BUILDING = 4; // Building rootfs
- BUILD_STATE_OPTIMIZING = 5; // Applying optimizations
- BUILD_STATE_COMPLETED = 6; // Build successful
- BUILD_STATE_FAILED = 7; // Build failed
- BUILD_STATE_CANCELLED = 8; // Build cancelled
- BUILD_STATE_CLEANING = 9; // Cleaning up resources
-}
-
-// Tenant service tiers
-enum TenantTier {
- TENANT_TIER_UNSPECIFIED = 0;
- TENANT_TIER_FREE = 1; // Limited resources
- TENANT_TIER_PRO = 2; // Standard resources
- TENANT_TIER_ENTERPRISE = 3; // Higher limits + isolation
- TENANT_TIER_DEDICATED = 4; // Dedicated infrastructure
-}
-
-// Init process strategies for microVMs
-enum InitStrategy {
- INIT_STRATEGY_UNSPECIFIED = 0;
- INIT_STRATEGY_TINI = 1; // Use tini as init (recommended)
- INIT_STRATEGY_DIRECT = 2; // Direct exec (risky)
- INIT_STRATEGY_CUSTOM = 3; // Custom init script
-}
-
-// Build source types - extensible for future build types
-message BuildSource {
- oneof source_type {
- DockerImageSource docker_image = 1;
- GitRepositorySource git_repository = 2;
- ArchiveSource archive = 3;
- // Future: nix_flake = 4, buildpack = 5, etc.
- }
-}
-
-// Docker image extraction (first implementation)
-message DockerImageSource {
- string image_uri = 1; // "ghcr.io/unkeyed/unkey:f4cfee5"
- DockerAuth auth = 2; // Registry authentication
- repeated string pull_tags = 3; // Additional tags to consider
-}
-
-message DockerAuth {
- string username = 1;
- string password = 2;
- string token = 3;
- string registry = 4;
-}
-
-// Git repository builds (future)
-message GitRepositorySource {
- string repository_url = 1; // "https://github.com/unkeyed/unkey"
- string ref = 2; // branch/tag/commit
- string build_context = 3; // subdirectory if needed
- GitAuth auth = 4;
-}
-
-message GitAuth {
- string username = 1;
- string password = 2;
- string ssh_key = 3;
- string token = 4;
-}
-
-// Archive builds (future)
-message ArchiveSource {
- string archive_url = 1; // URL to tar.gz, zip, etc.
- string archive_type = 2; // "tar.gz", "zip"
- string build_context = 3; // subdirectory in archive
-}
-
-// Build target types - extensible
-message BuildTarget {
- oneof target_type {
- MicroVMRootfs microvm_rootfs = 1;
- ContainerImage container_image = 2;
- // Future: wasm_module = 3, lambda_layer = 4, etc.
- }
-}
-
-// MicroVM rootfs (our focus)
-message MicroVMRootfs {
- InitStrategy init_strategy = 1;
- RuntimeConfig runtime_config = 2;
- OptimizationSettings optimization = 3;
- repeated string preserve_paths = 4;
-}
-
-// Container image (future)
-message ContainerImage {
- string base_image = 1;
- repeated string layers = 2;
-}
-
-message RuntimeConfig {
- repeated string command = 1; // Override CMD
- repeated string entrypoint = 2; // Override ENTRYPOINT
- string working_dir = 3; // Override WORKDIR
- map environment = 4; // Environment variables
- repeated string exposed_ports = 5; // Ports to expose
-}
-
-message OptimizationSettings {
- bool strip_debug_symbols = 1; // Strip debug info
- bool compress_binaries = 2; // Compress with UPX
- bool remove_docs = 3; // Remove documentation
- bool remove_cache = 4; // Remove package caches
- repeated string preserve_paths = 5; // Paths to always keep
- repeated string exclude_patterns = 6; // Files to exclude
-}
-
-// Build strategies - how to build from source to target
-message BuildStrategy {
- oneof strategy_type {
- DockerExtractStrategy docker_extract = 1;
- GoApiStrategy go_api = 2;
- SinatraStrategy sinatra = 3;
- NodejsStrategy nodejs = 4;
- // Future: python_wsgi = 5, rust_binary = 6, etc.
- }
-}
-
-// Docker extraction strategy (first implementation)
-message DockerExtractStrategy {
- bool preserve_layers = 1; // Keep layer structure
- bool flatten_filesystem = 2; // Merge all layers
- repeated string exclude_patterns = 3; // Files to exclude
-}
-
-// Go API strategy (future)
-message GoApiStrategy {
- string go_version = 1; // "1.21", "latest"
- repeated string build_flags = 2; // "-ldflags", "-tags"
- string main_package = 3; // "./cmd/api"
- bool enable_cgo = 4;
-}
-
-// Sinatra strategy (future)
-message SinatraStrategy {
- string ruby_version = 1; // "3.2", "latest"
- string gemfile_path = 2; // "Gemfile"
- string rack_server = 3; // "puma", "unicorn"
- map rack_config = 4; // Server-specific config
-}
-
-// Node.js strategy (future)
-message NodejsStrategy {
- string node_version = 1; // "18", "20", "latest"
- string package_manager = 2; // "npm", "yarn", "pnpm"
- string start_script = 3; // "start", "server"
- bool enable_production = 4; // NODE_ENV=production
-}
-
-// Main build configuration
-message BuildConfig {
- // Tenant identification
-
- // What we're building from
- BuildSource source = 1;
-
- // What we're building to
- BuildTarget target = 2;
-
- // How to build it
- BuildStrategy strategy = 3;
-
- // Build metadata
- string build_name = 4; // Human-readable name
- map labels = 5; // Custom labels
-
- // Suggested asset ID to use when registering the built artifact
- // This allows the caller to pre-generate the asset ID
- string suggested_asset_id = 6;
-}
-
-// Build isolation metadata
-message BuildIsolation {
- string sandbox_id = 1; // Unique sandbox identifier
- string network_namespace = 2; // Network isolation
- string filesystem_namespace = 3; // Filesystem isolation
- repeated string security_contexts = 4; // SELinux/AppArmor contexts
- string cgroup_path = 5; // Resource cgroup
-}
-
-// Image metadata extracted from Docker images
-message ImageMetadata {
- string original_image = 1; // Original Docker image
- string image_digest = 2; // Docker image SHA256
- repeated string layers = 3; // Layer digests
- map labels = 4; // Docker labels
- repeated string command = 5; // Original CMD
- repeated string entrypoint = 6; // Original ENTRYPOINT
- string working_dir = 7; // WORKDIR
- map env = 8; // Environment variables
- repeated string exposed_ports = 9; // EXPOSE ports
- string user = 10; // USER directive
- repeated string volumes = 11; // VOLUME directives
-}
-
-// Build performance metrics
-message BuildMetrics {
- int64 pull_duration_ms = 1; // Time to pull image/source
- int64 extract_duration_ms = 2; // Time to extract layers
- int64 build_duration_ms = 3; // Time to build rootfs
- int64 optimize_duration_ms = 4; // Time for optimizations
- int64 total_duration_ms = 5; // Total build time
-
- int64 original_size_bytes = 6; // Original image/source size
- int64 rootfs_size_bytes = 7; // Final rootfs size
- int64 compression_ratio = 8; // Size reduction percentage
-
- int64 memory_peak_bytes = 9; // Peak memory usage
- int64 disk_usage_bytes = 10; // Temporary disk usage
- int32 cpu_cores_used = 11; // CPU cores utilized
-}
-
-// Complete build job information
-message BuildJob {
- string build_id = 1; // Unique build identifier
- BuildConfig config = 2; // Build configuration
- BuildState state = 3; // Current build state
-
- // Timestamps
- google.protobuf.Timestamp created_at = 4;
- google.protobuf.Timestamp started_at = 5;
- google.protobuf.Timestamp completed_at = 6;
-
- // Results
- string rootfs_path = 7; // Path to built rootfs
- int64 rootfs_size_bytes = 8; // Size of rootfs
- string rootfs_checksum = 9; // SHA256 of rootfs
-
- // Build metadata
- ImageMetadata image_metadata = 10;
- BuildMetrics metrics = 11;
- BuildIsolation isolation = 12;
-
- // Error information
- string error_message = 13;
- repeated string build_logs = 14;
-
- // Progress information
- int32 progress_percent = 15; // 0-100
- string current_step = 16; // Current build step
-}
-
-// Build log entry for streaming
-message StreamBuildLogsResponse {
- google.protobuf.Timestamp timestamp = 1;
- string level = 2; // "info", "warn", "error", "debug"
- string message = 3;
- string component = 4; // "puller", "extractor", "builder"
- map metadata = 5;
-}
-
-// Request/Response messages
-message CreateBuildRequest {
- BuildConfig config = 1;
-}
-
-message CreateBuildResponse {
- string build_id = 1;
- BuildState state = 2;
- google.protobuf.Timestamp created_at = 3;
- string rootfs_path = 4; // Path to the generated rootfs for VM creation
-}
-
-message GetBuildRequest {
- string build_id = 1;
- string tenant_id = 2; // For authorization
-}
-
-message GetBuildResponse {
- BuildJob build = 1;
-}
-
-message ListBuildsRequest {
- repeated BuildState state_filter = 1;
- int32 page_size = 2;
- string page_token = 3;
-}
-
-message ListBuildsResponse {
- repeated BuildJob builds = 1;
- string next_page_token = 2;
- int32 total_count = 3;
-}
-
-message CancelBuildRequest {
- string build_id = 1;
-}
-
-message CancelBuildResponse {
- bool success = 1;
- BuildState state = 2;
-}
-
-message DeleteBuildRequest {
- string build_id = 1;
- bool force = 2; // Delete even if running
-}
-
-message DeleteBuildResponse {
- bool success = 1;
-}
-
-message StreamBuildLogsRequest {
- string build_id = 1;
- bool follow = 2; // Continue streaming new logs
-}
-
-message GetBuildStatsRequest {
- string tenant_id = 1;
- google.protobuf.Timestamp start_time = 2;
- google.protobuf.Timestamp end_time = 3;
-}
-
-message GetBuildStatsResponse {
- int32 total_builds = 1;
- int32 successful_builds = 2;
- int32 failed_builds = 3;
- int64 avg_build_time_ms = 4;
- int64 total_storage_bytes = 5;
- int64 total_compute_minutes = 6;
- repeated BuildJob recent_builds = 7;
-}
diff --git a/go/proto/krane/v1/deployment.proto b/go/proto/krane/v1/deployment.proto
new file mode 100644
index 0000000000..88302b7b42
--- /dev/null
+++ b/go/proto/krane/v1/deployment.proto
@@ -0,0 +1,71 @@
+syntax = "proto3";
+
+package krane.v1;
+
+option go_package = "github.com/unkeyed/unkey/go/gen/proto/krane/v1;kranev1";
+
+service DeploymentService {
+ // CreateDeployment
+ rpc CreateDeployment(CreateDeploymentRequest) returns (CreateDeploymentResponse);
+
+ // GetDeployment
+ rpc GetDeployment(GetDeploymentRequest) returns (GetDeploymentResponse);
+
+ // DeleteDeployment
+ rpc DeleteDeployment(DeleteDeploymentRequest) returns (DeleteDeploymentResponse);
+}
+
+message DeploymentRequest {
+ string namespace = 1;
+ string deployment_id = 2;
+
+ string image = 3;
+
+ uint32 replicas = 4;
+ uint32 cpu_millicores = 5;
+ uint64 memory_size_mib = 6;
+}
+
+message CreateDeploymentRequest {
+ DeploymentRequest deployment = 1;
+}
+
+message CreateDeploymentResponse {
+ DeploymentStatus status = 1;
+}
+
+message UpdateDeploymentRequest {
+ DeploymentRequest deployment = 1;
+}
+message UpdateDeploymentResponse {
+ repeated string pod_ids = 1;
+}
+
+message DeleteDeploymentRequest {
+ string namespace = 1;
+ string deployment_id = 2;
+}
+
+message DeleteDeploymentResponse {}
+
+message GetDeploymentRequest {
+ string namespace = 1;
+ string deployment_id = 2;
+}
+
+message GetDeploymentResponse {
+ repeated Instance instances = 2;
+}
+
+enum DeploymentStatus {
+ DEPLOYMENT_STATUS_UNSPECIFIED = 0;
+ DEPLOYMENT_STATUS_PENDING = 1; // Deployment request accepted, container/pod creation in progress
+ DEPLOYMENT_STATUS_RUNNING = 2; // Container/pod is running and healthy
+ DEPLOYMENT_STATUS_TERMINATING = 3; // Container/pod is being terminated
+}
+
+message Instance {
+ string id = 1;
+ string address = 2;
+ DeploymentStatus status = 3;
+}
diff --git a/go/proto/metald/v1/deployment.proto b/go/proto/metald/v1/deployment.proto
deleted file mode 100644
index 62c0dfc8e7..0000000000
--- a/go/proto/metald/v1/deployment.proto
+++ /dev/null
@@ -1,55 +0,0 @@
-syntax = "proto3";
-
-package metald.v1;
-
-import "metald/v1/vm.proto";
-
-option go_package = "github.com/unkeyed/unkey/go/gen/proto/metald/v1;metaldv1";
-
-message DeploymentRequest {
- string deployment_id = 1;
-
- string image = 2;
-
- uint32 vm_count = 3;
- uint32 cpu = 4;
- uint64 memory_size_mib = 5;
-}
-
-message CreateDeploymentRequest {
- DeploymentRequest deployment = 1;
-}
-
-message CreateDeploymentResponse {
- repeated string vm_ids = 1;
-}
-
-message UpdateDeploymentRequest {
- DeploymentRequest deployment = 1;
-}
-message UpdateDeploymentResponse {
- repeated string vm_ids = 1;
-}
-
-message DeleteDeploymentRequest {
- string deployment_id = 1;
-}
-
-message DeleteDeploymentResponse {}
-
-message GetDeploymentRequest {
- string deployment_id = 1;
-}
-
-message GetDeploymentResponse {
- string deployment_id = 1;
-
- message Vm {
- string id = 1;
- string host = 2;
- VmState state = 3;
- uint32 port = 4;
- }
-
- repeated Vm vms = 2;
-}
diff --git a/go/proto/metald/v1/metald.proto b/go/proto/metald/v1/metald.proto
deleted file mode 100644
index 22f41434cd..0000000000
--- a/go/proto/metald/v1/metald.proto
+++ /dev/null
@@ -1,60 +0,0 @@
-syntax = "proto3";
-
-package metald.v1;
-
-import "metald/v1/deployment.proto";
-import "metald/v1/vm.proto";
-
-option go_package = "github.com/unkeyed/unkey/go/gen/proto/metald/v1;metaldv1";
-
-// VmService provides unified VM management across different hypervisor backends
-service VmService {
- //
- // Deployment related RPCs
- // These endpoints are most used by the control-plane side of things
- //
-
- // CreateDeployment
- rpc CreateDeployment(CreateDeploymentRequest) returns (CreateDeploymentResponse);
-
- // UpdateDeployment
- rpc UpdateDeployment(UpdateDeploymentRequest) returns (UpdateDeploymentResponse);
-
- // DeleteDeployment
- rpc DeleteDeployment(DeleteDeploymentRequest) returns (DeleteDeploymentResponse);
-
- // GetDeployment
- rpc GetDeployment(GetDeploymentRequest) returns (GetDeploymentResponse);
-
- //
- // VMM related RPCs
- // These endpoints are mostly for use by metald itself, builder, and assetmanagerd.
- //
-
- // CreateVm creates a new virtual machine
- rpc CreateVm(CreateVmRequest) returns (CreateVmResponse);
-
- // DeleteVm removes a virtual machine
- rpc DeleteVm(DeleteVmRequest) returns (DeleteVmResponse);
-
- // BootVm starts a created virtual machine
- rpc BootVm(BootVmRequest) returns (BootVmResponse);
-
- // ShutdownVm gracefully stops a running virtual machine
- rpc ShutdownVm(ShutdownVmRequest) returns (ShutdownVmResponse);
-
- // PauseVm pauses a running virtual machine
- rpc PauseVm(PauseVmRequest) returns (PauseVmResponse);
-
- // ResumeVm resumes a paused virtual machine
- rpc ResumeVm(ResumeVmRequest) returns (ResumeVmResponse);
-
- // RebootVm restarts a running virtual machine
- rpc RebootVm(RebootVmRequest) returns (RebootVmResponse);
-
- // GetVmInfo retrieves virtual machine status and configuration
- rpc GetVmInfo(GetVmInfoRequest) returns (GetVmInfoResponse);
-
- // ListVms lists all virtual machines managed by this service
- rpc ListVms(ListVmsRequest) returns (ListVmsResponse);
-}
diff --git a/go/proto/metald/v1/network.proto b/go/proto/metald/v1/network.proto
deleted file mode 100644
index 4b49e7888e..0000000000
--- a/go/proto/metald/v1/network.proto
+++ /dev/null
@@ -1,77 +0,0 @@
-syntax = "proto3";
-
-package metald.v1;
-
-option go_package = "github.com/unkeyed/unkey/go/gen/proto/metald/v1;metaldv1";
-
-message NetworkStats {
- int64 bytes_received = 1;
- int64 bytes_transmitted = 2;
- int64 packets_received = 3;
- int64 packets_transmitted = 4;
-}
-
-message NetworkInterface {
- // Unique identifier for this network interface
- string id = 1;
-
- // MAC address (optional, will be generated if not provided)
- string mac_address = 2;
-
- // Host-side TAP device name
- string tap_device = 3;
-
- // Network interface type (virtio-net, e1000, etc.)
- string interface_type = 4;
-
- // Additional network options
- map options = 5;
-
- // IPv4 configuration (optional)
- IPv4Config ipv4_config = 6;
-
- // IPv6 configuration (optional)
- IPv6Config ipv6_config = 7;
-
- // Network mode
- NetworkMode mode = 8;
-
- // Rate limiting
- RateLimit rx_rate_limit = 10; // Receive rate limit
- RateLimit tx_rate_limit = 11; // Transmit rate limit
-}
-
-// IPv4 network configuration
-message IPv4Config {
- string address = 1; // IPv4 address (e.g., "10.100.1.2")
- string netmask = 2; // Network mask (e.g., "255.255.255.0")
- string gateway = 3; // Default gateway
- repeated string dns_servers = 4; // DNS servers
- bool dhcp = 5; // Use DHCP instead of static config
-}
-
-// IPv6 network configuration
-message IPv6Config {
- string address = 1; // IPv6 address (e.g., "fd00::1:2")
- int32 prefix_length = 2; // Prefix length (e.g., 64)
- string gateway = 3; // Default gateway
- repeated string dns_servers = 4; // DNS servers (IPv6 addresses)
- bool slaac = 5; // Use SLAAC (Stateless Address Autoconfiguration)
- bool privacy_extensions = 6; // Enable privacy extensions
- string link_local = 7; // Link-local address (auto-generated if empty)
-}
-
-// Network mode for the interface
-enum NetworkMode {
- NETWORK_MODE_UNSPECIFIED = 0;
- NETWORK_MODE_DUAL_STACK = 1; // Both IPv4 and IPv6
- NETWORK_MODE_IPV4_ONLY = 2; // IPv4 only
- NETWORK_MODE_IPV6_ONLY = 3; // IPv6 only
-}
-
-// Rate limiting configuration
-message RateLimit {
- int64 bandwidth = 1; // Bandwidth in bytes/second
- int64 refill_time = 2; // Token bucket refill time in milliseconds
- int64 burst = 3; // Burst size in bytes
-}
diff --git a/go/proto/metald/v1/storage.proto b/go/proto/metald/v1/storage.proto
deleted file mode 100644
index d8a793c962..0000000000
--- a/go/proto/metald/v1/storage.proto
+++ /dev/null
@@ -1,32 +0,0 @@
-syntax = "proto3";
-
-package metald.v1;
-
-option go_package = "github.com/unkeyed/unkey/go/gen/proto/metald/v1;metaldv1";
-
-message StorageStats {
- int64 bytes_read = 1;
- int64 bytes_written = 2;
- int64 read_operations = 3;
- int64 write_operations = 4;
-}
-
-message StorageDevice {
- // Unique identifier for this storage device
- string id = 1;
-
- // Path to the backing file or block device
- string path = 2;
-
- // Whether this device is read-only
- bool read_only = 3;
-
- // Whether this is the root/boot device
- bool is_root_device = 4;
-
- // Storage interface type (virtio-blk, nvme, etc.)
- string interface_type = 5;
-
- // Additional storage options
- map options = 6;
-}
diff --git a/go/proto/metald/v1/vm.proto b/go/proto/metald/v1/vm.proto
deleted file mode 100644
index bd9d14e34c..0000000000
--- a/go/proto/metald/v1/vm.proto
+++ /dev/null
@@ -1,248 +0,0 @@
-syntax = "proto3";
-
-package metald.v1;
-
-import "google/protobuf/timestamp.proto";
-import "metald/v1/network.proto";
-import "metald/v1/storage.proto";
-
-option go_package = "github.com/unkeyed/unkey/go/gen/proto/metald/v1;metaldv1";
-
-// VM lifecycle states
-enum VmState {
- VM_STATE_UNSPECIFIED = 0;
- VM_STATE_CREATED = 1;
- VM_STATE_RUNNING = 2;
- VM_STATE_PAUSED = 3;
- VM_STATE_SHUTDOWN = 4;
-}
-
-// Unified VM configuration that works across different hypervisors
-message VmConfig {
- // CPU configuration
- uint32 vcpu_count = 1;
-
- // Memory configuration
- uint64 memory_size_mib = 2;
-
- // Boot configuration
- string boot = 3;
-
- // Network configuration
- string network_config = 4;
-
- // Console configuration
- ConsoleConfig console = 5;
-
- // Storage configuration
- StorageDevice storage = 6;
-
- // VM Identifier
- string id = 7;
-
- // Metadata and labels
- map metadata = 8;
-}
-
-message ListVmsRequest {
- // Optional filter by state
- repeated VmState state_filter = 1;
-
- // Pagination
- int32 page_size = 2;
- string page_token = 3;
-}
-
-message ListVmsResponse {
- repeated VmInfo vms = 1;
- string next_page_token = 2;
- uint64 total_count = 3;
-}
-
-// Request/Response messages
-message CreateVmRequest {
- // Generated unique identifier for the VM
- string vm_id = 1;
-
- // VM configuration
- VmConfig config = 2;
-}
-
-message Endpoint {
- string host = 1;
- uint32 port = 2;
-}
-
-message CreateVmResponse {
- // Current VM state after creation
- VmState state = 1;
-
- // Endpoint is the host:port pair
- Endpoint endpoint = 2;
-}
-
-message DeleteVmRequest {
- string vm_id = 1;
-
- // Whether to force deletion even if VM is running
- bool force = 2;
-}
-
-message DeleteVmResponse {
- bool success = 1;
-}
-
-message BootVmRequest {
- string vm_id = 1;
-}
-
-message BootVmResponse {
- VmState state = 2;
-}
-
-message ShutdownVmRequest {
- string vm_id = 1;
-
- // Whether to force shutdown (vs graceful)
- bool force = 2;
-
- // Timeout for graceful shutdown (seconds)
- int32 timeout_seconds = 3;
-}
-
-message ShutdownVmResponse {
- VmState state = 2;
-}
-
-message PauseVmRequest {
- string vm_id = 1;
-}
-
-message PauseVmResponse {
- VmState state = 2;
-}
-
-message ResumeVmRequest {
- string vm_id = 1;
-}
-
-message ResumeVmResponse {
- VmState state = 2;
-}
-
-message RebootVmRequest {
- string vm_id = 1;
-
- // Whether to force reboot (vs graceful)
- bool force = 2;
-}
-
-message RebootVmResponse {
- VmState state = 2;
-}
-
-message GetVmInfoRequest {
- string vm_id = 1;
-}
-
-message GetVmInfoResponse {
- string vm_id = 1;
- VmConfig config = 2;
- VmState state = 3;
- VmMetrics metrics = 4;
-
- // Backend-specific information
- map backend_info = 5;
-}
-
-message VmMetrics {
- // CPU usage percentage (0-100)
- double cpu_usage_percent = 1;
-
- // Memory usage in MiB
- uint64 memory_usage_mib = 2;
-
- // Network I/O statistics
- NetworkStats network_stats = 3;
-
- // Storage I/O statistics
- StorageStats storage_stats = 4;
-
- // VM uptime in seconds
- int64 uptime_seconds = 5;
-}
-
-message VmInfo {
- string vm_id = 1;
- VmState state = 2;
-
- // Basic config info (subset of full config)
- int32 vcpu_count = 3;
- uint64 memory_size_mib = 4;
-
- // Creation and modification timestamps
- google.protobuf.Timestamp created_timestamp = 5;
- google.protobuf.Timestamp modified_timestamp = 6;
-
- // Metadata
- map metadata = 7;
-
- // deployment_id vm is attached to
- string deployment_id = 8;
-}
-
-message CpuConfig {
- // Number of virtual CPUs to allocate at boot
- int32 vcpu_count = 1;
-
- // Maximum number of virtual CPUs (for hotplug)
- int32 max_vcpu_count = 2;
-
- // CPU topology (optional)
- CpuTopology topology = 3;
-
- // CPU features and model (backend-specific)
- map features = 4;
-}
-
-message CpuTopology {
- int32 sockets = 1;
- int32 cores_per_socket = 2;
- int32 threads_per_core = 3;
-}
-
-message MemoryConfig {
- // Memory size in MiB
- int64 memory_size_mib = 1;
-
- // Memory backing options (hugepages, etc.)
- map backing = 2;
-}
-
-message BootConfig {
- // Path to kernel image
- string kernel_path = 1;
-
- // Path to initial ramdisk (optional)
- string initrd_path = 2;
-
- // Kernel command line arguments
- string kernel_args = 3;
-
- // Boot order and options
- map boot_options = 4;
-}
-
-message ConsoleConfig {
- // Whether console is enabled
- bool enabled = 1;
-
- // Console output destination (file path, pty, etc.)
- string output = 2;
-
- // Console input source (optional)
- string input = 3;
-
- // Console type (serial, virtio-console, etc.)
- string console_type = 4;
-}
diff --git a/internal/proto/generated/assetmanagerd/v1/asset_pb.ts b/internal/proto/generated/assetmanagerd/v1/asset_pb.ts
deleted file mode 100644
index 8e2418857b..0000000000
--- a/internal/proto/generated/assetmanagerd/v1/asset_pb.ts
+++ /dev/null
@@ -1,1088 +0,0 @@
-// @generated by protoc-gen-es v2.8.0 with parameter "target=ts"
-// @generated from file assetmanagerd/v1/asset.proto (package assetmanagerd.v1, syntax proto3)
-/* eslint-disable */
-
-import type { Message } from "@bufbuild/protobuf";
-import type { GenEnum, GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
-import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
-
-/**
- * Describes the file assetmanagerd/v1/asset.proto.
- */
-export const file_assetmanagerd_v1_asset: GenFile =
- /*@__PURE__*/
- fileDesc(
- "Chxhc3NldG1hbmFnZXJkL3YxL2Fzc2V0LnByb3RvEhBhc3NldG1hbmFnZXJkLnYxIs0DCgVBc3NldBIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEikKBHR5cGUYAyABKA4yGy5hc3NldG1hbmFnZXJkLnYxLkFzc2V0VHlwZRItCgZzdGF0dXMYBCABKA4yHS5hc3NldG1hbmFnZXJkLnYxLkFzc2V0U3RhdHVzEjEKB2JhY2tlbmQYBSABKA4yIC5hc3NldG1hbmFnZXJkLnYxLlN0b3JhZ2VCYWNrZW5kEhAKCGxvY2F0aW9uGAYgASgJEhIKCnNpemVfYnl0ZXMYByABKAMSEAoIY2hlY2tzdW0YCCABKAkSMwoGbGFiZWxzGAkgAygLMiMuYXNzZXRtYW5hZ2VyZC52MS5Bc3NldC5MYWJlbHNFbnRyeRISCgpjcmVhdGVkX2J5GAogASgJEhIKCmNyZWF0ZWRfYXQYCyABKAMSGAoQbGFzdF9hY2Nlc3NlZF9hdBgMIAEoAxIXCg9yZWZlcmVuY2VfY291bnQYDSABKAUSEAoIYnVpbGRfaWQYDiABKAkSFAoMc291cmNlX2ltYWdlGA8gASgJGi0KC0xhYmVsc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiaAoSVXBsb2FkQXNzZXRSZXF1ZXN0EjkKCG1ldGFkYXRhGAEgASgLMiUuYXNzZXRtYW5hZ2VyZC52MS5VcGxvYWRBc3NldE1ldGFkYXRhSAASDwoFY2h1bmsYAiABKAxIAEIGCgRkYXRhIpwCChNVcGxvYWRBc3NldE1ldGFkYXRhEgwKBG5hbWUYASABKAkSKQoEdHlwZRgCIAEoDjIbLmFzc2V0bWFuYWdlcmQudjEuQXNzZXRUeXBlEhIKCnNpemVfYnl0ZXMYAyABKAMSQQoGbGFiZWxzGAQgAygLMjEuYXNzZXRtYW5hZ2VyZC52MS5VcGxvYWRBc3NldE1ldGFkYXRhLkxhYmVsc0VudHJ5EhIKCmNyZWF0ZWRfYnkYBSABKAkSEAoIYnVpbGRfaWQYBiABKAkSFAoMc291cmNlX2ltYWdlGAcgASgJEgoKAmlkGAggASgJGi0KC0xhYmVsc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiPQoTVXBsb2FkQXNzZXRSZXNwb25zZRImCgVhc3NldBgBIAEoCzIXLmFzc2V0bWFuYWdlcmQudjEuQXNzZXQi9QIKFFJlZ2lzdGVyQXNzZXRSZXF1ZXN0EgwKBG5hbWUYASABKAkSKQoEdHlwZRgCIAEoDjIbLmFzc2V0bWFuYWdlcmQudjEuQXNzZXRUeXBlEjEKB2JhY2tlbmQYAyABKA4yIC5hc3NldG1hbmFnZXJkLnYxLlN0b3JhZ2VCYWNrZW5kEhAKCGxvY2F0aW9uGAQgASgJEhIKCnNpemVfYnl0ZXMYBSABKAMSEAoIY2hlY2tzdW0YBiABKAkSQgoGbGFiZWxzGAcgAygLMjIuYXNzZXRtYW5hZ2VyZC52MS5SZWdpc3RlckFzc2V0UmVxdWVzdC5MYWJlbHNFbnRyeRISCgpjcmVhdGVkX2J5GAggASgJEhAKCGJ1aWxkX2lkGAkgASgJEhQKDHNvdXJjZV9pbWFnZRgKIAEoCRIKCgJpZBgLIAEoCRotCgtMYWJlbHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIj8KFVJlZ2lzdGVyQXNzZXRSZXNwb25zZRImCgVhc3NldBgBIAEoCzIXLmFzc2V0bWFuYWdlcmQudjEuQXNzZXQiMwoPR2V0QXNzZXRSZXF1ZXN0EgoKAmlkGAEgASgJEhQKDGVuc3VyZV9sb2NhbBgCIAEoCCJOChBHZXRBc3NldFJlc3BvbnNlEiYKBWFzc2V0GAEgASgLMhcuYXNzZXRtYW5hZ2VyZC52MS5Bc3NldBISCgpsb2NhbF9wYXRoGAIgASgJIpoCChFMaXN0QXNzZXRzUmVxdWVzdBIpCgR0eXBlGAEgASgOMhsuYXNzZXRtYW5hZ2VyZC52MS5Bc3NldFR5cGUSLQoGc3RhdHVzGAIgASgOMh0uYXNzZXRtYW5hZ2VyZC52MS5Bc3NldFN0YXR1cxJOCg5sYWJlbF9zZWxlY3RvchgDIAMoCzI2LmFzc2V0bWFuYWdlcmQudjEuTGlzdEFzc2V0c1JlcXVlc3QuTGFiZWxTZWxlY3RvckVudHJ5EhEKCXBhZ2Vfc2l6ZRgEIAEoBRISCgpwYWdlX3Rva2VuGAUgASgJGjQKEkxhYmVsU2VsZWN0b3JFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIlYKEkxpc3RBc3NldHNSZXNwb25zZRInCgZhc3NldHMYASADKAsyFy5hc3NldG1hbmFnZXJkLnYxLkFzc2V0EhcKD25leHRfcGFnZV90b2tlbhgCIAEoCSJRChNBY3F1aXJlQXNzZXRSZXF1ZXN0EhAKCGFzc2V0X2lkGAEgASgJEhMKC2FjcXVpcmVkX2J5GAIgASgJEhMKC3R0bF9zZWNvbmRzGAMgASgDIlAKFEFjcXVpcmVBc3NldFJlc3BvbnNlEiYKBWFzc2V0GAEgASgLMhcuYXNzZXRtYW5hZ2VyZC52MS5Bc3NldBIQCghsZWFzZV9pZBgCIAEoCSInChNSZWxlYXNlQXNzZXRSZXF1ZXN0EhAKCGxlYXNlX2lkGAEgASgJIj4KFFJlbGVhc2VBc3NldFJlc3BvbnNlEiYKBWFzc2V0GAEgASgLMhcuYXNzZXRtYW5hZ2VyZC52MS5Bc3NldCIvChJEZWxldGVBc3NldFJlcXVlc3QSCgoCaWQYASABKAkSDQoFZm9yY2UYAiABKAgiNwoTRGVsZXRlQXNzZXRSZXNwb25zZRIPCgdkZWxldGVkGAEgASgIEg8KB21lc3NhZ2UYAiABKAkiXgoVR2FyYmFnZUNvbGxlY3RSZXF1ZXN0EhcKD21heF9hZ2Vfc2Vjb25kcxgBIAEoAxIbChNkZWxldGVfdW5yZWZlcmVuY2VkGAIgASgIEg8KB2RyeV9ydW4YAyABKAgiXgoWR2FyYmFnZUNvbGxlY3RSZXNwb25zZRIvCg5kZWxldGVkX2Fzc2V0cxgBIAMoCzIXLmFzc2V0bWFuYWdlcmQudjEuQXNzZXQSEwoLYnl0ZXNfZnJlZWQYAiABKAMiVAoUUHJlcGFyZUFzc2V0c1JlcXVlc3QSEQoJYXNzZXRfaWRzGAEgAygJEhMKC3RhcmdldF9wYXRoGAIgASgJEhQKDHByZXBhcmVkX2ZvchgDIAEoCSKYAQoVUHJlcGFyZUFzc2V0c1Jlc3BvbnNlEkwKC2Fzc2V0X3BhdGhzGAEgAygLMjcuYXNzZXRtYW5hZ2VyZC52MS5QcmVwYXJlQXNzZXRzUmVzcG9uc2UuQXNzZXRQYXRoc0VudHJ5GjEKD0Fzc2V0UGF0aHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBItMCChJRdWVyeUFzc2V0c1JlcXVlc3QSKQoEdHlwZRgBIAEoDjIbLmFzc2V0bWFuYWdlcmQudjEuQXNzZXRUeXBlEi0KBnN0YXR1cxgCIAEoDjIdLmFzc2V0bWFuYWdlcmQudjEuQXNzZXRTdGF0dXMSTwoObGFiZWxfc2VsZWN0b3IYAyADKAsyNy5hc3NldG1hbmFnZXJkLnYxLlF1ZXJ5QXNzZXRzUmVxdWVzdC5MYWJlbFNlbGVjdG9yRW50cnkSEQoJcGFnZV9zaXplGAQgASgFEhIKCnBhZ2VfdG9rZW4YBSABKAkSNQoNYnVpbGRfb3B0aW9ucxgGIAEoCzIeLmFzc2V0bWFuYWdlcmQudjEuQnVpbGRPcHRpb25zGjQKEkxhYmVsU2VsZWN0b3JFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIvwBCgxCdWlsZE9wdGlvbnMSGQoRZW5hYmxlX2F1dG9fYnVpbGQYASABKAgSGwoTd2FpdF9mb3JfY29tcGxldGlvbhgCIAEoCBIdChVidWlsZF90aW1lb3V0X3NlY29uZHMYAyABKAUSRQoMYnVpbGRfbGFiZWxzGAQgAygLMi8uYXNzZXRtYW5hZ2VyZC52MS5CdWlsZE9wdGlvbnMuQnVpbGRMYWJlbHNFbnRyeRIaChJzdWdnZXN0ZWRfYXNzZXRfaWQYBSABKAkaMgoQQnVpbGRMYWJlbHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIo4BChNRdWVyeUFzc2V0c1Jlc3BvbnNlEicKBmFzc2V0cxgBIAMoCzIXLmFzc2V0bWFuYWdlcmQudjEuQXNzZXQSFwoPbmV4dF9wYWdlX3Rva2VuGAIgASgJEjUKEHRyaWdnZXJlZF9idWlsZHMYAyADKAsyGy5hc3NldG1hbmFnZXJkLnYxLkJ1aWxkSW5mbyJsCglCdWlsZEluZm8SEAoIYnVpbGRfaWQYASABKAkSFAoMZG9ja2VyX2ltYWdlGAIgASgJEg4KBnN0YXR1cxgDIAEoCRIVCg1lcnJvcl9tZXNzYWdlGAQgASgJEhAKCGFzc2V0X2lkGAUgASgJKocBCglBc3NldFR5cGUSGgoWQVNTRVRfVFlQRV9VTlNQRUNJRklFRBAAEhUKEUFTU0VUX1RZUEVfS0VSTkVMEAESFQoRQVNTRVRfVFlQRV9ST09URlMQAhIVChFBU1NFVF9UWVBFX0lOSVRSRBADEhkKFUFTU0VUX1RZUEVfRElTS19JTUFHRRAEKpYBCgtBc3NldFN0YXR1cxIcChhBU1NFVF9TVEFUVVNfVU5TUEVDSUZJRUQQABIaChZBU1NFVF9TVEFUVVNfVVBMT0FESU5HEAESGgoWQVNTRVRfU1RBVFVTX0FWQUlMQUJMRRACEhkKFUFTU0VUX1NUQVRVU19ERUxFVElORxADEhYKEkFTU0VUX1NUQVRVU19FUlJPUhAEKpcBCg5TdG9yYWdlQmFja2VuZBIfChtTVE9SQUdFX0JBQ0tFTkRfVU5TUEVDSUZJRUQQABIZChVTVE9SQUdFX0JBQ0tFTkRfTE9DQUwQARIWChJTVE9SQUdFX0JBQ0tFTkRfUzMQAhIYChRTVE9SQUdFX0JBQ0tFTkRfSFRUUBADEhcKE1NUT1JBR0VfQkFDS0VORF9ORlMQBDK+BwoTQXNzZXRNYW5hZ2VyU2VydmljZRJcCgtVcGxvYWRBc3NldBIkLmFzc2V0bWFuYWdlcmQudjEuVXBsb2FkQXNzZXRSZXF1ZXN0GiUuYXNzZXRtYW5hZ2VyZC52MS5VcGxvYWRBc3NldFJlc3BvbnNlKAESYAoNUmVnaXN0ZXJBc3NldBImLmFzc2V0bWFuYWdlcmQudjEuUmVnaXN0ZXJBc3NldFJlcXVlc3QaJy5hc3NldG1hbmFnZXJkLnYxLlJlZ2lzdGVyQXNzZXRSZXNwb25zZRJRCghHZXRBc3NldBIhLmFzc2V0bWFuYWdlcmQudjEuR2V0QXNzZXRSZXF1ZXN0GiIuYXNzZXRtYW5hZ2VyZC52MS5HZXRBc3NldFJlc3BvbnNlElcKCkxpc3RBc3NldHMSIy5hc3NldG1hbmFnZXJkLnYxLkxpc3RBc3NldHNSZXF1ZXN0GiQuYXNzZXRtYW5hZ2VyZC52MS5MaXN0QXNzZXRzUmVzcG9uc2USXQoMQWNxdWlyZUFzc2V0EiUuYXNzZXRtYW5hZ2VyZC52MS5BY3F1aXJlQXNzZXRSZXF1ZXN0GiYuYXNzZXRtYW5hZ2VyZC52MS5BY3F1aXJlQXNzZXRSZXNwb25zZRJdCgxSZWxlYXNlQXNzZXQSJS5hc3NldG1hbmFnZXJkLnYxLlJlbGVhc2VBc3NldFJlcXVlc3QaJi5hc3NldG1hbmFnZXJkLnYxLlJlbGVhc2VBc3NldFJlc3BvbnNlEloKC0RlbGV0ZUFzc2V0EiQuYXNzZXRtYW5hZ2VyZC52MS5EZWxldGVBc3NldFJlcXVlc3QaJS5hc3NldG1hbmFnZXJkLnYxLkRlbGV0ZUFzc2V0UmVzcG9uc2USYwoOR2FyYmFnZUNvbGxlY3QSJy5hc3NldG1hbmFnZXJkLnYxLkdhcmJhZ2VDb2xsZWN0UmVxdWVzdBooLmFzc2V0bWFuYWdlcmQudjEuR2FyYmFnZUNvbGxlY3RSZXNwb25zZRJgCg1QcmVwYXJlQXNzZXRzEiYuYXNzZXRtYW5hZ2VyZC52MS5QcmVwYXJlQXNzZXRzUmVxdWVzdBonLmFzc2V0bWFuYWdlcmQudjEuUHJlcGFyZUFzc2V0c1Jlc3BvbnNlEloKC1F1ZXJ5QXNzZXRzEiQuYXNzZXRtYW5hZ2VyZC52MS5RdWVyeUFzc2V0c1JlcXVlc3QaJS5hc3NldG1hbmFnZXJkLnYxLlF1ZXJ5QXNzZXRzUmVzcG9uc2VCSFpGZ2l0aHViLmNvbS91bmtleWVkL3Vua2V5L2dvL2dlbi9wcm90by9hc3NldG1hbmFnZXJkL3YxO2Fzc2V0bWFuYWdlcmR2MWIGcHJvdG8z",
- );
-
-/**
- * @generated from message assetmanagerd.v1.Asset
- */
-export type Asset = Message<"assetmanagerd.v1.Asset"> & {
- /**
- * @generated from field: string id = 1;
- */
- id: string;
-
- /**
- * @generated from field: string name = 2;
- */
- name: string;
-
- /**
- * @generated from field: assetmanagerd.v1.AssetType type = 3;
- */
- type: AssetType;
-
- /**
- * @generated from field: assetmanagerd.v1.AssetStatus status = 4;
- */
- status: AssetStatus;
-
- /**
- * Storage information
- *
- * @generated from field: assetmanagerd.v1.StorageBackend backend = 5;
- */
- backend: StorageBackend;
-
- /**
- * Path or URL depending on backend
- *
- * @generated from field: string location = 6;
- */
- location: string;
-
- /**
- * @generated from field: int64 size_bytes = 7;
- */
- sizeBytes: bigint;
-
- /**
- * SHA256
- *
- * @generated from field: string checksum = 8;
- */
- checksum: string;
-
- /**
- * Metadata
- *
- * @generated from field: map labels = 9;
- */
- labels: { [key: string]: string };
-
- /**
- * e.g., "builderd", "manual"
- *
- * @generated from field: string created_by = 10;
- */
- createdBy: string;
-
- /**
- * Unix timestamp
- *
- * @generated from field: int64 created_at = 11;
- */
- createdAt: bigint;
-
- /**
- * @generated from field: int64 last_accessed_at = 12;
- */
- lastAccessedAt: bigint;
-
- /**
- * Reference counting for GC
- *
- * @generated from field: int32 reference_count = 13;
- */
- referenceCount: number;
-
- /**
- * Build information (if created by builderd)
- *
- * @generated from field: string build_id = 14;
- */
- buildId: string;
-
- /**
- * @generated from field: string source_image = 15;
- */
- sourceImage: string;
-};
-
-/**
- * Describes the message assetmanagerd.v1.Asset.
- * Use `create(AssetSchema)` to create a new message.
- */
-export const AssetSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 0);
-
-/**
- * @generated from message assetmanagerd.v1.UploadAssetRequest
- */
-export type UploadAssetRequest = Message<"assetmanagerd.v1.UploadAssetRequest"> & {
- /**
- * @generated from oneof assetmanagerd.v1.UploadAssetRequest.data
- */
- data:
- | {
- /**
- * @generated from field: assetmanagerd.v1.UploadAssetMetadata metadata = 1;
- */
- value: UploadAssetMetadata;
- case: "metadata";
- }
- | {
- /**
- * @generated from field: bytes chunk = 2;
- */
- value: Uint8Array;
- case: "chunk";
- }
- | { case: undefined; value?: undefined };
-};
-
-/**
- * Describes the message assetmanagerd.v1.UploadAssetRequest.
- * Use `create(UploadAssetRequestSchema)` to create a new message.
- */
-export const UploadAssetRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 1);
-
-/**
- * @generated from message assetmanagerd.v1.UploadAssetMetadata
- */
-export type UploadAssetMetadata = Message<"assetmanagerd.v1.UploadAssetMetadata"> & {
- /**
- * @generated from field: string name = 1;
- */
- name: string;
-
- /**
- * @generated from field: assetmanagerd.v1.AssetType type = 2;
- */
- type: AssetType;
-
- /**
- * @generated from field: int64 size_bytes = 3;
- */
- sizeBytes: bigint;
-
- /**
- * @generated from field: map labels = 4;
- */
- labels: { [key: string]: string };
-
- /**
- * @generated from field: string created_by = 5;
- */
- createdBy: string;
-
- /**
- * @generated from field: string build_id = 6;
- */
- buildId: string;
-
- /**
- * @generated from field: string source_image = 7;
- */
- sourceImage: string;
-
- /**
- * Optional: specific asset ID to use
- *
- * @generated from field: string id = 8;
- */
- id: string;
-};
-
-/**
- * Describes the message assetmanagerd.v1.UploadAssetMetadata.
- * Use `create(UploadAssetMetadataSchema)` to create a new message.
- */
-export const UploadAssetMetadataSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 2);
-
-/**
- * @generated from message assetmanagerd.v1.UploadAssetResponse
- */
-export type UploadAssetResponse = Message<"assetmanagerd.v1.UploadAssetResponse"> & {
- /**
- * @generated from field: assetmanagerd.v1.Asset asset = 1;
- */
- asset?: Asset;
-};
-
-/**
- * Describes the message assetmanagerd.v1.UploadAssetResponse.
- * Use `create(UploadAssetResponseSchema)` to create a new message.
- */
-export const UploadAssetResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 3);
-
-/**
- * @generated from message assetmanagerd.v1.RegisterAssetRequest
- */
-export type RegisterAssetRequest = Message<"assetmanagerd.v1.RegisterAssetRequest"> & {
- /**
- * @generated from field: string name = 1;
- */
- name: string;
-
- /**
- * @generated from field: assetmanagerd.v1.AssetType type = 2;
- */
- type: AssetType;
-
- /**
- * @generated from field: assetmanagerd.v1.StorageBackend backend = 3;
- */
- backend: StorageBackend;
-
- /**
- * @generated from field: string location = 4;
- */
- location: string;
-
- /**
- * @generated from field: int64 size_bytes = 5;
- */
- sizeBytes: bigint;
-
- /**
- * @generated from field: string checksum = 6;
- */
- checksum: string;
-
- /**
- * @generated from field: map labels = 7;
- */
- labels: { [key: string]: string };
-
- /**
- * @generated from field: string created_by = 8;
- */
- createdBy: string;
-
- /**
- * Optional build information
- *
- * @generated from field: string build_id = 9;
- */
- buildId: string;
-
- /**
- * @generated from field: string source_image = 10;
- */
- sourceImage: string;
-
- /**
- * Optional: specific asset ID to use (if not provided, one will be generated)
- *
- * @generated from field: string id = 11;
- */
- id: string;
-};
-
-/**
- * Describes the message assetmanagerd.v1.RegisterAssetRequest.
- * Use `create(RegisterAssetRequestSchema)` to create a new message.
- */
-export const RegisterAssetRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 4);
-
-/**
- * @generated from message assetmanagerd.v1.RegisterAssetResponse
- */
-export type RegisterAssetResponse = Message<"assetmanagerd.v1.RegisterAssetResponse"> & {
- /**
- * @generated from field: assetmanagerd.v1.Asset asset = 1;
- */
- asset?: Asset;
-};
-
-/**
- * Describes the message assetmanagerd.v1.RegisterAssetResponse.
- * Use `create(RegisterAssetResponseSchema)` to create a new message.
- */
-export const RegisterAssetResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 5);
-
-/**
- * @generated from message assetmanagerd.v1.GetAssetRequest
- */
-export type GetAssetRequest = Message<"assetmanagerd.v1.GetAssetRequest"> & {
- /**
- * @generated from field: string id = 1;
- */
- id: string;
-
- /**
- * If true, ensures asset is available locally (downloads if needed)
- *
- * @generated from field: bool ensure_local = 2;
- */
- ensureLocal: boolean;
-};
-
-/**
- * Describes the message assetmanagerd.v1.GetAssetRequest.
- * Use `create(GetAssetRequestSchema)` to create a new message.
- */
-export const GetAssetRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 6);
-
-/**
- * @generated from message assetmanagerd.v1.GetAssetResponse
- */
-export type GetAssetResponse = Message<"assetmanagerd.v1.GetAssetResponse"> & {
- /**
- * @generated from field: assetmanagerd.v1.Asset asset = 1;
- */
- asset?: Asset;
-
- /**
- * Local path if ensure_local was true
- *
- * @generated from field: string local_path = 2;
- */
- localPath: string;
-};
-
-/**
- * Describes the message assetmanagerd.v1.GetAssetResponse.
- * Use `create(GetAssetResponseSchema)` to create a new message.
- */
-export const GetAssetResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 7);
-
-/**
- * @generated from message assetmanagerd.v1.ListAssetsRequest
- */
-export type ListAssetsRequest = Message<"assetmanagerd.v1.ListAssetsRequest"> & {
- /**
- * Filter by type
- *
- * @generated from field: assetmanagerd.v1.AssetType type = 1;
- */
- type: AssetType;
-
- /**
- * Filter by status
- *
- * @generated from field: assetmanagerd.v1.AssetStatus status = 2;
- */
- status: AssetStatus;
-
- /**
- * Filter by labels (all must match)
- *
- * @generated from field: map label_selector = 3;
- */
- labelSelector: { [key: string]: string };
-
- /**
- * Pagination
- *
- * @generated from field: int32 page_size = 4;
- */
- pageSize: number;
-
- /**
- * @generated from field: string page_token = 5;
- */
- pageToken: string;
-};
-
-/**
- * Describes the message assetmanagerd.v1.ListAssetsRequest.
- * Use `create(ListAssetsRequestSchema)` to create a new message.
- */
-export const ListAssetsRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 8);
-
-/**
- * @generated from message assetmanagerd.v1.ListAssetsResponse
- */
-export type ListAssetsResponse = Message<"assetmanagerd.v1.ListAssetsResponse"> & {
- /**
- * @generated from field: repeated assetmanagerd.v1.Asset assets = 1;
- */
- assets: Asset[];
-
- /**
- * @generated from field: string next_page_token = 2;
- */
- nextPageToken: string;
-};
-
-/**
- * Describes the message assetmanagerd.v1.ListAssetsResponse.
- * Use `create(ListAssetsResponseSchema)` to create a new message.
- */
-export const ListAssetsResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 9);
-
-/**
- * @generated from message assetmanagerd.v1.AcquireAssetRequest
- */
-export type AcquireAssetRequest = Message<"assetmanagerd.v1.AcquireAssetRequest"> & {
- /**
- * @generated from field: string asset_id = 1;
- */
- assetId: string;
-
- /**
- * e.g., "vm-123"
- *
- * @generated from field: string acquired_by = 2;
- */
- acquiredBy: string;
-
- /**
- * Optional auto-release after TTL
- *
- * @generated from field: int64 ttl_seconds = 3;
- */
- ttlSeconds: bigint;
-};
-
-/**
- * Describes the message assetmanagerd.v1.AcquireAssetRequest.
- * Use `create(AcquireAssetRequestSchema)` to create a new message.
- */
-export const AcquireAssetRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 10);
-
-/**
- * @generated from message assetmanagerd.v1.AcquireAssetResponse
- */
-export type AcquireAssetResponse = Message<"assetmanagerd.v1.AcquireAssetResponse"> & {
- /**
- * @generated from field: assetmanagerd.v1.Asset asset = 1;
- */
- asset?: Asset;
-
- /**
- * Use this for release
- *
- * @generated from field: string lease_id = 2;
- */
- leaseId: string;
-};
-
-/**
- * Describes the message assetmanagerd.v1.AcquireAssetResponse.
- * Use `create(AcquireAssetResponseSchema)` to create a new message.
- */
-export const AcquireAssetResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 11);
-
-/**
- * @generated from message assetmanagerd.v1.ReleaseAssetRequest
- */
-export type ReleaseAssetRequest = Message<"assetmanagerd.v1.ReleaseAssetRequest"> & {
- /**
- * @generated from field: string lease_id = 1;
- */
- leaseId: string;
-};
-
-/**
- * Describes the message assetmanagerd.v1.ReleaseAssetRequest.
- * Use `create(ReleaseAssetRequestSchema)` to create a new message.
- */
-export const ReleaseAssetRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 12);
-
-/**
- * @generated from message assetmanagerd.v1.ReleaseAssetResponse
- */
-export type ReleaseAssetResponse = Message<"assetmanagerd.v1.ReleaseAssetResponse"> & {
- /**
- * @generated from field: assetmanagerd.v1.Asset asset = 1;
- */
- asset?: Asset;
-};
-
-/**
- * Describes the message assetmanagerd.v1.ReleaseAssetResponse.
- * Use `create(ReleaseAssetResponseSchema)` to create a new message.
- */
-export const ReleaseAssetResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 13);
-
-/**
- * @generated from message assetmanagerd.v1.DeleteAssetRequest
- */
-export type DeleteAssetRequest = Message<"assetmanagerd.v1.DeleteAssetRequest"> & {
- /**
- * @generated from field: string id = 1;
- */
- id: string;
-
- /**
- * Delete even if ref count > 0
- *
- * @generated from field: bool force = 2;
- */
- force: boolean;
-};
-
-/**
- * Describes the message assetmanagerd.v1.DeleteAssetRequest.
- * Use `create(DeleteAssetRequestSchema)` to create a new message.
- */
-export const DeleteAssetRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 14);
-
-/**
- * @generated from message assetmanagerd.v1.DeleteAssetResponse
- */
-export type DeleteAssetResponse = Message<"assetmanagerd.v1.DeleteAssetResponse"> & {
- /**
- * @generated from field: bool deleted = 1;
- */
- deleted: boolean;
-
- /**
- * @generated from field: string message = 2;
- */
- message: string;
-};
-
-/**
- * Describes the message assetmanagerd.v1.DeleteAssetResponse.
- * Use `create(DeleteAssetResponseSchema)` to create a new message.
- */
-export const DeleteAssetResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 15);
-
-/**
- * @generated from message assetmanagerd.v1.GarbageCollectRequest
- */
-export type GarbageCollectRequest = Message<"assetmanagerd.v1.GarbageCollectRequest"> & {
- /**
- * Delete assets not accessed in this many seconds
- *
- * @generated from field: int64 max_age_seconds = 1;
- */
- maxAgeSeconds: bigint;
-
- /**
- * Delete assets with 0 references
- *
- * @generated from field: bool delete_unreferenced = 2;
- */
- deleteUnreferenced: boolean;
-
- /**
- * Dry run - just return what would be deleted
- *
- * @generated from field: bool dry_run = 3;
- */
- dryRun: boolean;
-};
-
-/**
- * Describes the message assetmanagerd.v1.GarbageCollectRequest.
- * Use `create(GarbageCollectRequestSchema)` to create a new message.
- */
-export const GarbageCollectRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 16);
-
-/**
- * @generated from message assetmanagerd.v1.GarbageCollectResponse
- */
-export type GarbageCollectResponse = Message<"assetmanagerd.v1.GarbageCollectResponse"> & {
- /**
- * @generated from field: repeated assetmanagerd.v1.Asset deleted_assets = 1;
- */
- deletedAssets: Asset[];
-
- /**
- * @generated from field: int64 bytes_freed = 2;
- */
- bytesFreed: bigint;
-};
-
-/**
- * Describes the message assetmanagerd.v1.GarbageCollectResponse.
- * Use `create(GarbageCollectResponseSchema)` to create a new message.
- */
-export const GarbageCollectResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 17);
-
-/**
- * @generated from message assetmanagerd.v1.PrepareAssetsRequest
- */
-export type PrepareAssetsRequest = Message<"assetmanagerd.v1.PrepareAssetsRequest"> & {
- /**
- * @generated from field: repeated string asset_ids = 1;
- */
- assetIds: string[];
-
- /**
- * e.g., jailer chroot path
- *
- * @generated from field: string target_path = 2;
- */
- targetPath: string;
-
- /**
- * e.g., "vm-123"
- *
- * @generated from field: string prepared_for = 3;
- */
- preparedFor: string;
-};
-
-/**
- * Describes the message assetmanagerd.v1.PrepareAssetsRequest.
- * Use `create(PrepareAssetsRequestSchema)` to create a new message.
- */
-export const PrepareAssetsRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 18);
-
-/**
- * @generated from message assetmanagerd.v1.PrepareAssetsResponse
- */
-export type PrepareAssetsResponse = Message<"assetmanagerd.v1.PrepareAssetsResponse"> & {
- /**
- * asset_id -> local path
- *
- * @generated from field: map asset_paths = 1;
- */
- assetPaths: { [key: string]: string };
-};
-
-/**
- * Describes the message assetmanagerd.v1.PrepareAssetsResponse.
- * Use `create(PrepareAssetsResponseSchema)` to create a new message.
- */
-export const PrepareAssetsResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 19);
-
-/**
- * QueryAssetsRequest is similar to ListAssetsRequest but with build options
- *
- * @generated from message assetmanagerd.v1.QueryAssetsRequest
- */
-export type QueryAssetsRequest = Message<"assetmanagerd.v1.QueryAssetsRequest"> & {
- /**
- * Filter by type
- *
- * @generated from field: assetmanagerd.v1.AssetType type = 1;
- */
- type: AssetType;
-
- /**
- * Filter by status
- *
- * @generated from field: assetmanagerd.v1.AssetStatus status = 2;
- */
- status: AssetStatus;
-
- /**
- * Filter by labels (all must match)
- *
- * @generated from field: map label_selector = 3;
- */
- labelSelector: { [key: string]: string };
-
- /**
- * Pagination
- *
- * @generated from field: int32 page_size = 4;
- */
- pageSize: number;
-
- /**
- * @generated from field: string page_token = 5;
- */
- pageToken: string;
-
- /**
- * Build options - if asset not found and these are set, trigger build
- *
- * @generated from field: assetmanagerd.v1.BuildOptions build_options = 6;
- */
- buildOptions?: BuildOptions;
-};
-
-/**
- * Describes the message assetmanagerd.v1.QueryAssetsRequest.
- * Use `create(QueryAssetsRequestSchema)` to create a new message.
- */
-export const QueryAssetsRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 20);
-
-/**
- * BuildOptions controls automatic asset creation
- *
- * @generated from message assetmanagerd.v1.BuildOptions
- */
-export type BuildOptions = Message<"assetmanagerd.v1.BuildOptions"> & {
- /**
- * Enable automatic building if assets don't exist
- *
- * @generated from field: bool enable_auto_build = 1;
- */
- enableAutoBuild: boolean;
-
- /**
- * Wait for build completion before returning
- *
- * @generated from field: bool wait_for_completion = 2;
- */
- waitForCompletion: boolean;
-
- /**
- * Timeout for build operation (seconds)
- *
- * @generated from field: int32 build_timeout_seconds = 3;
- */
- buildTimeoutSeconds: number;
-
- /**
- * Additional labels to add to the built asset
- *
- * @generated from field: map build_labels = 4;
- */
- buildLabels: { [key: string]: string };
-
- /**
- * Suggested asset ID to use when registering the built asset
- * This allows the caller to know the asset ID before it's built
- *
- * @generated from field: string suggested_asset_id = 5;
- */
- suggestedAssetId: string;
-};
-
-/**
- * Describes the message assetmanagerd.v1.BuildOptions.
- * Use `create(BuildOptionsSchema)` to create a new message.
- */
-export const BuildOptionsSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 21);
-
-/**
- * QueryAssetsResponse includes build information if builds were triggered
- *
- * @generated from message assetmanagerd.v1.QueryAssetsResponse
- */
-export type QueryAssetsResponse = Message<"assetmanagerd.v1.QueryAssetsResponse"> & {
- /**
- * @generated from field: repeated assetmanagerd.v1.Asset assets = 1;
- */
- assets: Asset[];
-
- /**
- * @generated from field: string next_page_token = 2;
- */
- nextPageToken: string;
-
- /**
- * Information about any builds that were triggered
- *
- * @generated from field: repeated assetmanagerd.v1.BuildInfo triggered_builds = 3;
- */
- triggeredBuilds: BuildInfo[];
-};
-
-/**
- * Describes the message assetmanagerd.v1.QueryAssetsResponse.
- * Use `create(QueryAssetsResponseSchema)` to create a new message.
- */
-export const QueryAssetsResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 22);
-
-/**
- * BuildInfo provides information about triggered builds
- *
- * @generated from message assetmanagerd.v1.BuildInfo
- */
-export type BuildInfo = Message<"assetmanagerd.v1.BuildInfo"> & {
- /**
- * @generated from field: string build_id = 1;
- */
- buildId: string;
-
- /**
- * @generated from field: string docker_image = 2;
- */
- dockerImage: string;
-
- /**
- * "pending", "building", "completed", "failed"
- *
- * @generated from field: string status = 3;
- */
- status: string;
-
- /**
- * @generated from field: string error_message = 4;
- */
- errorMessage: string;
-
- /**
- * Asset ID if build completed and asset was registered
- *
- * @generated from field: string asset_id = 5;
- */
- assetId: string;
-};
-
-/**
- * Describes the message assetmanagerd.v1.BuildInfo.
- * Use `create(BuildInfoSchema)` to create a new message.
- */
-export const BuildInfoSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_assetmanagerd_v1_asset, 23);
-
-/**
- * @generated from enum assetmanagerd.v1.AssetType
- */
-export enum AssetType {
- /**
- * @generated from enum value: ASSET_TYPE_UNSPECIFIED = 0;
- */
- UNSPECIFIED = 0,
-
- /**
- * @generated from enum value: ASSET_TYPE_KERNEL = 1;
- */
- KERNEL = 1,
-
- /**
- * @generated from enum value: ASSET_TYPE_ROOTFS = 2;
- */
- ROOTFS = 2,
-
- /**
- * @generated from enum value: ASSET_TYPE_INITRD = 3;
- */
- INITRD = 3,
-
- /**
- * @generated from enum value: ASSET_TYPE_DISK_IMAGE = 4;
- */
- DISK_IMAGE = 4,
-}
-
-/**
- * Describes the enum assetmanagerd.v1.AssetType.
- */
-export const AssetTypeSchema: GenEnum =
- /*@__PURE__*/
- enumDesc(file_assetmanagerd_v1_asset, 0);
-
-/**
- * @generated from enum assetmanagerd.v1.AssetStatus
- */
-export enum AssetStatus {
- /**
- * @generated from enum value: ASSET_STATUS_UNSPECIFIED = 0;
- */
- UNSPECIFIED = 0,
-
- /**
- * @generated from enum value: ASSET_STATUS_UPLOADING = 1;
- */
- UPLOADING = 1,
-
- /**
- * @generated from enum value: ASSET_STATUS_AVAILABLE = 2;
- */
- AVAILABLE = 2,
-
- /**
- * @generated from enum value: ASSET_STATUS_DELETING = 3;
- */
- DELETING = 3,
-
- /**
- * @generated from enum value: ASSET_STATUS_ERROR = 4;
- */
- ERROR = 4,
-}
-
-/**
- * Describes the enum assetmanagerd.v1.AssetStatus.
- */
-export const AssetStatusSchema: GenEnum =
- /*@__PURE__*/
- enumDesc(file_assetmanagerd_v1_asset, 1);
-
-/**
- * @generated from enum assetmanagerd.v1.StorageBackend
- */
-export enum StorageBackend {
- /**
- * @generated from enum value: STORAGE_BACKEND_UNSPECIFIED = 0;
- */
- UNSPECIFIED = 0,
-
- /**
- * @generated from enum value: STORAGE_BACKEND_LOCAL = 1;
- */
- LOCAL = 1,
-
- /**
- * @generated from enum value: STORAGE_BACKEND_S3 = 2;
- */
- S3 = 2,
-
- /**
- * @generated from enum value: STORAGE_BACKEND_HTTP = 3;
- */
- HTTP = 3,
-
- /**
- * @generated from enum value: STORAGE_BACKEND_NFS = 4;
- */
- NFS = 4,
-}
-
-/**
- * Describes the enum assetmanagerd.v1.StorageBackend.
- */
-export const StorageBackendSchema: GenEnum =
- /*@__PURE__*/
- enumDesc(file_assetmanagerd_v1_asset, 2);
-
-/**
- * AssetManagerService manages VM assets (kernels, rootfs images) across the
- * infrastructure
- *
- * @generated from service assetmanagerd.v1.AssetManagerService
- */
-export const AssetManagerService: GenService<{
- /**
- * Upload and register an asset in one operation
- *
- * @generated from rpc assetmanagerd.v1.AssetManagerService.UploadAsset
- */
- uploadAsset: {
- methodKind: "client_streaming";
- input: typeof UploadAssetRequestSchema;
- output: typeof UploadAssetResponseSchema;
- };
- /**
- * Register a new asset (called by builderd after creating images)
- *
- * @generated from rpc assetmanagerd.v1.AssetManagerService.RegisterAsset
- */
- registerAsset: {
- methodKind: "unary";
- input: typeof RegisterAssetRequestSchema;
- output: typeof RegisterAssetResponseSchema;
- };
- /**
- * Get asset location and metadata
- *
- * @generated from rpc assetmanagerd.v1.AssetManagerService.GetAsset
- */
- getAsset: {
- methodKind: "unary";
- input: typeof GetAssetRequestSchema;
- output: typeof GetAssetResponseSchema;
- };
- /**
- * List available assets with filtering
- *
- * @generated from rpc assetmanagerd.v1.AssetManagerService.ListAssets
- */
- listAssets: {
- methodKind: "unary";
- input: typeof ListAssetsRequestSchema;
- output: typeof ListAssetsResponseSchema;
- };
- /**
- * Mark asset as in-use (reference counting for GC)
- *
- * @generated from rpc assetmanagerd.v1.AssetManagerService.AcquireAsset
- */
- acquireAsset: {
- methodKind: "unary";
- input: typeof AcquireAssetRequestSchema;
- output: typeof AcquireAssetResponseSchema;
- };
- /**
- * Release asset reference (decrements ref count)
- *
- * @generated from rpc assetmanagerd.v1.AssetManagerService.ReleaseAsset
- */
- releaseAsset: {
- methodKind: "unary";
- input: typeof ReleaseAssetRequestSchema;
- output: typeof ReleaseAssetResponseSchema;
- };
- /**
- * Delete an asset (only if ref count is 0)
- *
- * @generated from rpc assetmanagerd.v1.AssetManagerService.DeleteAsset
- */
- deleteAsset: {
- methodKind: "unary";
- input: typeof DeleteAssetRequestSchema;
- output: typeof DeleteAssetResponseSchema;
- };
- /**
- * Trigger garbage collection of unused assets
- *
- * @generated from rpc assetmanagerd.v1.AssetManagerService.GarbageCollect
- */
- garbageCollect: {
- methodKind: "unary";
- input: typeof GarbageCollectRequestSchema;
- output: typeof GarbageCollectResponseSchema;
- };
- /**
- * Pre-stage assets for a specific host/jailer
- *
- * @generated from rpc assetmanagerd.v1.AssetManagerService.PrepareAssets
- */
- prepareAssets: {
- methodKind: "unary";
- input: typeof PrepareAssetsRequestSchema;
- output: typeof PrepareAssetsResponseSchema;
- };
- /**
- * Query assets with automatic build triggering if not found
- * This is the enhanced version of ListAssets that supports automatic asset creation
- *
- * @generated from rpc assetmanagerd.v1.AssetManagerService.QueryAssets
- */
- queryAssets: {
- methodKind: "unary";
- input: typeof QueryAssetsRequestSchema;
- output: typeof QueryAssetsResponseSchema;
- };
-}> = /*@__PURE__*/ serviceDesc(file_assetmanagerd_v1_asset, 0);
diff --git a/internal/proto/generated/billaged/v1/billing_pb.ts b/internal/proto/generated/billaged/v1/billing_pb.ts
deleted file mode 100644
index 3f83e3df84..0000000000
--- a/internal/proto/generated/billaged/v1/billing_pb.ts
+++ /dev/null
@@ -1,338 +0,0 @@
-// @generated by protoc-gen-es v2.8.0 with parameter "target=ts"
-// @generated from file billaged/v1/billing.proto (package billaged.v1, syntax proto3)
-/* eslint-disable */
-
-import type { Message } from "@bufbuild/protobuf";
-import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
-import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
-import type { Timestamp } from "@bufbuild/protobuf/wkt";
-import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt";
-
-/**
- * Describes the file billaged/v1/billing.proto.
- */
-export const file_billaged_v1_billing: GenFile =
- /*@__PURE__*/
- fileDesc(
- "ChliaWxsYWdlZC92MS9iaWxsaW5nLnByb3RvEgtiaWxsYWdlZC52MSLVAQoJVk1NZXRyaWNzEi0KCXRpbWVzdGFtcBgBIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASFgoOY3B1X3RpbWVfbmFub3MYAiABKAMSGgoSbWVtb3J5X3VzYWdlX2J5dGVzGAMgASgDEhcKD2Rpc2tfcmVhZF9ieXRlcxgEIAEoAxIYChBkaXNrX3dyaXRlX2J5dGVzGAUgASgDEhgKEG5ldHdvcmtfcnhfYnl0ZXMYBiABKAMSGAoQbmV0d29ya190eF9ieXRlcxgHIAEoAyJmChdTZW5kTWV0cmljc0JhdGNoUmVxdWVzdBINCgV2bV9pZBgBIAEoCRITCgtjdXN0b21lcl9pZBgCIAEoCRInCgdtZXRyaWNzGAMgAygLMhYuYmlsbGFnZWQudjEuVk1NZXRyaWNzIjwKGFNlbmRNZXRyaWNzQmF0Y2hSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEg8KB21lc3NhZ2UYAiABKAkiPwoUU2VuZEhlYXJ0YmVhdFJlcXVlc3QSEwoLaW5zdGFuY2VfaWQYASABKAkSEgoKYWN0aXZlX3ZtcxgCIAMoCSIoChVTZW5kSGVhcnRiZWF0UmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCCJQChZOb3RpZnlWbVN0YXJ0ZWRSZXF1ZXN0Eg0KBXZtX2lkGAEgASgJEhMKC2N1c3RvbWVyX2lkGAIgASgJEhIKCnN0YXJ0X3RpbWUYAyABKAMiKgoXTm90aWZ5Vm1TdGFydGVkUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCCI6ChZOb3RpZnlWbVN0b3BwZWRSZXF1ZXN0Eg0KBXZtX2lkGAEgASgJEhEKCXN0b3BfdGltZRgCIAEoAyIqChdOb3RpZnlWbVN0b3BwZWRSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIIlEKGE5vdGlmeVBvc3NpYmxlR2FwUmVxdWVzdBINCgV2bV9pZBgBIAEoCRIRCglsYXN0X3NlbnQYAiABKAMSEwoLcmVzdW1lX3RpbWUYAyABKAMiLAoZTm90aWZ5UG9zc2libGVHYXBSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIMukDCg5CaWxsaW5nU2VydmljZRJfChBTZW5kTWV0cmljc0JhdGNoEiQuYmlsbGFnZWQudjEuU2VuZE1ldHJpY3NCYXRjaFJlcXVlc3QaJS5iaWxsYWdlZC52MS5TZW5kTWV0cmljc0JhdGNoUmVzcG9uc2USVgoNU2VuZEhlYXJ0YmVhdBIhLmJpbGxhZ2VkLnYxLlNlbmRIZWFydGJlYXRSZXF1ZXN0GiIuYmlsbGFnZWQudjEuU2VuZEhlYXJ0YmVhdFJlc3BvbnNlElwKD05vdGlmeVZtU3RhcnRlZBIjLmJpbGxhZ2VkLnYxLk5vdGlmeVZtU3RhcnRlZFJlcXVlc3QaJC5iaWxsYWdlZC52MS5Ob3RpZnlWbVN0YXJ0ZWRSZXNwb25zZRJcCg9Ob3RpZnlWbVN0b3BwZWQSIy5iaWxsYWdlZC52MS5Ob3RpZnlWbVN0b3BwZWRSZXF1ZXN0GiQuYmlsbGFnZWQudjEuTm90aWZ5Vm1TdG9wcGVkUmVzcG9uc2USYgoRTm90aWZ5UG9zc2libGVHYXASJS5iaWxsYWdlZC52MS5Ob3RpZnlQb3NzaWJsZUdhcFJlcXVlc3QaJi5iaWxsYWdlZC52MS5Ob3RpZnlQb3NzaWJsZUdhcFJlc3BvbnNlQj5aPGdpdGh1Yi5jb20vdW5rZXllZC91bmtleS9nby9nZW4vcHJvdG8vYmlsbGFnZWQvdjE7YmlsbGFnZWR2MWIGcHJvdG8z",
- [file_google_protobuf_timestamp],
- );
-
-/**
- * @generated from message billaged.v1.VMMetrics
- */
-export type VMMetrics = Message<"billaged.v1.VMMetrics"> & {
- /**
- * @generated from field: google.protobuf.Timestamp timestamp = 1;
- */
- timestamp?: Timestamp;
-
- /**
- * @generated from field: int64 cpu_time_nanos = 2;
- */
- cpuTimeNanos: bigint;
-
- /**
- * @generated from field: int64 memory_usage_bytes = 3;
- */
- memoryUsageBytes: bigint;
-
- /**
- * @generated from field: int64 disk_read_bytes = 4;
- */
- diskReadBytes: bigint;
-
- /**
- * @generated from field: int64 disk_write_bytes = 5;
- */
- diskWriteBytes: bigint;
-
- /**
- * @generated from field: int64 network_rx_bytes = 6;
- */
- networkRxBytes: bigint;
-
- /**
- * @generated from field: int64 network_tx_bytes = 7;
- */
- networkTxBytes: bigint;
-};
-
-/**
- * Describes the message billaged.v1.VMMetrics.
- * Use `create(VMMetricsSchema)` to create a new message.
- */
-export const VMMetricsSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_billaged_v1_billing, 0);
-
-/**
- * @generated from message billaged.v1.SendMetricsBatchRequest
- */
-export type SendMetricsBatchRequest = Message<"billaged.v1.SendMetricsBatchRequest"> & {
- /**
- * @generated from field: string vm_id = 1;
- */
- vmId: string;
-
- /**
- * @generated from field: string customer_id = 2;
- */
- customerId: string;
-
- /**
- * @generated from field: repeated billaged.v1.VMMetrics metrics = 3;
- */
- metrics: VMMetrics[];
-};
-
-/**
- * Describes the message billaged.v1.SendMetricsBatchRequest.
- * Use `create(SendMetricsBatchRequestSchema)` to create a new message.
- */
-export const SendMetricsBatchRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_billaged_v1_billing, 1);
-
-/**
- * @generated from message billaged.v1.SendMetricsBatchResponse
- */
-export type SendMetricsBatchResponse = Message<"billaged.v1.SendMetricsBatchResponse"> & {
- /**
- * @generated from field: bool success = 1;
- */
- success: boolean;
-
- /**
- * @generated from field: string message = 2;
- */
- message: string;
-};
-
-/**
- * Describes the message billaged.v1.SendMetricsBatchResponse.
- * Use `create(SendMetricsBatchResponseSchema)` to create a new message.
- */
-export const SendMetricsBatchResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_billaged_v1_billing, 2);
-
-/**
- * @generated from message billaged.v1.SendHeartbeatRequest
- */
-export type SendHeartbeatRequest = Message<"billaged.v1.SendHeartbeatRequest"> & {
- /**
- * @generated from field: string instance_id = 1;
- */
- instanceId: string;
-
- /**
- * @generated from field: repeated string active_vms = 2;
- */
- activeVms: string[];
-};
-
-/**
- * Describes the message billaged.v1.SendHeartbeatRequest.
- * Use `create(SendHeartbeatRequestSchema)` to create a new message.
- */
-export const SendHeartbeatRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_billaged_v1_billing, 3);
-
-/**
- * @generated from message billaged.v1.SendHeartbeatResponse
- */
-export type SendHeartbeatResponse = Message<"billaged.v1.SendHeartbeatResponse"> & {
- /**
- * @generated from field: bool success = 1;
- */
- success: boolean;
-};
-
-/**
- * Describes the message billaged.v1.SendHeartbeatResponse.
- * Use `create(SendHeartbeatResponseSchema)` to create a new message.
- */
-export const SendHeartbeatResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_billaged_v1_billing, 4);
-
-/**
- * @generated from message billaged.v1.NotifyVmStartedRequest
- */
-export type NotifyVmStartedRequest = Message<"billaged.v1.NotifyVmStartedRequest"> & {
- /**
- * @generated from field: string vm_id = 1;
- */
- vmId: string;
-
- /**
- * @generated from field: string customer_id = 2;
- */
- customerId: string;
-
- /**
- * @generated from field: int64 start_time = 3;
- */
- startTime: bigint;
-};
-
-/**
- * Describes the message billaged.v1.NotifyVmStartedRequest.
- * Use `create(NotifyVmStartedRequestSchema)` to create a new message.
- */
-export const NotifyVmStartedRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_billaged_v1_billing, 5);
-
-/**
- * @generated from message billaged.v1.NotifyVmStartedResponse
- */
-export type NotifyVmStartedResponse = Message<"billaged.v1.NotifyVmStartedResponse"> & {
- /**
- * @generated from field: bool success = 1;
- */
- success: boolean;
-};
-
-/**
- * Describes the message billaged.v1.NotifyVmStartedResponse.
- * Use `create(NotifyVmStartedResponseSchema)` to create a new message.
- */
-export const NotifyVmStartedResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_billaged_v1_billing, 6);
-
-/**
- * @generated from message billaged.v1.NotifyVmStoppedRequest
- */
-export type NotifyVmStoppedRequest = Message<"billaged.v1.NotifyVmStoppedRequest"> & {
- /**
- * @generated from field: string vm_id = 1;
- */
- vmId: string;
-
- /**
- * @generated from field: int64 stop_time = 2;
- */
- stopTime: bigint;
-};
-
-/**
- * Describes the message billaged.v1.NotifyVmStoppedRequest.
- * Use `create(NotifyVmStoppedRequestSchema)` to create a new message.
- */
-export const NotifyVmStoppedRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_billaged_v1_billing, 7);
-
-/**
- * @generated from message billaged.v1.NotifyVmStoppedResponse
- */
-export type NotifyVmStoppedResponse = Message<"billaged.v1.NotifyVmStoppedResponse"> & {
- /**
- * @generated from field: bool success = 1;
- */
- success: boolean;
-};
-
-/**
- * Describes the message billaged.v1.NotifyVmStoppedResponse.
- * Use `create(NotifyVmStoppedResponseSchema)` to create a new message.
- */
-export const NotifyVmStoppedResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_billaged_v1_billing, 8);
-
-/**
- * @generated from message billaged.v1.NotifyPossibleGapRequest
- */
-export type NotifyPossibleGapRequest = Message<"billaged.v1.NotifyPossibleGapRequest"> & {
- /**
- * @generated from field: string vm_id = 1;
- */
- vmId: string;
-
- /**
- * @generated from field: int64 last_sent = 2;
- */
- lastSent: bigint;
-
- /**
- * @generated from field: int64 resume_time = 3;
- */
- resumeTime: bigint;
-};
-
-/**
- * Describes the message billaged.v1.NotifyPossibleGapRequest.
- * Use `create(NotifyPossibleGapRequestSchema)` to create a new message.
- */
-export const NotifyPossibleGapRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_billaged_v1_billing, 9);
-
-/**
- * @generated from message billaged.v1.NotifyPossibleGapResponse
- */
-export type NotifyPossibleGapResponse = Message<"billaged.v1.NotifyPossibleGapResponse"> & {
- /**
- * @generated from field: bool success = 1;
- */
- success: boolean;
-};
-
-/**
- * Describes the message billaged.v1.NotifyPossibleGapResponse.
- * Use `create(NotifyPossibleGapResponseSchema)` to create a new message.
- */
-export const NotifyPossibleGapResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_billaged_v1_billing, 10);
-
-/**
- * @generated from service billaged.v1.BillingService
- */
-export const BillingService: GenService<{
- /**
- * @generated from rpc billaged.v1.BillingService.SendMetricsBatch
- */
- sendMetricsBatch: {
- methodKind: "unary";
- input: typeof SendMetricsBatchRequestSchema;
- output: typeof SendMetricsBatchResponseSchema;
- };
- /**
- * @generated from rpc billaged.v1.BillingService.SendHeartbeat
- */
- sendHeartbeat: {
- methodKind: "unary";
- input: typeof SendHeartbeatRequestSchema;
- output: typeof SendHeartbeatResponseSchema;
- };
- /**
- * @generated from rpc billaged.v1.BillingService.NotifyVmStarted
- */
- notifyVmStarted: {
- methodKind: "unary";
- input: typeof NotifyVmStartedRequestSchema;
- output: typeof NotifyVmStartedResponseSchema;
- };
- /**
- * @generated from rpc billaged.v1.BillingService.NotifyVmStopped
- */
- notifyVmStopped: {
- methodKind: "unary";
- input: typeof NotifyVmStoppedRequestSchema;
- output: typeof NotifyVmStoppedResponseSchema;
- };
- /**
- * @generated from rpc billaged.v1.BillingService.NotifyPossibleGap
- */
- notifyPossibleGap: {
- methodKind: "unary";
- input: typeof NotifyPossibleGapRequestSchema;
- output: typeof NotifyPossibleGapResponseSchema;
- };
-}> = /*@__PURE__*/ serviceDesc(file_billaged_v1_billing, 0);
diff --git a/internal/proto/generated/builderd/v1/builder_pb.ts b/internal/proto/generated/builderd/v1/builder_pb.ts
deleted file mode 100644
index 5d534e4c6b..0000000000
--- a/internal/proto/generated/builderd/v1/builder_pb.ts
+++ /dev/null
@@ -1,1703 +0,0 @@
-// @generated by protoc-gen-es v2.8.0 with parameter "target=ts"
-// @generated from file builderd/v1/builder.proto (package builderd.v1, syntax proto3)
-/* eslint-disable */
-
-import type { Message } from "@bufbuild/protobuf";
-import type { GenEnum, GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
-import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
-import type { Timestamp } from "@bufbuild/protobuf/wkt";
-import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt";
-
-/**
- * Describes the file builderd/v1/builder.proto.
- */
-export const file_builderd_v1_builder: GenFile =
- /*@__PURE__*/
- fileDesc(
- "ChlidWlsZGVyZC92MS9idWlsZGVyLnByb3RvEgtidWlsZGVyZC52MSK/AQoLQnVpbGRTb3VyY2USNgoMZG9ja2VyX2ltYWdlGAEgASgLMh4uYnVpbGRlcmQudjEuRG9ja2VySW1hZ2VTb3VyY2VIABI6Cg5naXRfcmVwb3NpdG9yeRgCIAEoCzIgLmJ1aWxkZXJkLnYxLkdpdFJlcG9zaXRvcnlTb3VyY2VIABItCgdhcmNoaXZlGAMgASgLMhouYnVpbGRlcmQudjEuQXJjaGl2ZVNvdXJjZUgAQg0KC3NvdXJjZV90eXBlImAKEURvY2tlckltYWdlU291cmNlEhEKCWltYWdlX3VyaRgBIAEoCRIlCgRhdXRoGAIgASgLMhcuYnVpbGRlcmQudjEuRG9ja2VyQXV0aBIRCglwdWxsX3RhZ3MYAyADKAkiUQoKRG9ja2VyQXV0aBIQCgh1c2VybmFtZRgBIAEoCRIQCghwYXNzd29yZBgCIAEoCRINCgV0b2tlbhgDIAEoCRIQCghyZWdpc3RyeRgEIAEoCSJ1ChNHaXRSZXBvc2l0b3J5U291cmNlEhYKDnJlcG9zaXRvcnlfdXJsGAEgASgJEgsKA3JlZhgCIAEoCRIVCg1idWlsZF9jb250ZXh0GAMgASgJEiIKBGF1dGgYBCABKAsyFC5idWlsZGVyZC52MS5HaXRBdXRoIk0KB0dpdEF1dGgSEAoIdXNlcm5hbWUYASABKAkSEAoIcGFzc3dvcmQYAiABKAkSDwoHc3NoX2tleRgDIAEoCRINCgV0b2tlbhgEIAEoCSJRCg1BcmNoaXZlU291cmNlEhMKC2FyY2hpdmVfdXJsGAEgASgJEhQKDGFyY2hpdmVfdHlwZRgCIAEoCRIVCg1idWlsZF9jb250ZXh0GAMgASgJIooBCgtCdWlsZFRhcmdldBI0Cg5taWNyb3ZtX3Jvb3RmcxgBIAEoCzIaLmJ1aWxkZXJkLnYxLk1pY3JvVk1Sb290ZnNIABI2Cg9jb250YWluZXJfaW1hZ2UYAiABKAsyGy5idWlsZGVyZC52MS5Db250YWluZXJJbWFnZUgAQg0KC3RhcmdldF90eXBlIsYBCg1NaWNyb1ZNUm9vdGZzEjAKDWluaXRfc3RyYXRlZ3kYASABKA4yGS5idWlsZGVyZC52MS5Jbml0U3RyYXRlZ3kSMgoOcnVudGltZV9jb25maWcYAiABKAsyGi5idWlsZGVyZC52MS5SdW50aW1lQ29uZmlnEjcKDG9wdGltaXphdGlvbhgDIAEoCzIhLmJ1aWxkZXJkLnYxLk9wdGltaXphdGlvblNldHRpbmdzEhYKDnByZXNlcnZlX3BhdGhzGAQgAygJIjQKDkNvbnRhaW5lckltYWdlEhIKCmJhc2VfaW1hZ2UYASABKAkSDgoGbGF5ZXJzGAIgAygJItYBCg1SdW50aW1lQ29uZmlnEg8KB2NvbW1hbmQYASADKAkSEgoKZW50cnlwb2ludBgCIAMoCRITCgt3b3JraW5nX2RpchgDIAEoCRJACgtlbnZpcm9ubWVudBgEIAMoCzIrLmJ1aWxkZXJkLnYxLlJ1bnRpbWVDb25maWcuRW52aXJvbm1lbnRFbnRyeRIVCg1leHBvc2VkX3BvcnRzGAUgAygJGjIKEEVudmlyb25tZW50RW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASKrAQoUT3B0aW1pemF0aW9uU2V0dGluZ3MSGwoTc3RyaXBfZGVidWdfc3ltYm9scxgBIAEoCBIZChFjb21wcmVzc19iaW5hcmllcxgCIAEoCBITCgtyZW1vdmVfZG9jcxgDIAEoCBIUCgxyZW1vdmVfY2FjaGUYBCABKAgSFgoOcHJlc2VydmVfcGF0aHMYBSADKAkSGAoQZXhjbHVkZV9wYXR0ZXJucxgGIAMoCSLsAQoNQnVpbGRTdHJhdGVneRI8Cg5kb2NrZXJfZXh0cmFjdBgBIAEoCzIiLmJ1aWxkZXJkLnYxLkRvY2tlckV4dHJhY3RTdHJhdGVneUgAEiwKBmdvX2FwaRgCIAEoCzIaLmJ1aWxkZXJkLnYxLkdvQXBpU3RyYXRlZ3lIABIvCgdzaW5hdHJhGAMgASgLMhwuYnVpbGRlcmQudjEuU2luYXRyYVN0cmF0ZWd5SAASLQoGbm9kZWpzGAQgASgLMhsuYnVpbGRlcmQudjEuTm9kZWpzU3RyYXRlZ3lIAEIPCg1zdHJhdGVneV90eXBlImYKFURvY2tlckV4dHJhY3RTdHJhdGVneRIXCg9wcmVzZXJ2ZV9sYXllcnMYASABKAgSGgoSZmxhdHRlbl9maWxlc3lzdGVtGAIgASgIEhgKEGV4Y2x1ZGVfcGF0dGVybnMYAyADKAkiYgoNR29BcGlTdHJhdGVneRISCgpnb192ZXJzaW9uGAEgASgJEhMKC2J1aWxkX2ZsYWdzGAIgAygJEhQKDG1haW5fcGFja2FnZRgDIAEoCRISCgplbmFibGVfY2dvGAQgASgIIsgBCg9TaW5hdHJhU3RyYXRlZ3kSFAoMcnVieV92ZXJzaW9uGAEgASgJEhQKDGdlbWZpbGVfcGF0aBgCIAEoCRITCgtyYWNrX3NlcnZlchgDIAEoCRJBCgtyYWNrX2NvbmZpZxgEIAMoCzIsLmJ1aWxkZXJkLnYxLlNpbmF0cmFTdHJhdGVneS5SYWNrQ29uZmlnRW50cnkaMQoPUmFja0NvbmZpZ0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEicAoOTm9kZWpzU3RyYXRlZ3kSFAoMbm9kZV92ZXJzaW9uGAEgASgJEhcKD3BhY2thZ2VfbWFuYWdlchgCIAEoCRIUCgxzdGFydF9zY3JpcHQYAyABKAkSGQoRZW5hYmxlX3Byb2R1Y3Rpb24YBCABKAgipAIKC0J1aWxkQ29uZmlnEigKBnNvdXJjZRgBIAEoCzIYLmJ1aWxkZXJkLnYxLkJ1aWxkU291cmNlEigKBnRhcmdldBgCIAEoCzIYLmJ1aWxkZXJkLnYxLkJ1aWxkVGFyZ2V0EiwKCHN0cmF0ZWd5GAMgASgLMhouYnVpbGRlcmQudjEuQnVpbGRTdHJhdGVneRISCgpidWlsZF9uYW1lGAQgASgJEjQKBmxhYmVscxgFIAMoCzIkLmJ1aWxkZXJkLnYxLkJ1aWxkQ29uZmlnLkxhYmVsc0VudHJ5EhoKEnN1Z2dlc3RlZF9hc3NldF9pZBgGIAEoCRotCgtMYWJlbHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIo0BCg5CdWlsZElzb2xhdGlvbhISCgpzYW5kYm94X2lkGAEgASgJEhkKEW5ldHdvcmtfbmFtZXNwYWNlGAIgASgJEhwKFGZpbGVzeXN0ZW1fbmFtZXNwYWNlGAMgASgJEhkKEXNlY3VyaXR5X2NvbnRleHRzGAQgAygJEhMKC2Nncm91cF9wYXRoGAUgASgJIoIDCg1JbWFnZU1ldGFkYXRhEhYKDm9yaWdpbmFsX2ltYWdlGAEgASgJEhQKDGltYWdlX2RpZ2VzdBgCIAEoCRIOCgZsYXllcnMYAyADKAkSNgoGbGFiZWxzGAQgAygLMiYuYnVpbGRlcmQudjEuSW1hZ2VNZXRhZGF0YS5MYWJlbHNFbnRyeRIPCgdjb21tYW5kGAUgAygJEhIKCmVudHJ5cG9pbnQYBiADKAkSEwoLd29ya2luZ19kaXIYByABKAkSMAoDZW52GAggAygLMiMuYnVpbGRlcmQudjEuSW1hZ2VNZXRhZGF0YS5FbnZFbnRyeRIVCg1leHBvc2VkX3BvcnRzGAkgAygJEgwKBHVzZXIYCiABKAkSDwoHdm9sdW1lcxgLIAMoCRotCgtMYWJlbHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBGioKCEVudkVudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiuQIKDEJ1aWxkTWV0cmljcxIYChBwdWxsX2R1cmF0aW9uX21zGAEgASgDEhsKE2V4dHJhY3RfZHVyYXRpb25fbXMYAiABKAMSGQoRYnVpbGRfZHVyYXRpb25fbXMYAyABKAMSHAoUb3B0aW1pemVfZHVyYXRpb25fbXMYBCABKAMSGQoRdG90YWxfZHVyYXRpb25fbXMYBSABKAMSGwoTb3JpZ2luYWxfc2l6ZV9ieXRlcxgGIAEoAxIZChFyb290ZnNfc2l6ZV9ieXRlcxgHIAEoAxIZChFjb21wcmVzc2lvbl9yYXRpbxgIIAEoAxIZChFtZW1vcnlfcGVha19ieXRlcxgJIAEoAxIYChBkaXNrX3VzYWdlX2J5dGVzGAogASgDEhYKDmNwdV9jb3Jlc191c2VkGAsgASgFIrQECghCdWlsZEpvYhIQCghidWlsZF9pZBgBIAEoCRIoCgZjb25maWcYAiABKAsyGC5idWlsZGVyZC52MS5CdWlsZENvbmZpZxImCgVzdGF0ZRgDIAEoDjIXLmJ1aWxkZXJkLnYxLkJ1aWxkU3RhdGUSLgoKY3JlYXRlZF9hdBgEIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKc3RhcnRlZF9hdBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASMAoMY29tcGxldGVkX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBITCgtyb290ZnNfcGF0aBgHIAEoCRIZChFyb290ZnNfc2l6ZV9ieXRlcxgIIAEoAxIXCg9yb290ZnNfY2hlY2tzdW0YCSABKAkSMgoOaW1hZ2VfbWV0YWRhdGEYCiABKAsyGi5idWlsZGVyZC52MS5JbWFnZU1ldGFkYXRhEioKB21ldHJpY3MYCyABKAsyGS5idWlsZGVyZC52MS5CdWlsZE1ldHJpY3MSLgoJaXNvbGF0aW9uGAwgASgLMhsuYnVpbGRlcmQudjEuQnVpbGRJc29sYXRpb24SFQoNZXJyb3JfbWVzc2FnZRgNIAEoCRISCgpidWlsZF9sb2dzGA4gAygJEhgKEHByb2dyZXNzX3BlcmNlbnQYDyABKAUSFAoMY3VycmVudF9zdGVwGBAgASgJIvIBChdTdHJlYW1CdWlsZExvZ3NSZXNwb25zZRItCgl0aW1lc3RhbXAYASABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEg0KBWxldmVsGAIgASgJEg8KB21lc3NhZ2UYAyABKAkSEQoJY29tcG9uZW50GAQgASgJEkQKCG1ldGFkYXRhGAUgAygLMjIuYnVpbGRlcmQudjEuU3RyZWFtQnVpbGRMb2dzUmVzcG9uc2UuTWV0YWRhdGFFbnRyeRovCg1NZXRhZGF0YUVudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiPgoSQ3JlYXRlQnVpbGRSZXF1ZXN0EigKBmNvbmZpZxgBIAEoCzIYLmJ1aWxkZXJkLnYxLkJ1aWxkQ29uZmlnIpQBChNDcmVhdGVCdWlsZFJlc3BvbnNlEhAKCGJ1aWxkX2lkGAEgASgJEiYKBXN0YXRlGAIgASgOMhcuYnVpbGRlcmQudjEuQnVpbGRTdGF0ZRIuCgpjcmVhdGVkX2F0GAMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBITCgtyb290ZnNfcGF0aBgEIAEoCSI2Cg9HZXRCdWlsZFJlcXVlc3QSEAoIYnVpbGRfaWQYASABKAkSEQoJdGVuYW50X2lkGAIgASgJIjgKEEdldEJ1aWxkUmVzcG9uc2USJAoFYnVpbGQYASABKAsyFS5idWlsZGVyZC52MS5CdWlsZEpvYiJpChFMaXN0QnVpbGRzUmVxdWVzdBItCgxzdGF0ZV9maWx0ZXIYASADKA4yFy5idWlsZGVyZC52MS5CdWlsZFN0YXRlEhEKCXBhZ2Vfc2l6ZRgCIAEoBRISCgpwYWdlX3Rva2VuGAMgASgJImkKEkxpc3RCdWlsZHNSZXNwb25zZRIlCgZidWlsZHMYASADKAsyFS5idWlsZGVyZC52MS5CdWlsZEpvYhIXCg9uZXh0X3BhZ2VfdG9rZW4YAiABKAkSEwoLdG90YWxfY291bnQYAyABKAUiJgoSQ2FuY2VsQnVpbGRSZXF1ZXN0EhAKCGJ1aWxkX2lkGAEgASgJIk4KE0NhbmNlbEJ1aWxkUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCBImCgVzdGF0ZRgCIAEoDjIXLmJ1aWxkZXJkLnYxLkJ1aWxkU3RhdGUiNQoSRGVsZXRlQnVpbGRSZXF1ZXN0EhAKCGJ1aWxkX2lkGAEgASgJEg0KBWZvcmNlGAIgASgIIiYKE0RlbGV0ZUJ1aWxkUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCCI6ChZTdHJlYW1CdWlsZExvZ3NSZXF1ZXN0EhAKCGJ1aWxkX2lkGAEgASgJEg4KBmZvbGxvdxgCIAEoCCKHAQoUR2V0QnVpbGRTdGF0c1JlcXVlc3QSEQoJdGVuYW50X2lkGAEgASgJEi4KCnN0YXJ0X3RpbWUYAiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEiwKCGVuZF90aW1lGAMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCLkAQoVR2V0QnVpbGRTdGF0c1Jlc3BvbnNlEhQKDHRvdGFsX2J1aWxkcxgBIAEoBRIZChFzdWNjZXNzZnVsX2J1aWxkcxgCIAEoBRIVCg1mYWlsZWRfYnVpbGRzGAMgASgFEhkKEWF2Z19idWlsZF90aW1lX21zGAQgASgDEhsKE3RvdGFsX3N0b3JhZ2VfYnl0ZXMYBSABKAMSHQoVdG90YWxfY29tcHV0ZV9taW51dGVzGAYgASgDEiwKDXJlY2VudF9idWlsZHMYByADKAsyFS5idWlsZGVyZC52MS5CdWlsZEpvYiqVAgoKQnVpbGRTdGF0ZRIbChdCVUlMRF9TVEFURV9VTlNQRUNJRklFRBAAEhcKE0JVSUxEX1NUQVRFX1BFTkRJTkcQARIXChNCVUlMRF9TVEFURV9QVUxMSU5HEAISGgoWQlVJTERfU1RBVEVfRVhUUkFDVElORxADEhgKFEJVSUxEX1NUQVRFX0JVSUxESU5HEAQSGgoWQlVJTERfU1RBVEVfT1BUSU1JWklORxAFEhkKFUJVSUxEX1NUQVRFX0NPTVBMRVRFRBAGEhYKEkJVSUxEX1NUQVRFX0ZBSUxFRBAHEhkKFUJVSUxEX1NUQVRFX0NBTkNFTExFRBAIEhgKFEJVSUxEX1NUQVRFX0NMRUFOSU5HEAkqiwEKClRlbmFudFRpZXISGwoXVEVOQU5UX1RJRVJfVU5TUEVDSUZJRUQQABIUChBURU5BTlRfVElFUl9GUkVFEAESEwoPVEVOQU5UX1RJRVJfUFJPEAISGgoWVEVOQU5UX1RJRVJfRU5URVJQUklTRRADEhkKFVRFTkFOVF9USUVSX0RFRElDQVRFRBAEKnkKDEluaXRTdHJhdGVneRIdChlJTklUX1NUUkFURUdZX1VOU1BFQ0lGSUVEEAASFgoSSU5JVF9TVFJBVEVHWV9USU5JEAESGAoUSU5JVF9TVFJBVEVHWV9ESVJFQ1QQAhIYChRJTklUX1NUUkFURUdZX0NVU1RPTRADMtYECg5CdWlsZGVyU2VydmljZRJQCgtDcmVhdGVCdWlsZBIfLmJ1aWxkZXJkLnYxLkNyZWF0ZUJ1aWxkUmVxdWVzdBogLmJ1aWxkZXJkLnYxLkNyZWF0ZUJ1aWxkUmVzcG9uc2USRwoIR2V0QnVpbGQSHC5idWlsZGVyZC52MS5HZXRCdWlsZFJlcXVlc3QaHS5idWlsZGVyZC52MS5HZXRCdWlsZFJlc3BvbnNlEk0KCkxpc3RCdWlsZHMSHi5idWlsZGVyZC52MS5MaXN0QnVpbGRzUmVxdWVzdBofLmJ1aWxkZXJkLnYxLkxpc3RCdWlsZHNSZXNwb25zZRJQCgtDYW5jZWxCdWlsZBIfLmJ1aWxkZXJkLnYxLkNhbmNlbEJ1aWxkUmVxdWVzdBogLmJ1aWxkZXJkLnYxLkNhbmNlbEJ1aWxkUmVzcG9uc2USUAoLRGVsZXRlQnVpbGQSHy5idWlsZGVyZC52MS5EZWxldGVCdWlsZFJlcXVlc3QaIC5idWlsZGVyZC52MS5EZWxldGVCdWlsZFJlc3BvbnNlEl4KD1N0cmVhbUJ1aWxkTG9ncxIjLmJ1aWxkZXJkLnYxLlN0cmVhbUJ1aWxkTG9nc1JlcXVlc3QaJC5idWlsZGVyZC52MS5TdHJlYW1CdWlsZExvZ3NSZXNwb25zZTABElYKDUdldEJ1aWxkU3RhdHMSIS5idWlsZGVyZC52MS5HZXRCdWlsZFN0YXRzUmVxdWVzdBoiLmJ1aWxkZXJkLnYxLkdldEJ1aWxkU3RhdHNSZXNwb25zZUI+WjxnaXRodWIuY29tL3Vua2V5ZWQvdW5rZXkvZ28vZ2VuL3Byb3RvL2J1aWxkZXJkL3YxO2J1aWxkZXJkdjFiBnByb3RvMw",
- [file_google_protobuf_timestamp],
- );
-
-/**
- * Build source types - extensible for future build types
- *
- * @generated from message builderd.v1.BuildSource
- */
-export type BuildSource = Message<"builderd.v1.BuildSource"> & {
- /**
- * @generated from oneof builderd.v1.BuildSource.source_type
- */
- sourceType:
- | {
- /**
- * @generated from field: builderd.v1.DockerImageSource docker_image = 1;
- */
- value: DockerImageSource;
- case: "dockerImage";
- }
- | {
- /**
- * @generated from field: builderd.v1.GitRepositorySource git_repository = 2;
- */
- value: GitRepositorySource;
- case: "gitRepository";
- }
- | {
- /**
- * Future: nix_flake = 4, buildpack = 5, etc.
- *
- * @generated from field: builderd.v1.ArchiveSource archive = 3;
- */
- value: ArchiveSource;
- case: "archive";
- }
- | { case: undefined; value?: undefined };
-};
-
-/**
- * Describes the message builderd.v1.BuildSource.
- * Use `create(BuildSourceSchema)` to create a new message.
- */
-export const BuildSourceSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 0);
-
-/**
- * Docker image extraction (first implementation)
- *
- * @generated from message builderd.v1.DockerImageSource
- */
-export type DockerImageSource = Message<"builderd.v1.DockerImageSource"> & {
- /**
- * "ghcr.io/unkeyed/unkey:f4cfee5"
- *
- * @generated from field: string image_uri = 1;
- */
- imageUri: string;
-
- /**
- * Registry authentication
- *
- * @generated from field: builderd.v1.DockerAuth auth = 2;
- */
- auth?: DockerAuth;
-
- /**
- * Additional tags to consider
- *
- * @generated from field: repeated string pull_tags = 3;
- */
- pullTags: string[];
-};
-
-/**
- * Describes the message builderd.v1.DockerImageSource.
- * Use `create(DockerImageSourceSchema)` to create a new message.
- */
-export const DockerImageSourceSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 1);
-
-/**
- * @generated from message builderd.v1.DockerAuth
- */
-export type DockerAuth = Message<"builderd.v1.DockerAuth"> & {
- /**
- * @generated from field: string username = 1;
- */
- username: string;
-
- /**
- * @generated from field: string password = 2;
- */
- password: string;
-
- /**
- * @generated from field: string token = 3;
- */
- token: string;
-
- /**
- * @generated from field: string registry = 4;
- */
- registry: string;
-};
-
-/**
- * Describes the message builderd.v1.DockerAuth.
- * Use `create(DockerAuthSchema)` to create a new message.
- */
-export const DockerAuthSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 2);
-
-/**
- * Git repository builds (future)
- *
- * @generated from message builderd.v1.GitRepositorySource
- */
-export type GitRepositorySource = Message<"builderd.v1.GitRepositorySource"> & {
- /**
- * "https://github.com/unkeyed/unkey"
- *
- * @generated from field: string repository_url = 1;
- */
- repositoryUrl: string;
-
- /**
- * branch/tag/commit
- *
- * @generated from field: string ref = 2;
- */
- ref: string;
-
- /**
- * subdirectory if needed
- *
- * @generated from field: string build_context = 3;
- */
- buildContext: string;
-
- /**
- * @generated from field: builderd.v1.GitAuth auth = 4;
- */
- auth?: GitAuth;
-};
-
-/**
- * Describes the message builderd.v1.GitRepositorySource.
- * Use `create(GitRepositorySourceSchema)` to create a new message.
- */
-export const GitRepositorySourceSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 3);
-
-/**
- * @generated from message builderd.v1.GitAuth
- */
-export type GitAuth = Message<"builderd.v1.GitAuth"> & {
- /**
- * @generated from field: string username = 1;
- */
- username: string;
-
- /**
- * @generated from field: string password = 2;
- */
- password: string;
-
- /**
- * @generated from field: string ssh_key = 3;
- */
- sshKey: string;
-
- /**
- * @generated from field: string token = 4;
- */
- token: string;
-};
-
-/**
- * Describes the message builderd.v1.GitAuth.
- * Use `create(GitAuthSchema)` to create a new message.
- */
-export const GitAuthSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 4);
-
-/**
- * Archive builds (future)
- *
- * @generated from message builderd.v1.ArchiveSource
- */
-export type ArchiveSource = Message<"builderd.v1.ArchiveSource"> & {
- /**
- * URL to tar.gz, zip, etc.
- *
- * @generated from field: string archive_url = 1;
- */
- archiveUrl: string;
-
- /**
- * "tar.gz", "zip"
- *
- * @generated from field: string archive_type = 2;
- */
- archiveType: string;
-
- /**
- * subdirectory in archive
- *
- * @generated from field: string build_context = 3;
- */
- buildContext: string;
-};
-
-/**
- * Describes the message builderd.v1.ArchiveSource.
- * Use `create(ArchiveSourceSchema)` to create a new message.
- */
-export const ArchiveSourceSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 5);
-
-/**
- * Build target types - extensible
- *
- * @generated from message builderd.v1.BuildTarget
- */
-export type BuildTarget = Message<"builderd.v1.BuildTarget"> & {
- /**
- * @generated from oneof builderd.v1.BuildTarget.target_type
- */
- targetType:
- | {
- /**
- * @generated from field: builderd.v1.MicroVMRootfs microvm_rootfs = 1;
- */
- value: MicroVMRootfs;
- case: "microvmRootfs";
- }
- | {
- /**
- * Future: wasm_module = 3, lambda_layer = 4, etc.
- *
- * @generated from field: builderd.v1.ContainerImage container_image = 2;
- */
- value: ContainerImage;
- case: "containerImage";
- }
- | { case: undefined; value?: undefined };
-};
-
-/**
- * Describes the message builderd.v1.BuildTarget.
- * Use `create(BuildTargetSchema)` to create a new message.
- */
-export const BuildTargetSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 6);
-
-/**
- * MicroVM rootfs (our focus)
- *
- * @generated from message builderd.v1.MicroVMRootfs
- */
-export type MicroVMRootfs = Message<"builderd.v1.MicroVMRootfs"> & {
- /**
- * @generated from field: builderd.v1.InitStrategy init_strategy = 1;
- */
- initStrategy: InitStrategy;
-
- /**
- * @generated from field: builderd.v1.RuntimeConfig runtime_config = 2;
- */
- runtimeConfig?: RuntimeConfig;
-
- /**
- * @generated from field: builderd.v1.OptimizationSettings optimization = 3;
- */
- optimization?: OptimizationSettings;
-
- /**
- * @generated from field: repeated string preserve_paths = 4;
- */
- preservePaths: string[];
-};
-
-/**
- * Describes the message builderd.v1.MicroVMRootfs.
- * Use `create(MicroVMRootfsSchema)` to create a new message.
- */
-export const MicroVMRootfsSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 7);
-
-/**
- * Container image (future)
- *
- * @generated from message builderd.v1.ContainerImage
- */
-export type ContainerImage = Message<"builderd.v1.ContainerImage"> & {
- /**
- * @generated from field: string base_image = 1;
- */
- baseImage: string;
-
- /**
- * @generated from field: repeated string layers = 2;
- */
- layers: string[];
-};
-
-/**
- * Describes the message builderd.v1.ContainerImage.
- * Use `create(ContainerImageSchema)` to create a new message.
- */
-export const ContainerImageSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 8);
-
-/**
- * @generated from message builderd.v1.RuntimeConfig
- */
-export type RuntimeConfig = Message<"builderd.v1.RuntimeConfig"> & {
- /**
- * Override CMD
- *
- * @generated from field: repeated string command = 1;
- */
- command: string[];
-
- /**
- * Override ENTRYPOINT
- *
- * @generated from field: repeated string entrypoint = 2;
- */
- entrypoint: string[];
-
- /**
- * Override WORKDIR
- *
- * @generated from field: string working_dir = 3;
- */
- workingDir: string;
-
- /**
- * Environment variables
- *
- * @generated from field: map environment = 4;
- */
- environment: { [key: string]: string };
-
- /**
- * Ports to expose
- *
- * @generated from field: repeated string exposed_ports = 5;
- */
- exposedPorts: string[];
-};
-
-/**
- * Describes the message builderd.v1.RuntimeConfig.
- * Use `create(RuntimeConfigSchema)` to create a new message.
- */
-export const RuntimeConfigSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 9);
-
-/**
- * @generated from message builderd.v1.OptimizationSettings
- */
-export type OptimizationSettings = Message<"builderd.v1.OptimizationSettings"> & {
- /**
- * Strip debug info
- *
- * @generated from field: bool strip_debug_symbols = 1;
- */
- stripDebugSymbols: boolean;
-
- /**
- * Compress with UPX
- *
- * @generated from field: bool compress_binaries = 2;
- */
- compressBinaries: boolean;
-
- /**
- * Remove documentation
- *
- * @generated from field: bool remove_docs = 3;
- */
- removeDocs: boolean;
-
- /**
- * Remove package caches
- *
- * @generated from field: bool remove_cache = 4;
- */
- removeCache: boolean;
-
- /**
- * Paths to always keep
- *
- * @generated from field: repeated string preserve_paths = 5;
- */
- preservePaths: string[];
-
- /**
- * Files to exclude
- *
- * @generated from field: repeated string exclude_patterns = 6;
- */
- excludePatterns: string[];
-};
-
-/**
- * Describes the message builderd.v1.OptimizationSettings.
- * Use `create(OptimizationSettingsSchema)` to create a new message.
- */
-export const OptimizationSettingsSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 10);
-
-/**
- * Build strategies - how to build from source to target
- *
- * @generated from message builderd.v1.BuildStrategy
- */
-export type BuildStrategy = Message<"builderd.v1.BuildStrategy"> & {
- /**
- * @generated from oneof builderd.v1.BuildStrategy.strategy_type
- */
- strategyType:
- | {
- /**
- * @generated from field: builderd.v1.DockerExtractStrategy docker_extract = 1;
- */
- value: DockerExtractStrategy;
- case: "dockerExtract";
- }
- | {
- /**
- * @generated from field: builderd.v1.GoApiStrategy go_api = 2;
- */
- value: GoApiStrategy;
- case: "goApi";
- }
- | {
- /**
- * @generated from field: builderd.v1.SinatraStrategy sinatra = 3;
- */
- value: SinatraStrategy;
- case: "sinatra";
- }
- | {
- /**
- * Future: python_wsgi = 5, rust_binary = 6, etc.
- *
- * @generated from field: builderd.v1.NodejsStrategy nodejs = 4;
- */
- value: NodejsStrategy;
- case: "nodejs";
- }
- | { case: undefined; value?: undefined };
-};
-
-/**
- * Describes the message builderd.v1.BuildStrategy.
- * Use `create(BuildStrategySchema)` to create a new message.
- */
-export const BuildStrategySchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 11);
-
-/**
- * Docker extraction strategy (first implementation)
- *
- * @generated from message builderd.v1.DockerExtractStrategy
- */
-export type DockerExtractStrategy = Message<"builderd.v1.DockerExtractStrategy"> & {
- /**
- * Keep layer structure
- *
- * @generated from field: bool preserve_layers = 1;
- */
- preserveLayers: boolean;
-
- /**
- * Merge all layers
- *
- * @generated from field: bool flatten_filesystem = 2;
- */
- flattenFilesystem: boolean;
-
- /**
- * Files to exclude
- *
- * @generated from field: repeated string exclude_patterns = 3;
- */
- excludePatterns: string[];
-};
-
-/**
- * Describes the message builderd.v1.DockerExtractStrategy.
- * Use `create(DockerExtractStrategySchema)` to create a new message.
- */
-export const DockerExtractStrategySchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 12);
-
-/**
- * Go API strategy (future)
- *
- * @generated from message builderd.v1.GoApiStrategy
- */
-export type GoApiStrategy = Message<"builderd.v1.GoApiStrategy"> & {
- /**
- * "1.21", "latest"
- *
- * @generated from field: string go_version = 1;
- */
- goVersion: string;
-
- /**
- * "-ldflags", "-tags"
- *
- * @generated from field: repeated string build_flags = 2;
- */
- buildFlags: string[];
-
- /**
- * "./cmd/api"
- *
- * @generated from field: string main_package = 3;
- */
- mainPackage: string;
-
- /**
- * @generated from field: bool enable_cgo = 4;
- */
- enableCgo: boolean;
-};
-
-/**
- * Describes the message builderd.v1.GoApiStrategy.
- * Use `create(GoApiStrategySchema)` to create a new message.
- */
-export const GoApiStrategySchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 13);
-
-/**
- * Sinatra strategy (future)
- *
- * @generated from message builderd.v1.SinatraStrategy
- */
-export type SinatraStrategy = Message<"builderd.v1.SinatraStrategy"> & {
- /**
- * "3.2", "latest"
- *
- * @generated from field: string ruby_version = 1;
- */
- rubyVersion: string;
-
- /**
- * "Gemfile"
- *
- * @generated from field: string gemfile_path = 2;
- */
- gemfilePath: string;
-
- /**
- * "puma", "unicorn"
- *
- * @generated from field: string rack_server = 3;
- */
- rackServer: string;
-
- /**
- * Server-specific config
- *
- * @generated from field: map rack_config = 4;
- */
- rackConfig: { [key: string]: string };
-};
-
-/**
- * Describes the message builderd.v1.SinatraStrategy.
- * Use `create(SinatraStrategySchema)` to create a new message.
- */
-export const SinatraStrategySchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 14);
-
-/**
- * Node.js strategy (future)
- *
- * @generated from message builderd.v1.NodejsStrategy
- */
-export type NodejsStrategy = Message<"builderd.v1.NodejsStrategy"> & {
- /**
- * "18", "20", "latest"
- *
- * @generated from field: string node_version = 1;
- */
- nodeVersion: string;
-
- /**
- * "npm", "yarn", "pnpm"
- *
- * @generated from field: string package_manager = 2;
- */
- packageManager: string;
-
- /**
- * "start", "server"
- *
- * @generated from field: string start_script = 3;
- */
- startScript: string;
-
- /**
- * NODE_ENV=production
- *
- * @generated from field: bool enable_production = 4;
- */
- enableProduction: boolean;
-};
-
-/**
- * Describes the message builderd.v1.NodejsStrategy.
- * Use `create(NodejsStrategySchema)` to create a new message.
- */
-export const NodejsStrategySchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 15);
-
-/**
- * Main build configuration
- *
- * Tenant identification
- *
- * @generated from message builderd.v1.BuildConfig
- */
-export type BuildConfig = Message<"builderd.v1.BuildConfig"> & {
- /**
- * What we're building from
- *
- * @generated from field: builderd.v1.BuildSource source = 1;
- */
- source?: BuildSource;
-
- /**
- * What we're building to
- *
- * @generated from field: builderd.v1.BuildTarget target = 2;
- */
- target?: BuildTarget;
-
- /**
- * How to build it
- *
- * @generated from field: builderd.v1.BuildStrategy strategy = 3;
- */
- strategy?: BuildStrategy;
-
- /**
- * Build metadata
- *
- * Human-readable name
- *
- * @generated from field: string build_name = 4;
- */
- buildName: string;
-
- /**
- * Custom labels
- *
- * @generated from field: map labels = 5;
- */
- labels: { [key: string]: string };
-
- /**
- * Suggested asset ID to use when registering the built artifact
- * This allows the caller to pre-generate the asset ID
- *
- * @generated from field: string suggested_asset_id = 6;
- */
- suggestedAssetId: string;
-};
-
-/**
- * Describes the message builderd.v1.BuildConfig.
- * Use `create(BuildConfigSchema)` to create a new message.
- */
-export const BuildConfigSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 16);
-
-/**
- * Build isolation metadata
- *
- * @generated from message builderd.v1.BuildIsolation
- */
-export type BuildIsolation = Message<"builderd.v1.BuildIsolation"> & {
- /**
- * Unique sandbox identifier
- *
- * @generated from field: string sandbox_id = 1;
- */
- sandboxId: string;
-
- /**
- * Network isolation
- *
- * @generated from field: string network_namespace = 2;
- */
- networkNamespace: string;
-
- /**
- * Filesystem isolation
- *
- * @generated from field: string filesystem_namespace = 3;
- */
- filesystemNamespace: string;
-
- /**
- * SELinux/AppArmor contexts
- *
- * @generated from field: repeated string security_contexts = 4;
- */
- securityContexts: string[];
-
- /**
- * Resource cgroup
- *
- * @generated from field: string cgroup_path = 5;
- */
- cgroupPath: string;
-};
-
-/**
- * Describes the message builderd.v1.BuildIsolation.
- * Use `create(BuildIsolationSchema)` to create a new message.
- */
-export const BuildIsolationSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 17);
-
-/**
- * Image metadata extracted from Docker images
- *
- * @generated from message builderd.v1.ImageMetadata
- */
-export type ImageMetadata = Message<"builderd.v1.ImageMetadata"> & {
- /**
- * Original Docker image
- *
- * @generated from field: string original_image = 1;
- */
- originalImage: string;
-
- /**
- * Docker image SHA256
- *
- * @generated from field: string image_digest = 2;
- */
- imageDigest: string;
-
- /**
- * Layer digests
- *
- * @generated from field: repeated string layers = 3;
- */
- layers: string[];
-
- /**
- * Docker labels
- *
- * @generated from field: map labels = 4;
- */
- labels: { [key: string]: string };
-
- /**
- * Original CMD
- *
- * @generated from field: repeated string command = 5;
- */
- command: string[];
-
- /**
- * Original ENTRYPOINT
- *
- * @generated from field: repeated string entrypoint = 6;
- */
- entrypoint: string[];
-
- /**
- * WORKDIR
- *
- * @generated from field: string working_dir = 7;
- */
- workingDir: string;
-
- /**
- * Environment variables
- *
- * @generated from field: map env = 8;
- */
- env: { [key: string]: string };
-
- /**
- * EXPOSE ports
- *
- * @generated from field: repeated string exposed_ports = 9;
- */
- exposedPorts: string[];
-
- /**
- * USER directive
- *
- * @generated from field: string user = 10;
- */
- user: string;
-
- /**
- * VOLUME directives
- *
- * @generated from field: repeated string volumes = 11;
- */
- volumes: string[];
-};
-
-/**
- * Describes the message builderd.v1.ImageMetadata.
- * Use `create(ImageMetadataSchema)` to create a new message.
- */
-export const ImageMetadataSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 18);
-
-/**
- * Build performance metrics
- *
- * @generated from message builderd.v1.BuildMetrics
- */
-export type BuildMetrics = Message<"builderd.v1.BuildMetrics"> & {
- /**
- * Time to pull image/source
- *
- * @generated from field: int64 pull_duration_ms = 1;
- */
- pullDurationMs: bigint;
-
- /**
- * Time to extract layers
- *
- * @generated from field: int64 extract_duration_ms = 2;
- */
- extractDurationMs: bigint;
-
- /**
- * Time to build rootfs
- *
- * @generated from field: int64 build_duration_ms = 3;
- */
- buildDurationMs: bigint;
-
- /**
- * Time for optimizations
- *
- * @generated from field: int64 optimize_duration_ms = 4;
- */
- optimizeDurationMs: bigint;
-
- /**
- * Total build time
- *
- * @generated from field: int64 total_duration_ms = 5;
- */
- totalDurationMs: bigint;
-
- /**
- * Original image/source size
- *
- * @generated from field: int64 original_size_bytes = 6;
- */
- originalSizeBytes: bigint;
-
- /**
- * Final rootfs size
- *
- * @generated from field: int64 rootfs_size_bytes = 7;
- */
- rootfsSizeBytes: bigint;
-
- /**
- * Size reduction percentage
- *
- * @generated from field: int64 compression_ratio = 8;
- */
- compressionRatio: bigint;
-
- /**
- * Peak memory usage
- *
- * @generated from field: int64 memory_peak_bytes = 9;
- */
- memoryPeakBytes: bigint;
-
- /**
- * Temporary disk usage
- *
- * @generated from field: int64 disk_usage_bytes = 10;
- */
- diskUsageBytes: bigint;
-
- /**
- * CPU cores utilized
- *
- * @generated from field: int32 cpu_cores_used = 11;
- */
- cpuCoresUsed: number;
-};
-
-/**
- * Describes the message builderd.v1.BuildMetrics.
- * Use `create(BuildMetricsSchema)` to create a new message.
- */
-export const BuildMetricsSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 19);
-
-/**
- * Complete build job information
- *
- * @generated from message builderd.v1.BuildJob
- */
-export type BuildJob = Message<"builderd.v1.BuildJob"> & {
- /**
- * Unique build identifier
- *
- * @generated from field: string build_id = 1;
- */
- buildId: string;
-
- /**
- * Build configuration
- *
- * @generated from field: builderd.v1.BuildConfig config = 2;
- */
- config?: BuildConfig;
-
- /**
- * Current build state
- *
- * @generated from field: builderd.v1.BuildState state = 3;
- */
- state: BuildState;
-
- /**
- * Timestamps
- *
- * @generated from field: google.protobuf.Timestamp created_at = 4;
- */
- createdAt?: Timestamp;
-
- /**
- * @generated from field: google.protobuf.Timestamp started_at = 5;
- */
- startedAt?: Timestamp;
-
- /**
- * @generated from field: google.protobuf.Timestamp completed_at = 6;
- */
- completedAt?: Timestamp;
-
- /**
- * Results
- *
- * Path to built rootfs
- *
- * @generated from field: string rootfs_path = 7;
- */
- rootfsPath: string;
-
- /**
- * Size of rootfs
- *
- * @generated from field: int64 rootfs_size_bytes = 8;
- */
- rootfsSizeBytes: bigint;
-
- /**
- * SHA256 of rootfs
- *
- * @generated from field: string rootfs_checksum = 9;
- */
- rootfsChecksum: string;
-
- /**
- * Build metadata
- *
- * @generated from field: builderd.v1.ImageMetadata image_metadata = 10;
- */
- imageMetadata?: ImageMetadata;
-
- /**
- * @generated from field: builderd.v1.BuildMetrics metrics = 11;
- */
- metrics?: BuildMetrics;
-
- /**
- * @generated from field: builderd.v1.BuildIsolation isolation = 12;
- */
- isolation?: BuildIsolation;
-
- /**
- * Error information
- *
- * @generated from field: string error_message = 13;
- */
- errorMessage: string;
-
- /**
- * @generated from field: repeated string build_logs = 14;
- */
- buildLogs: string[];
-
- /**
- * Progress information
- *
- * 0-100
- *
- * @generated from field: int32 progress_percent = 15;
- */
- progressPercent: number;
-
- /**
- * Current build step
- *
- * @generated from field: string current_step = 16;
- */
- currentStep: string;
-};
-
-/**
- * Describes the message builderd.v1.BuildJob.
- * Use `create(BuildJobSchema)` to create a new message.
- */
-export const BuildJobSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 20);
-
-/**
- * Build log entry for streaming
- *
- * @generated from message builderd.v1.StreamBuildLogsResponse
- */
-export type StreamBuildLogsResponse = Message<"builderd.v1.StreamBuildLogsResponse"> & {
- /**
- * @generated from field: google.protobuf.Timestamp timestamp = 1;
- */
- timestamp?: Timestamp;
-
- /**
- * "info", "warn", "error", "debug"
- *
- * @generated from field: string level = 2;
- */
- level: string;
-
- /**
- * @generated from field: string message = 3;
- */
- message: string;
-
- /**
- * "puller", "extractor", "builder"
- *
- * @generated from field: string component = 4;
- */
- component: string;
-
- /**
- * @generated from field: map metadata = 5;
- */
- metadata: { [key: string]: string };
-};
-
-/**
- * Describes the message builderd.v1.StreamBuildLogsResponse.
- * Use `create(StreamBuildLogsResponseSchema)` to create a new message.
- */
-export const StreamBuildLogsResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 21);
-
-/**
- * Request/Response messages
- *
- * @generated from message builderd.v1.CreateBuildRequest
- */
-export type CreateBuildRequest = Message<"builderd.v1.CreateBuildRequest"> & {
- /**
- * @generated from field: builderd.v1.BuildConfig config = 1;
- */
- config?: BuildConfig;
-};
-
-/**
- * Describes the message builderd.v1.CreateBuildRequest.
- * Use `create(CreateBuildRequestSchema)` to create a new message.
- */
-export const CreateBuildRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 22);
-
-/**
- * @generated from message builderd.v1.CreateBuildResponse
- */
-export type CreateBuildResponse = Message<"builderd.v1.CreateBuildResponse"> & {
- /**
- * @generated from field: string build_id = 1;
- */
- buildId: string;
-
- /**
- * @generated from field: builderd.v1.BuildState state = 2;
- */
- state: BuildState;
-
- /**
- * @generated from field: google.protobuf.Timestamp created_at = 3;
- */
- createdAt?: Timestamp;
-
- /**
- * Path to the generated rootfs for VM creation
- *
- * @generated from field: string rootfs_path = 4;
- */
- rootfsPath: string;
-};
-
-/**
- * Describes the message builderd.v1.CreateBuildResponse.
- * Use `create(CreateBuildResponseSchema)` to create a new message.
- */
-export const CreateBuildResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 23);
-
-/**
- * @generated from message builderd.v1.GetBuildRequest
- */
-export type GetBuildRequest = Message<"builderd.v1.GetBuildRequest"> & {
- /**
- * @generated from field: string build_id = 1;
- */
- buildId: string;
-
- /**
- * For authorization
- *
- * @generated from field: string tenant_id = 2;
- */
- tenantId: string;
-};
-
-/**
- * Describes the message builderd.v1.GetBuildRequest.
- * Use `create(GetBuildRequestSchema)` to create a new message.
- */
-export const GetBuildRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 24);
-
-/**
- * @generated from message builderd.v1.GetBuildResponse
- */
-export type GetBuildResponse = Message<"builderd.v1.GetBuildResponse"> & {
- /**
- * @generated from field: builderd.v1.BuildJob build = 1;
- */
- build?: BuildJob;
-};
-
-/**
- * Describes the message builderd.v1.GetBuildResponse.
- * Use `create(GetBuildResponseSchema)` to create a new message.
- */
-export const GetBuildResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 25);
-
-/**
- * @generated from message builderd.v1.ListBuildsRequest
- */
-export type ListBuildsRequest = Message<"builderd.v1.ListBuildsRequest"> & {
- /**
- * @generated from field: repeated builderd.v1.BuildState state_filter = 1;
- */
- stateFilter: BuildState[];
-
- /**
- * @generated from field: int32 page_size = 2;
- */
- pageSize: number;
-
- /**
- * @generated from field: string page_token = 3;
- */
- pageToken: string;
-};
-
-/**
- * Describes the message builderd.v1.ListBuildsRequest.
- * Use `create(ListBuildsRequestSchema)` to create a new message.
- */
-export const ListBuildsRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 26);
-
-/**
- * @generated from message builderd.v1.ListBuildsResponse
- */
-export type ListBuildsResponse = Message<"builderd.v1.ListBuildsResponse"> & {
- /**
- * @generated from field: repeated builderd.v1.BuildJob builds = 1;
- */
- builds: BuildJob[];
-
- /**
- * @generated from field: string next_page_token = 2;
- */
- nextPageToken: string;
-
- /**
- * @generated from field: int32 total_count = 3;
- */
- totalCount: number;
-};
-
-/**
- * Describes the message builderd.v1.ListBuildsResponse.
- * Use `create(ListBuildsResponseSchema)` to create a new message.
- */
-export const ListBuildsResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 27);
-
-/**
- * @generated from message builderd.v1.CancelBuildRequest
- */
-export type CancelBuildRequest = Message<"builderd.v1.CancelBuildRequest"> & {
- /**
- * @generated from field: string build_id = 1;
- */
- buildId: string;
-};
-
-/**
- * Describes the message builderd.v1.CancelBuildRequest.
- * Use `create(CancelBuildRequestSchema)` to create a new message.
- */
-export const CancelBuildRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 28);
-
-/**
- * @generated from message builderd.v1.CancelBuildResponse
- */
-export type CancelBuildResponse = Message<"builderd.v1.CancelBuildResponse"> & {
- /**
- * @generated from field: bool success = 1;
- */
- success: boolean;
-
- /**
- * @generated from field: builderd.v1.BuildState state = 2;
- */
- state: BuildState;
-};
-
-/**
- * Describes the message builderd.v1.CancelBuildResponse.
- * Use `create(CancelBuildResponseSchema)` to create a new message.
- */
-export const CancelBuildResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 29);
-
-/**
- * @generated from message builderd.v1.DeleteBuildRequest
- */
-export type DeleteBuildRequest = Message<"builderd.v1.DeleteBuildRequest"> & {
- /**
- * @generated from field: string build_id = 1;
- */
- buildId: string;
-
- /**
- * Delete even if running
- *
- * @generated from field: bool force = 2;
- */
- force: boolean;
-};
-
-/**
- * Describes the message builderd.v1.DeleteBuildRequest.
- * Use `create(DeleteBuildRequestSchema)` to create a new message.
- */
-export const DeleteBuildRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 30);
-
-/**
- * @generated from message builderd.v1.DeleteBuildResponse
- */
-export type DeleteBuildResponse = Message<"builderd.v1.DeleteBuildResponse"> & {
- /**
- * @generated from field: bool success = 1;
- */
- success: boolean;
-};
-
-/**
- * Describes the message builderd.v1.DeleteBuildResponse.
- * Use `create(DeleteBuildResponseSchema)` to create a new message.
- */
-export const DeleteBuildResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 31);
-
-/**
- * @generated from message builderd.v1.StreamBuildLogsRequest
- */
-export type StreamBuildLogsRequest = Message<"builderd.v1.StreamBuildLogsRequest"> & {
- /**
- * @generated from field: string build_id = 1;
- */
- buildId: string;
-
- /**
- * Continue streaming new logs
- *
- * @generated from field: bool follow = 2;
- */
- follow: boolean;
-};
-
-/**
- * Describes the message builderd.v1.StreamBuildLogsRequest.
- * Use `create(StreamBuildLogsRequestSchema)` to create a new message.
- */
-export const StreamBuildLogsRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 32);
-
-/**
- * @generated from message builderd.v1.GetBuildStatsRequest
- */
-export type GetBuildStatsRequest = Message<"builderd.v1.GetBuildStatsRequest"> & {
- /**
- * @generated from field: string tenant_id = 1;
- */
- tenantId: string;
-
- /**
- * @generated from field: google.protobuf.Timestamp start_time = 2;
- */
- startTime?: Timestamp;
-
- /**
- * @generated from field: google.protobuf.Timestamp end_time = 3;
- */
- endTime?: Timestamp;
-};
-
-/**
- * Describes the message builderd.v1.GetBuildStatsRequest.
- * Use `create(GetBuildStatsRequestSchema)` to create a new message.
- */
-export const GetBuildStatsRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 33);
-
-/**
- * @generated from message builderd.v1.GetBuildStatsResponse
- */
-export type GetBuildStatsResponse = Message<"builderd.v1.GetBuildStatsResponse"> & {
- /**
- * @generated from field: int32 total_builds = 1;
- */
- totalBuilds: number;
-
- /**
- * @generated from field: int32 successful_builds = 2;
- */
- successfulBuilds: number;
-
- /**
- * @generated from field: int32 failed_builds = 3;
- */
- failedBuilds: number;
-
- /**
- * @generated from field: int64 avg_build_time_ms = 4;
- */
- avgBuildTimeMs: bigint;
-
- /**
- * @generated from field: int64 total_storage_bytes = 5;
- */
- totalStorageBytes: bigint;
-
- /**
- * @generated from field: int64 total_compute_minutes = 6;
- */
- totalComputeMinutes: bigint;
-
- /**
- * @generated from field: repeated builderd.v1.BuildJob recent_builds = 7;
- */
- recentBuilds: BuildJob[];
-};
-
-/**
- * Describes the message builderd.v1.GetBuildStatsResponse.
- * Use `create(GetBuildStatsResponseSchema)` to create a new message.
- */
-export const GetBuildStatsResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_builderd_v1_builder, 34);
-
-/**
- * Build job lifecycle states
- *
- * @generated from enum builderd.v1.BuildState
- */
-export enum BuildState {
- /**
- * @generated from enum value: BUILD_STATE_UNSPECIFIED = 0;
- */
- UNSPECIFIED = 0,
-
- /**
- * Job queued
- *
- * @generated from enum value: BUILD_STATE_PENDING = 1;
- */
- PENDING = 1,
-
- /**
- * Pulling Docker image or source
- *
- * @generated from enum value: BUILD_STATE_PULLING = 2;
- */
- PULLING = 2,
-
- /**
- * Extracting/preparing source
- *
- * @generated from enum value: BUILD_STATE_EXTRACTING = 3;
- */
- EXTRACTING = 3,
-
- /**
- * Building rootfs
- *
- * @generated from enum value: BUILD_STATE_BUILDING = 4;
- */
- BUILDING = 4,
-
- /**
- * Applying optimizations
- *
- * @generated from enum value: BUILD_STATE_OPTIMIZING = 5;
- */
- OPTIMIZING = 5,
-
- /**
- * Build successful
- *
- * @generated from enum value: BUILD_STATE_COMPLETED = 6;
- */
- COMPLETED = 6,
-
- /**
- * Build failed
- *
- * @generated from enum value: BUILD_STATE_FAILED = 7;
- */
- FAILED = 7,
-
- /**
- * Build cancelled
- *
- * @generated from enum value: BUILD_STATE_CANCELLED = 8;
- */
- CANCELLED = 8,
-
- /**
- * Cleaning up resources
- *
- * @generated from enum value: BUILD_STATE_CLEANING = 9;
- */
- CLEANING = 9,
-}
-
-/**
- * Describes the enum builderd.v1.BuildState.
- */
-export const BuildStateSchema: GenEnum =
- /*@__PURE__*/
- enumDesc(file_builderd_v1_builder, 0);
-
-/**
- * Tenant service tiers
- *
- * @generated from enum builderd.v1.TenantTier
- */
-export enum TenantTier {
- /**
- * @generated from enum value: TENANT_TIER_UNSPECIFIED = 0;
- */
- UNSPECIFIED = 0,
-
- /**
- * Limited resources
- *
- * @generated from enum value: TENANT_TIER_FREE = 1;
- */
- FREE = 1,
-
- /**
- * Standard resources
- *
- * @generated from enum value: TENANT_TIER_PRO = 2;
- */
- PRO = 2,
-
- /**
- * Higher limits + isolation
- *
- * @generated from enum value: TENANT_TIER_ENTERPRISE = 3;
- */
- ENTERPRISE = 3,
-
- /**
- * Dedicated infrastructure
- *
- * @generated from enum value: TENANT_TIER_DEDICATED = 4;
- */
- DEDICATED = 4,
-}
-
-/**
- * Describes the enum builderd.v1.TenantTier.
- */
-export const TenantTierSchema: GenEnum =
- /*@__PURE__*/
- enumDesc(file_builderd_v1_builder, 1);
-
-/**
- * Init process strategies for microVMs
- *
- * @generated from enum builderd.v1.InitStrategy
- */
-export enum InitStrategy {
- /**
- * @generated from enum value: INIT_STRATEGY_UNSPECIFIED = 0;
- */
- UNSPECIFIED = 0,
-
- /**
- * Use tini as init (recommended)
- *
- * @generated from enum value: INIT_STRATEGY_TINI = 1;
- */
- TINI = 1,
-
- /**
- * Direct exec (risky)
- *
- * @generated from enum value: INIT_STRATEGY_DIRECT = 2;
- */
- DIRECT = 2,
-
- /**
- * Custom init script
- *
- * @generated from enum value: INIT_STRATEGY_CUSTOM = 3;
- */
- CUSTOM = 3,
-}
-
-/**
- * Describes the enum builderd.v1.InitStrategy.
- */
-export const InitStrategySchema: GenEnum =
- /*@__PURE__*/
- enumDesc(file_builderd_v1_builder, 2);
-
-/**
- * BuilderService provides multi-tenant build execution for various source types
- *
- * @generated from service builderd.v1.BuilderService
- */
-export const BuilderService: GenService<{
- /**
- * Create a new build job
- *
- * @generated from rpc builderd.v1.BuilderService.CreateBuild
- */
- createBuild: {
- methodKind: "unary";
- input: typeof CreateBuildRequestSchema;
- output: typeof CreateBuildResponseSchema;
- };
- /**
- * Get build status and progress
- *
- * @generated from rpc builderd.v1.BuilderService.GetBuild
- */
- getBuild: {
- methodKind: "unary";
- input: typeof GetBuildRequestSchema;
- output: typeof GetBuildResponseSchema;
- };
- /**
- * List builds with filtering (tenant-scoped)
- *
- * @generated from rpc builderd.v1.BuilderService.ListBuilds
- */
- listBuilds: {
- methodKind: "unary";
- input: typeof ListBuildsRequestSchema;
- output: typeof ListBuildsResponseSchema;
- };
- /**
- * Cancel a running build
- *
- * @generated from rpc builderd.v1.BuilderService.CancelBuild
- */
- cancelBuild: {
- methodKind: "unary";
- input: typeof CancelBuildRequestSchema;
- output: typeof CancelBuildResponseSchema;
- };
- /**
- * Delete a build and its artifacts
- *
- * @generated from rpc builderd.v1.BuilderService.DeleteBuild
- */
- deleteBuild: {
- methodKind: "unary";
- input: typeof DeleteBuildRequestSchema;
- output: typeof DeleteBuildResponseSchema;
- };
- /**
- * Stream build logs in real-time
- *
- * @generated from rpc builderd.v1.BuilderService.StreamBuildLogs
- */
- streamBuildLogs: {
- methodKind: "server_streaming";
- input: typeof StreamBuildLogsRequestSchema;
- output: typeof StreamBuildLogsResponseSchema;
- };
- /**
- * Get build statistics
- *
- * @generated from rpc builderd.v1.BuilderService.GetBuildStats
- */
- getBuildStats: {
- methodKind: "unary";
- input: typeof GetBuildStatsRequestSchema;
- output: typeof GetBuildStatsResponseSchema;
- };
-}> = /*@__PURE__*/ serviceDesc(file_builderd_v1_builder, 0);
diff --git a/internal/proto/generated/krane/v1/deployment_pb.ts b/internal/proto/generated/krane/v1/deployment_pb.ts
new file mode 100644
index 0000000000..20dc39aea7
--- /dev/null
+++ b/internal/proto/generated/krane/v1/deployment_pb.ts
@@ -0,0 +1,310 @@
+// @generated by protoc-gen-es v2.8.0 with parameter "target=ts"
+// @generated from file krane/v1/deployment.proto (package krane.v1, syntax proto3)
+/* eslint-disable */
+
+import type { Message } from "@bufbuild/protobuf";
+import type { GenEnum, GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
+import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
+
+/**
+ * Describes the file krane/v1/deployment.proto.
+ */
+export const file_krane_v1_deployment: GenFile =
+ /*@__PURE__*/
+ fileDesc(
+ "ChlrcmFuZS92MS9kZXBsb3ltZW50LnByb3RvEghrcmFuZS52MSKPAQoRRGVwbG95bWVudFJlcXVlc3QSEQoJbmFtZXNwYWNlGAEgASgJEhUKDWRlcGxveW1lbnRfaWQYAiABKAkSDQoFaW1hZ2UYAyABKAkSEAoIcmVwbGljYXMYBCABKA0SFgoOY3B1X21pbGxpY29yZXMYBSABKA0SFwoPbWVtb3J5X3NpemVfbWliGAYgASgEIkoKF0NyZWF0ZURlcGxveW1lbnRSZXF1ZXN0Ei8KCmRlcGxveW1lbnQYASABKAsyGy5rcmFuZS52MS5EZXBsb3ltZW50UmVxdWVzdCJGChhDcmVhdGVEZXBsb3ltZW50UmVzcG9uc2USKgoGc3RhdHVzGAEgASgOMhoua3JhbmUudjEuRGVwbG95bWVudFN0YXR1cyJKChdVcGRhdGVEZXBsb3ltZW50UmVxdWVzdBIvCgpkZXBsb3ltZW50GAEgASgLMhsua3JhbmUudjEuRGVwbG95bWVudFJlcXVlc3QiKwoYVXBkYXRlRGVwbG95bWVudFJlc3BvbnNlEg8KB3BvZF9pZHMYASADKAkiQwoXRGVsZXRlRGVwbG95bWVudFJlcXVlc3QSEQoJbmFtZXNwYWNlGAEgASgJEhUKDWRlcGxveW1lbnRfaWQYAiABKAkiGgoYRGVsZXRlRGVwbG95bWVudFJlc3BvbnNlIkAKFEdldERlcGxveW1lbnRSZXF1ZXN0EhEKCW5hbWVzcGFjZRgBIAEoCRIVCg1kZXBsb3ltZW50X2lkGAIgASgJIj4KFUdldERlcGxveW1lbnRSZXNwb25zZRIlCglpbnN0YW5jZXMYAiADKAsyEi5rcmFuZS52MS5JbnN0YW5jZSJTCghJbnN0YW5jZRIKCgJpZBgBIAEoCRIPCgdhZGRyZXNzGAIgASgJEioKBnN0YXR1cxgDIAEoDjIaLmtyYW5lLnYxLkRlcGxveW1lbnRTdGF0dXMqlgEKEERlcGxveW1lbnRTdGF0dXMSIQodREVQTE9ZTUVOVF9TVEFUVVNfVU5TUEVDSUZJRUQQABIdChlERVBMT1lNRU5UX1NUQVRVU19QRU5ESU5HEAESHQoZREVQTE9ZTUVOVF9TVEFUVVNfUlVOTklORxACEiEKHURFUExPWU1FTlRfU1RBVFVTX1RFUk1JTkFUSU5HEAMymwIKEURlcGxveW1lbnRTZXJ2aWNlElkKEENyZWF0ZURlcGxveW1lbnQSIS5rcmFuZS52MS5DcmVhdGVEZXBsb3ltZW50UmVxdWVzdBoiLmtyYW5lLnYxLkNyZWF0ZURlcGxveW1lbnRSZXNwb25zZRJQCg1HZXREZXBsb3ltZW50Eh4ua3JhbmUudjEuR2V0RGVwbG95bWVudFJlcXVlc3QaHy5rcmFuZS52MS5HZXREZXBsb3ltZW50UmVzcG9uc2USWQoQRGVsZXRlRGVwbG95bWVudBIhLmtyYW5lLnYxLkRlbGV0ZURlcGxveW1lbnRSZXF1ZXN0GiIua3JhbmUudjEuRGVsZXRlRGVwbG95bWVudFJlc3BvbnNlQjhaNmdpdGh1Yi5jb20vdW5rZXllZC91bmtleS9nby9nZW4vcHJvdG8va3JhbmUvdjE7a3JhbmV2MWIGcHJvdG8z",
+ );
+
+/**
+ * @generated from message krane.v1.DeploymentRequest
+ */
+export type DeploymentRequest = Message<"krane.v1.DeploymentRequest"> & {
+ /**
+ * @generated from field: string namespace = 1;
+ */
+ namespace: string;
+
+ /**
+ * @generated from field: string deployment_id = 2;
+ */
+ deploymentId: string;
+
+ /**
+ * @generated from field: string image = 3;
+ */
+ image: string;
+
+ /**
+ * @generated from field: uint32 replicas = 4;
+ */
+ replicas: number;
+
+ /**
+ * @generated from field: uint32 cpu_millicores = 5;
+ */
+ cpuMillicores: number;
+
+ /**
+ * @generated from field: uint64 memory_size_mib = 6;
+ */
+ memorySizeMib: bigint;
+};
+
+/**
+ * Describes the message krane.v1.DeploymentRequest.
+ * Use `create(DeploymentRequestSchema)` to create a new message.
+ */
+export const DeploymentRequestSchema: GenMessage =
+ /*@__PURE__*/
+ messageDesc(file_krane_v1_deployment, 0);
+
+/**
+ * @generated from message krane.v1.CreateDeploymentRequest
+ */
+export type CreateDeploymentRequest = Message<"krane.v1.CreateDeploymentRequest"> & {
+ /**
+ * @generated from field: krane.v1.DeploymentRequest deployment = 1;
+ */
+ deployment?: DeploymentRequest;
+};
+
+/**
+ * Describes the message krane.v1.CreateDeploymentRequest.
+ * Use `create(CreateDeploymentRequestSchema)` to create a new message.
+ */
+export const CreateDeploymentRequestSchema: GenMessage =
+ /*@__PURE__*/
+ messageDesc(file_krane_v1_deployment, 1);
+
+/**
+ * @generated from message krane.v1.CreateDeploymentResponse
+ */
+export type CreateDeploymentResponse = Message<"krane.v1.CreateDeploymentResponse"> & {
+ /**
+ * @generated from field: krane.v1.DeploymentStatus status = 1;
+ */
+ status: DeploymentStatus;
+};
+
+/**
+ * Describes the message krane.v1.CreateDeploymentResponse.
+ * Use `create(CreateDeploymentResponseSchema)` to create a new message.
+ */
+export const CreateDeploymentResponseSchema: GenMessage =
+ /*@__PURE__*/
+ messageDesc(file_krane_v1_deployment, 2);
+
+/**
+ * @generated from message krane.v1.UpdateDeploymentRequest
+ */
+export type UpdateDeploymentRequest = Message<"krane.v1.UpdateDeploymentRequest"> & {
+ /**
+ * @generated from field: krane.v1.DeploymentRequest deployment = 1;
+ */
+ deployment?: DeploymentRequest;
+};
+
+/**
+ * Describes the message krane.v1.UpdateDeploymentRequest.
+ * Use `create(UpdateDeploymentRequestSchema)` to create a new message.
+ */
+export const UpdateDeploymentRequestSchema: GenMessage =
+ /*@__PURE__*/
+ messageDesc(file_krane_v1_deployment, 3);
+
+/**
+ * @generated from message krane.v1.UpdateDeploymentResponse
+ */
+export type UpdateDeploymentResponse = Message<"krane.v1.UpdateDeploymentResponse"> & {
+ /**
+ * @generated from field: repeated string pod_ids = 1;
+ */
+ podIds: string[];
+};
+
+/**
+ * Describes the message krane.v1.UpdateDeploymentResponse.
+ * Use `create(UpdateDeploymentResponseSchema)` to create a new message.
+ */
+export const UpdateDeploymentResponseSchema: GenMessage =
+ /*@__PURE__*/
+ messageDesc(file_krane_v1_deployment, 4);
+
+/**
+ * @generated from message krane.v1.DeleteDeploymentRequest
+ */
+export type DeleteDeploymentRequest = Message<"krane.v1.DeleteDeploymentRequest"> & {
+ /**
+ * @generated from field: string namespace = 1;
+ */
+ namespace: string;
+
+ /**
+ * @generated from field: string deployment_id = 2;
+ */
+ deploymentId: string;
+};
+
+/**
+ * Describes the message krane.v1.DeleteDeploymentRequest.
+ * Use `create(DeleteDeploymentRequestSchema)` to create a new message.
+ */
+export const DeleteDeploymentRequestSchema: GenMessage =
+ /*@__PURE__*/
+ messageDesc(file_krane_v1_deployment, 5);
+
+/**
+ * @generated from message krane.v1.DeleteDeploymentResponse
+ */
+export type DeleteDeploymentResponse = Message<"krane.v1.DeleteDeploymentResponse"> & {};
+
+/**
+ * Describes the message krane.v1.DeleteDeploymentResponse.
+ * Use `create(DeleteDeploymentResponseSchema)` to create a new message.
+ */
+export const DeleteDeploymentResponseSchema: GenMessage =
+ /*@__PURE__*/
+ messageDesc(file_krane_v1_deployment, 6);
+
+/**
+ * @generated from message krane.v1.GetDeploymentRequest
+ */
+export type GetDeploymentRequest = Message<"krane.v1.GetDeploymentRequest"> & {
+ /**
+ * @generated from field: string namespace = 1;
+ */
+ namespace: string;
+
+ /**
+ * @generated from field: string deployment_id = 2;
+ */
+ deploymentId: string;
+};
+
+/**
+ * Describes the message krane.v1.GetDeploymentRequest.
+ * Use `create(GetDeploymentRequestSchema)` to create a new message.
+ */
+export const GetDeploymentRequestSchema: GenMessage =
+ /*@__PURE__*/
+ messageDesc(file_krane_v1_deployment, 7);
+
+/**
+ * @generated from message krane.v1.GetDeploymentResponse
+ */
+export type GetDeploymentResponse = Message<"krane.v1.GetDeploymentResponse"> & {
+ /**
+ * @generated from field: repeated krane.v1.Instance instances = 2;
+ */
+ instances: Instance[];
+};
+
+/**
+ * Describes the message krane.v1.GetDeploymentResponse.
+ * Use `create(GetDeploymentResponseSchema)` to create a new message.
+ */
+export const GetDeploymentResponseSchema: GenMessage =
+ /*@__PURE__*/
+ messageDesc(file_krane_v1_deployment, 8);
+
+/**
+ * @generated from message krane.v1.Instance
+ */
+export type Instance = Message<"krane.v1.Instance"> & {
+ /**
+ * @generated from field: string id = 1;
+ */
+ id: string;
+
+ /**
+ * @generated from field: string address = 2;
+ */
+ address: string;
+
+ /**
+ * @generated from field: krane.v1.DeploymentStatus status = 3;
+ */
+ status: DeploymentStatus;
+};
+
+/**
+ * Describes the message krane.v1.Instance.
+ * Use `create(InstanceSchema)` to create a new message.
+ */
+export const InstanceSchema: GenMessage =
+ /*@__PURE__*/
+ messageDesc(file_krane_v1_deployment, 9);
+
+/**
+ * @generated from enum krane.v1.DeploymentStatus
+ */
+export enum DeploymentStatus {
+ /**
+ * @generated from enum value: DEPLOYMENT_STATUS_UNSPECIFIED = 0;
+ */
+ UNSPECIFIED = 0,
+
+ /**
+ * Deployment request accepted, container/pod creation in progress
+ *
+ * @generated from enum value: DEPLOYMENT_STATUS_PENDING = 1;
+ */
+ PENDING = 1,
+
+ /**
+ * Container/pod is running and healthy
+ *
+ * @generated from enum value: DEPLOYMENT_STATUS_RUNNING = 2;
+ */
+ RUNNING = 2,
+
+ /**
+ * Container/pod is being terminated
+ *
+ * @generated from enum value: DEPLOYMENT_STATUS_TERMINATING = 3;
+ */
+ TERMINATING = 3,
+}
+
+/**
+ * Describes the enum krane.v1.DeploymentStatus.
+ */
+export const DeploymentStatusSchema: GenEnum =
+ /*@__PURE__*/
+ enumDesc(file_krane_v1_deployment, 0);
+
+/**
+ * @generated from service krane.v1.DeploymentService
+ */
+export const DeploymentService: GenService<{
+ /**
+ * CreateDeployment
+ *
+ * @generated from rpc krane.v1.DeploymentService.CreateDeployment
+ */
+ createDeployment: {
+ methodKind: "unary";
+ input: typeof CreateDeploymentRequestSchema;
+ output: typeof CreateDeploymentResponseSchema;
+ };
+ /**
+ * GetDeployment
+ *
+ * @generated from rpc krane.v1.DeploymentService.GetDeployment
+ */
+ getDeployment: {
+ methodKind: "unary";
+ input: typeof GetDeploymentRequestSchema;
+ output: typeof GetDeploymentResponseSchema;
+ };
+ /**
+ * DeleteDeployment
+ *
+ * @generated from rpc krane.v1.DeploymentService.DeleteDeployment
+ */
+ deleteDeployment: {
+ methodKind: "unary";
+ input: typeof DeleteDeploymentRequestSchema;
+ output: typeof DeleteDeploymentResponseSchema;
+ };
+}> = /*@__PURE__*/ serviceDesc(file_krane_v1_deployment, 0);
diff --git a/internal/proto/generated/metald/v1/deployment_pb.ts b/internal/proto/generated/metald/v1/deployment_pb.ts
deleted file mode 100644
index 9f5fa8cac0..0000000000
--- a/internal/proto/generated/metald/v1/deployment_pb.ts
+++ /dev/null
@@ -1,234 +0,0 @@
-// @generated by protoc-gen-es v2.8.0 with parameter "target=ts"
-// @generated from file metald/v1/deployment.proto (package metald.v1, syntax proto3)
-/* eslint-disable */
-
-import type { Message } from "@bufbuild/protobuf";
-import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv2";
-import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv2";
-import type { VmState } from "./vm_pb";
-import { file_metald_v1_vm } from "./vm_pb";
-
-/**
- * Describes the file metald/v1/deployment.proto.
- */
-export const file_metald_v1_deployment: GenFile =
- /*@__PURE__*/
- fileDesc(
- "ChptZXRhbGQvdjEvZGVwbG95bWVudC5wcm90bxIJbWV0YWxkLnYxInEKEURlcGxveW1lbnRSZXF1ZXN0EhUKDWRlcGxveW1lbnRfaWQYASABKAkSDQoFaW1hZ2UYAiABKAkSEAoIdm1fY291bnQYAyABKA0SCwoDY3B1GAQgASgNEhcKD21lbW9yeV9zaXplX21pYhgFIAEoBCJLChdDcmVhdGVEZXBsb3ltZW50UmVxdWVzdBIwCgpkZXBsb3ltZW50GAEgASgLMhwubWV0YWxkLnYxLkRlcGxveW1lbnRSZXF1ZXN0IioKGENyZWF0ZURlcGxveW1lbnRSZXNwb25zZRIOCgZ2bV9pZHMYASADKAkiSwoXVXBkYXRlRGVwbG95bWVudFJlcXVlc3QSMAoKZGVwbG95bWVudBgBIAEoCzIcLm1ldGFsZC52MS5EZXBsb3ltZW50UmVxdWVzdCIqChhVcGRhdGVEZXBsb3ltZW50UmVzcG9uc2USDgoGdm1faWRzGAEgAygJIjAKF0RlbGV0ZURlcGxveW1lbnRSZXF1ZXN0EhUKDWRlcGxveW1lbnRfaWQYASABKAkiGgoYRGVsZXRlRGVwbG95bWVudFJlc3BvbnNlIi0KFEdldERlcGxveW1lbnRSZXF1ZXN0EhUKDWRlcGxveW1lbnRfaWQYASABKAkisQEKFUdldERlcGxveW1lbnRSZXNwb25zZRIVCg1kZXBsb3ltZW50X2lkGAEgASgJEjAKA3ZtcxgCIAMoCzIjLm1ldGFsZC52MS5HZXREZXBsb3ltZW50UmVzcG9uc2UuVm0aTwoCVm0SCgoCaWQYASABKAkSDAoEaG9zdBgCIAEoCRIhCgVzdGF0ZRgDIAEoDjISLm1ldGFsZC52MS5WbVN0YXRlEgwKBHBvcnQYBCABKA1COlo4Z2l0aHViLmNvbS91bmtleWVkL3Vua2V5L2dvL2dlbi9wcm90by9tZXRhbGQvdjE7bWV0YWxkdjFiBnByb3RvMw",
- [file_metald_v1_vm],
- );
-
-/**
- * @generated from message metald.v1.DeploymentRequest
- */
-export type DeploymentRequest = Message<"metald.v1.DeploymentRequest"> & {
- /**
- * @generated from field: string deployment_id = 1;
- */
- deploymentId: string;
-
- /**
- * @generated from field: string image = 2;
- */
- image: string;
-
- /**
- * @generated from field: uint32 vm_count = 3;
- */
- vmCount: number;
-
- /**
- * @generated from field: uint32 cpu = 4;
- */
- cpu: number;
-
- /**
- * @generated from field: uint64 memory_size_mib = 5;
- */
- memorySizeMib: bigint;
-};
-
-/**
- * Describes the message metald.v1.DeploymentRequest.
- * Use `create(DeploymentRequestSchema)` to create a new message.
- */
-export const DeploymentRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_deployment, 0);
-
-/**
- * @generated from message metald.v1.CreateDeploymentRequest
- */
-export type CreateDeploymentRequest = Message<"metald.v1.CreateDeploymentRequest"> & {
- /**
- * @generated from field: metald.v1.DeploymentRequest deployment = 1;
- */
- deployment?: DeploymentRequest;
-};
-
-/**
- * Describes the message metald.v1.CreateDeploymentRequest.
- * Use `create(CreateDeploymentRequestSchema)` to create a new message.
- */
-export const CreateDeploymentRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_deployment, 1);
-
-/**
- * @generated from message metald.v1.CreateDeploymentResponse
- */
-export type CreateDeploymentResponse = Message<"metald.v1.CreateDeploymentResponse"> & {
- /**
- * @generated from field: repeated string vm_ids = 1;
- */
- vmIds: string[];
-};
-
-/**
- * Describes the message metald.v1.CreateDeploymentResponse.
- * Use `create(CreateDeploymentResponseSchema)` to create a new message.
- */
-export const CreateDeploymentResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_deployment, 2);
-
-/**
- * @generated from message metald.v1.UpdateDeploymentRequest
- */
-export type UpdateDeploymentRequest = Message<"metald.v1.UpdateDeploymentRequest"> & {
- /**
- * @generated from field: metald.v1.DeploymentRequest deployment = 1;
- */
- deployment?: DeploymentRequest;
-};
-
-/**
- * Describes the message metald.v1.UpdateDeploymentRequest.
- * Use `create(UpdateDeploymentRequestSchema)` to create a new message.
- */
-export const UpdateDeploymentRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_deployment, 3);
-
-/**
- * @generated from message metald.v1.UpdateDeploymentResponse
- */
-export type UpdateDeploymentResponse = Message<"metald.v1.UpdateDeploymentResponse"> & {
- /**
- * @generated from field: repeated string vm_ids = 1;
- */
- vmIds: string[];
-};
-
-/**
- * Describes the message metald.v1.UpdateDeploymentResponse.
- * Use `create(UpdateDeploymentResponseSchema)` to create a new message.
- */
-export const UpdateDeploymentResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_deployment, 4);
-
-/**
- * @generated from message metald.v1.DeleteDeploymentRequest
- */
-export type DeleteDeploymentRequest = Message<"metald.v1.DeleteDeploymentRequest"> & {
- /**
- * @generated from field: string deployment_id = 1;
- */
- deploymentId: string;
-};
-
-/**
- * Describes the message metald.v1.DeleteDeploymentRequest.
- * Use `create(DeleteDeploymentRequestSchema)` to create a new message.
- */
-export const DeleteDeploymentRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_deployment, 5);
-
-/**
- * @generated from message metald.v1.DeleteDeploymentResponse
- */
-export type DeleteDeploymentResponse = Message<"metald.v1.DeleteDeploymentResponse"> & {};
-
-/**
- * Describes the message metald.v1.DeleteDeploymentResponse.
- * Use `create(DeleteDeploymentResponseSchema)` to create a new message.
- */
-export const DeleteDeploymentResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_deployment, 6);
-
-/**
- * @generated from message metald.v1.GetDeploymentRequest
- */
-export type GetDeploymentRequest = Message<"metald.v1.GetDeploymentRequest"> & {
- /**
- * @generated from field: string deployment_id = 1;
- */
- deploymentId: string;
-};
-
-/**
- * Describes the message metald.v1.GetDeploymentRequest.
- * Use `create(GetDeploymentRequestSchema)` to create a new message.
- */
-export const GetDeploymentRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_deployment, 7);
-
-/**
- * @generated from message metald.v1.GetDeploymentResponse
- */
-export type GetDeploymentResponse = Message<"metald.v1.GetDeploymentResponse"> & {
- /**
- * @generated from field: string deployment_id = 1;
- */
- deploymentId: string;
-
- /**
- * @generated from field: repeated metald.v1.GetDeploymentResponse.Vm vms = 2;
- */
- vms: GetDeploymentResponse_Vm[];
-};
-
-/**
- * Describes the message metald.v1.GetDeploymentResponse.
- * Use `create(GetDeploymentResponseSchema)` to create a new message.
- */
-export const GetDeploymentResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_deployment, 8);
-
-/**
- * @generated from message metald.v1.GetDeploymentResponse.Vm
- */
-export type GetDeploymentResponse_Vm = Message<"metald.v1.GetDeploymentResponse.Vm"> & {
- /**
- * @generated from field: string id = 1;
- */
- id: string;
-
- /**
- * @generated from field: string host = 2;
- */
- host: string;
-
- /**
- * @generated from field: metald.v1.VmState state = 3;
- */
- state: VmState;
-
- /**
- * @generated from field: uint32 port = 4;
- */
- port: number;
-};
-
-/**
- * Describes the message metald.v1.GetDeploymentResponse.Vm.
- * Use `create(GetDeploymentResponse_VmSchema)` to create a new message.
- */
-export const GetDeploymentResponse_VmSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_deployment, 8, 0);
diff --git a/internal/proto/generated/metald/v1/metald_pb.ts b/internal/proto/generated/metald/v1/metald_pb.ts
deleted file mode 100644
index 5eb52f9b10..0000000000
--- a/internal/proto/generated/metald/v1/metald_pb.ts
+++ /dev/null
@@ -1,191 +0,0 @@
-// @generated by protoc-gen-es v2.8.0 with parameter "target=ts"
-// @generated from file metald/v1/metald.proto (package metald.v1, syntax proto3)
-/* eslint-disable */
-
-import type { GenFile, GenService } from "@bufbuild/protobuf/codegenv2";
-import { fileDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
-import type {
- CreateDeploymentRequestSchema,
- CreateDeploymentResponseSchema,
- DeleteDeploymentRequestSchema,
- DeleteDeploymentResponseSchema,
- GetDeploymentRequestSchema,
- GetDeploymentResponseSchema,
- UpdateDeploymentRequestSchema,
- UpdateDeploymentResponseSchema,
-} from "./deployment_pb";
-import { file_metald_v1_deployment } from "./deployment_pb";
-import type {
- BootVmRequestSchema,
- BootVmResponseSchema,
- CreateVmRequestSchema,
- CreateVmResponseSchema,
- DeleteVmRequestSchema,
- DeleteVmResponseSchema,
- GetVmInfoRequestSchema,
- GetVmInfoResponseSchema,
- ListVmsRequestSchema,
- ListVmsResponseSchema,
- PauseVmRequestSchema,
- PauseVmResponseSchema,
- RebootVmRequestSchema,
- RebootVmResponseSchema,
- ResumeVmRequestSchema,
- ResumeVmResponseSchema,
- ShutdownVmRequestSchema,
- ShutdownVmResponseSchema,
-} from "./vm_pb";
-import { file_metald_v1_vm } from "./vm_pb";
-
-/**
- * Describes the file metald/v1/metald.proto.
- */
-export const file_metald_v1_metald: GenFile =
- /*@__PURE__*/
- fileDesc(
- "ChZtZXRhbGQvdjEvbWV0YWxkLnByb3RvEgltZXRhbGQudjEy4AcKCVZtU2VydmljZRJbChBDcmVhdGVEZXBsb3ltZW50EiIubWV0YWxkLnYxLkNyZWF0ZURlcGxveW1lbnRSZXF1ZXN0GiMubWV0YWxkLnYxLkNyZWF0ZURlcGxveW1lbnRSZXNwb25zZRJbChBVcGRhdGVEZXBsb3ltZW50EiIubWV0YWxkLnYxLlVwZGF0ZURlcGxveW1lbnRSZXF1ZXN0GiMubWV0YWxkLnYxLlVwZGF0ZURlcGxveW1lbnRSZXNwb25zZRJbChBEZWxldGVEZXBsb3ltZW50EiIubWV0YWxkLnYxLkRlbGV0ZURlcGxveW1lbnRSZXF1ZXN0GiMubWV0YWxkLnYxLkRlbGV0ZURlcGxveW1lbnRSZXNwb25zZRJSCg1HZXREZXBsb3ltZW50Eh8ubWV0YWxkLnYxLkdldERlcGxveW1lbnRSZXF1ZXN0GiAubWV0YWxkLnYxLkdldERlcGxveW1lbnRSZXNwb25zZRJDCghDcmVhdGVWbRIaLm1ldGFsZC52MS5DcmVhdGVWbVJlcXVlc3QaGy5tZXRhbGQudjEuQ3JlYXRlVm1SZXNwb25zZRJDCghEZWxldGVWbRIaLm1ldGFsZC52MS5EZWxldGVWbVJlcXVlc3QaGy5tZXRhbGQudjEuRGVsZXRlVm1SZXNwb25zZRI9CgZCb290Vm0SGC5tZXRhbGQudjEuQm9vdFZtUmVxdWVzdBoZLm1ldGFsZC52MS5Cb290Vm1SZXNwb25zZRJJCgpTaHV0ZG93blZtEhwubWV0YWxkLnYxLlNodXRkb3duVm1SZXF1ZXN0Gh0ubWV0YWxkLnYxLlNodXRkb3duVm1SZXNwb25zZRJACgdQYXVzZVZtEhkubWV0YWxkLnYxLlBhdXNlVm1SZXF1ZXN0GhoubWV0YWxkLnYxLlBhdXNlVm1SZXNwb25zZRJDCghSZXN1bWVWbRIaLm1ldGFsZC52MS5SZXN1bWVWbVJlcXVlc3QaGy5tZXRhbGQudjEuUmVzdW1lVm1SZXNwb25zZRJDCghSZWJvb3RWbRIaLm1ldGFsZC52MS5SZWJvb3RWbVJlcXVlc3QaGy5tZXRhbGQudjEuUmVib290Vm1SZXNwb25zZRJGCglHZXRWbUluZm8SGy5tZXRhbGQudjEuR2V0Vm1JbmZvUmVxdWVzdBocLm1ldGFsZC52MS5HZXRWbUluZm9SZXNwb25zZRJACgdMaXN0Vm1zEhkubWV0YWxkLnYxLkxpc3RWbXNSZXF1ZXN0GhoubWV0YWxkLnYxLkxpc3RWbXNSZXNwb25zZUI6WjhnaXRodWIuY29tL3Vua2V5ZWQvdW5rZXkvZ28vZ2VuL3Byb3RvL21ldGFsZC92MTttZXRhbGR2MWIGcHJvdG8z",
- [file_metald_v1_deployment, file_metald_v1_vm],
- );
-
-/**
- * VmService provides unified VM management across different hypervisor backends
- *
- *
- * Deployment related RPCs
- * These endpoints are most used by the control-plane side of things
- *
- *
- * @generated from service metald.v1.VmService
- */
-export const VmService: GenService<{
- /**
- * CreateDeployment
- *
- * @generated from rpc metald.v1.VmService.CreateDeployment
- */
- createDeployment: {
- methodKind: "unary";
- input: typeof CreateDeploymentRequestSchema;
- output: typeof CreateDeploymentResponseSchema;
- };
- /**
- * UpdateDeployment
- *
- * @generated from rpc metald.v1.VmService.UpdateDeployment
- */
- updateDeployment: {
- methodKind: "unary";
- input: typeof UpdateDeploymentRequestSchema;
- output: typeof UpdateDeploymentResponseSchema;
- };
- /**
- * DeleteDeployment
- *
- * @generated from rpc metald.v1.VmService.DeleteDeployment
- */
- deleteDeployment: {
- methodKind: "unary";
- input: typeof DeleteDeploymentRequestSchema;
- output: typeof DeleteDeploymentResponseSchema;
- };
- /**
- * GetDeployment
- *
- * @generated from rpc metald.v1.VmService.GetDeployment
- */
- getDeployment: {
- methodKind: "unary";
- input: typeof GetDeploymentRequestSchema;
- output: typeof GetDeploymentResponseSchema;
- };
- /**
- * CreateVm creates a new virtual machine
- *
- * @generated from rpc metald.v1.VmService.CreateVm
- */
- createVm: {
- methodKind: "unary";
- input: typeof CreateVmRequestSchema;
- output: typeof CreateVmResponseSchema;
- };
- /**
- * DeleteVm removes a virtual machine
- *
- * @generated from rpc metald.v1.VmService.DeleteVm
- */
- deleteVm: {
- methodKind: "unary";
- input: typeof DeleteVmRequestSchema;
- output: typeof DeleteVmResponseSchema;
- };
- /**
- * BootVm starts a created virtual machine
- *
- * @generated from rpc metald.v1.VmService.BootVm
- */
- bootVm: {
- methodKind: "unary";
- input: typeof BootVmRequestSchema;
- output: typeof BootVmResponseSchema;
- };
- /**
- * ShutdownVm gracefully stops a running virtual machine
- *
- * @generated from rpc metald.v1.VmService.ShutdownVm
- */
- shutdownVm: {
- methodKind: "unary";
- input: typeof ShutdownVmRequestSchema;
- output: typeof ShutdownVmResponseSchema;
- };
- /**
- * PauseVm pauses a running virtual machine
- *
- * @generated from rpc metald.v1.VmService.PauseVm
- */
- pauseVm: {
- methodKind: "unary";
- input: typeof PauseVmRequestSchema;
- output: typeof PauseVmResponseSchema;
- };
- /**
- * ResumeVm resumes a paused virtual machine
- *
- * @generated from rpc metald.v1.VmService.ResumeVm
- */
- resumeVm: {
- methodKind: "unary";
- input: typeof ResumeVmRequestSchema;
- output: typeof ResumeVmResponseSchema;
- };
- /**
- * RebootVm restarts a running virtual machine
- *
- * @generated from rpc metald.v1.VmService.RebootVm
- */
- rebootVm: {
- methodKind: "unary";
- input: typeof RebootVmRequestSchema;
- output: typeof RebootVmResponseSchema;
- };
- /**
- * GetVmInfo retrieves virtual machine status and configuration
- *
- * @generated from rpc metald.v1.VmService.GetVmInfo
- */
- getVmInfo: {
- methodKind: "unary";
- input: typeof GetVmInfoRequestSchema;
- output: typeof GetVmInfoResponseSchema;
- };
- /**
- * ListVms lists all virtual machines managed by this service
- *
- * @generated from rpc metald.v1.VmService.ListVms
- */
- listVms: {
- methodKind: "unary";
- input: typeof ListVmsRequestSchema;
- output: typeof ListVmsResponseSchema;
- };
-}> = /*@__PURE__*/ serviceDesc(file_metald_v1_metald, 0);
diff --git a/internal/proto/generated/metald/v1/network_pb.ts b/internal/proto/generated/metald/v1/network_pb.ts
deleted file mode 100644
index 610a34263d..0000000000
--- a/internal/proto/generated/metald/v1/network_pb.ts
+++ /dev/null
@@ -1,324 +0,0 @@
-// @generated by protoc-gen-es v2.8.0 with parameter "target=ts"
-// @generated from file metald/v1/network.proto (package metald.v1, syntax proto3)
-/* eslint-disable */
-
-import type { Message } from "@bufbuild/protobuf";
-import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv2";
-import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv2";
-
-/**
- * Describes the file metald/v1/network.proto.
- */
-export const file_metald_v1_network: GenFile =
- /*@__PURE__*/
- fileDesc(
- "ChdtZXRhbGQvdjEvbmV0d29yay5wcm90bxIJbWV0YWxkLnYxIngKDE5ldHdvcmtTdGF0cxIWCg5ieXRlc19yZWNlaXZlZBgBIAEoAxIZChFieXRlc190cmFuc21pdHRlZBgCIAEoAxIYChBwYWNrZXRzX3JlY2VpdmVkGAMgASgDEhsKE3BhY2tldHNfdHJhbnNtaXR0ZWQYBCABKAMiogMKEE5ldHdvcmtJbnRlcmZhY2USCgoCaWQYASABKAkSEwoLbWFjX2FkZHJlc3MYAiABKAkSEgoKdGFwX2RldmljZRgDIAEoCRIWCg5pbnRlcmZhY2VfdHlwZRgEIAEoCRI5CgdvcHRpb25zGAUgAygLMigubWV0YWxkLnYxLk5ldHdvcmtJbnRlcmZhY2UuT3B0aW9uc0VudHJ5EioKC2lwdjRfY29uZmlnGAYgASgLMhUubWV0YWxkLnYxLklQdjRDb25maWcSKgoLaXB2Nl9jb25maWcYByABKAsyFS5tZXRhbGQudjEuSVB2NkNvbmZpZxIkCgRtb2RlGAggASgOMhYubWV0YWxkLnYxLk5ldHdvcmtNb2RlEisKDXJ4X3JhdGVfbGltaXQYCiABKAsyFC5tZXRhbGQudjEuUmF0ZUxpbWl0EisKDXR4X3JhdGVfbGltaXQYCyABKAsyFC5tZXRhbGQudjEuUmF0ZUxpbWl0Gi4KDE9wdGlvbnNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBImIKCklQdjRDb25maWcSDwoHYWRkcmVzcxgBIAEoCRIPCgduZXRtYXNrGAIgASgJEg8KB2dhdGV3YXkYAyABKAkSEwoLZG5zX3NlcnZlcnMYBCADKAkSDAoEZGhjcBgFIAEoCCKZAQoKSVB2NkNvbmZpZxIPCgdhZGRyZXNzGAEgASgJEhUKDXByZWZpeF9sZW5ndGgYAiABKAUSDwoHZ2F0ZXdheRgDIAEoCRITCgtkbnNfc2VydmVycxgEIAMoCRINCgVzbGFhYxgFIAEoCBIaChJwcml2YWN5X2V4dGVuc2lvbnMYBiABKAgSEgoKbGlua19sb2NhbBgHIAEoCSJCCglSYXRlTGltaXQSEQoJYmFuZHdpZHRoGAEgASgDEhMKC3JlZmlsbF90aW1lGAIgASgDEg0KBWJ1cnN0GAMgASgDKoABCgtOZXR3b3JrTW9kZRIcChhORVRXT1JLX01PREVfVU5TUEVDSUZJRUQQABIbChdORVRXT1JLX01PREVfRFVBTF9TVEFDSxABEhoKFk5FVFdPUktfTU9ERV9JUFY0X09OTFkQAhIaChZORVRXT1JLX01PREVfSVBWNl9PTkxZEANCOlo4Z2l0aHViLmNvbS91bmtleWVkL3Vua2V5L2dvL2dlbi9wcm90by9tZXRhbGQvdjE7bWV0YWxkdjFiBnByb3RvMw",
- );
-
-/**
- * @generated from message metald.v1.NetworkStats
- */
-export type NetworkStats = Message<"metald.v1.NetworkStats"> & {
- /**
- * @generated from field: int64 bytes_received = 1;
- */
- bytesReceived: bigint;
-
- /**
- * @generated from field: int64 bytes_transmitted = 2;
- */
- bytesTransmitted: bigint;
-
- /**
- * @generated from field: int64 packets_received = 3;
- */
- packetsReceived: bigint;
-
- /**
- * @generated from field: int64 packets_transmitted = 4;
- */
- packetsTransmitted: bigint;
-};
-
-/**
- * Describes the message metald.v1.NetworkStats.
- * Use `create(NetworkStatsSchema)` to create a new message.
- */
-export const NetworkStatsSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_network, 0);
-
-/**
- * @generated from message metald.v1.NetworkInterface
- */
-export type NetworkInterface = Message<"metald.v1.NetworkInterface"> & {
- /**
- * Unique identifier for this network interface
- *
- * @generated from field: string id = 1;
- */
- id: string;
-
- /**
- * MAC address (optional, will be generated if not provided)
- *
- * @generated from field: string mac_address = 2;
- */
- macAddress: string;
-
- /**
- * Host-side TAP device name
- *
- * @generated from field: string tap_device = 3;
- */
- tapDevice: string;
-
- /**
- * Network interface type (virtio-net, e1000, etc.)
- *
- * @generated from field: string interface_type = 4;
- */
- interfaceType: string;
-
- /**
- * Additional network options
- *
- * @generated from field: map options = 5;
- */
- options: { [key: string]: string };
-
- /**
- * IPv4 configuration (optional)
- *
- * @generated from field: metald.v1.IPv4Config ipv4_config = 6;
- */
- ipv4Config?: IPv4Config;
-
- /**
- * IPv6 configuration (optional)
- *
- * @generated from field: metald.v1.IPv6Config ipv6_config = 7;
- */
- ipv6Config?: IPv6Config;
-
- /**
- * Network mode
- *
- * @generated from field: metald.v1.NetworkMode mode = 8;
- */
- mode: NetworkMode;
-
- /**
- * Rate limiting
- *
- * Receive rate limit
- *
- * @generated from field: metald.v1.RateLimit rx_rate_limit = 10;
- */
- rxRateLimit?: RateLimit;
-
- /**
- * Transmit rate limit
- *
- * @generated from field: metald.v1.RateLimit tx_rate_limit = 11;
- */
- txRateLimit?: RateLimit;
-};
-
-/**
- * Describes the message metald.v1.NetworkInterface.
- * Use `create(NetworkInterfaceSchema)` to create a new message.
- */
-export const NetworkInterfaceSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_network, 1);
-
-/**
- * IPv4 network configuration
- *
- * @generated from message metald.v1.IPv4Config
- */
-export type IPv4Config = Message<"metald.v1.IPv4Config"> & {
- /**
- * IPv4 address (e.g., "10.100.1.2")
- *
- * @generated from field: string address = 1;
- */
- address: string;
-
- /**
- * Network mask (e.g., "255.255.255.0")
- *
- * @generated from field: string netmask = 2;
- */
- netmask: string;
-
- /**
- * Default gateway
- *
- * @generated from field: string gateway = 3;
- */
- gateway: string;
-
- /**
- * DNS servers
- *
- * @generated from field: repeated string dns_servers = 4;
- */
- dnsServers: string[];
-
- /**
- * Use DHCP instead of static config
- *
- * @generated from field: bool dhcp = 5;
- */
- dhcp: boolean;
-};
-
-/**
- * Describes the message metald.v1.IPv4Config.
- * Use `create(IPv4ConfigSchema)` to create a new message.
- */
-export const IPv4ConfigSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_network, 2);
-
-/**
- * IPv6 network configuration
- *
- * @generated from message metald.v1.IPv6Config
- */
-export type IPv6Config = Message<"metald.v1.IPv6Config"> & {
- /**
- * IPv6 address (e.g., "fd00::1:2")
- *
- * @generated from field: string address = 1;
- */
- address: string;
-
- /**
- * Prefix length (e.g., 64)
- *
- * @generated from field: int32 prefix_length = 2;
- */
- prefixLength: number;
-
- /**
- * Default gateway
- *
- * @generated from field: string gateway = 3;
- */
- gateway: string;
-
- /**
- * DNS servers (IPv6 addresses)
- *
- * @generated from field: repeated string dns_servers = 4;
- */
- dnsServers: string[];
-
- /**
- * Use SLAAC (Stateless Address Autoconfiguration)
- *
- * @generated from field: bool slaac = 5;
- */
- slaac: boolean;
-
- /**
- * Enable privacy extensions
- *
- * @generated from field: bool privacy_extensions = 6;
- */
- privacyExtensions: boolean;
-
- /**
- * Link-local address (auto-generated if empty)
- *
- * @generated from field: string link_local = 7;
- */
- linkLocal: string;
-};
-
-/**
- * Describes the message metald.v1.IPv6Config.
- * Use `create(IPv6ConfigSchema)` to create a new message.
- */
-export const IPv6ConfigSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_network, 3);
-
-/**
- * Rate limiting configuration
- *
- * @generated from message metald.v1.RateLimit
- */
-export type RateLimit = Message<"metald.v1.RateLimit"> & {
- /**
- * Bandwidth in bytes/second
- *
- * @generated from field: int64 bandwidth = 1;
- */
- bandwidth: bigint;
-
- /**
- * Token bucket refill time in milliseconds
- *
- * @generated from field: int64 refill_time = 2;
- */
- refillTime: bigint;
-
- /**
- * Burst size in bytes
- *
- * @generated from field: int64 burst = 3;
- */
- burst: bigint;
-};
-
-/**
- * Describes the message metald.v1.RateLimit.
- * Use `create(RateLimitSchema)` to create a new message.
- */
-export const RateLimitSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_network, 4);
-
-/**
- * Network mode for the interface
- *
- * @generated from enum metald.v1.NetworkMode
- */
-export enum NetworkMode {
- /**
- * @generated from enum value: NETWORK_MODE_UNSPECIFIED = 0;
- */
- UNSPECIFIED = 0,
-
- /**
- * Both IPv4 and IPv6
- *
- * @generated from enum value: NETWORK_MODE_DUAL_STACK = 1;
- */
- DUAL_STACK = 1,
-
- /**
- * IPv4 only
- *
- * @generated from enum value: NETWORK_MODE_IPV4_ONLY = 2;
- */
- IPV4_ONLY = 2,
-
- /**
- * IPv6 only
- *
- * @generated from enum value: NETWORK_MODE_IPV6_ONLY = 3;
- */
- IPV6_ONLY = 3,
-}
-
-/**
- * Describes the enum metald.v1.NetworkMode.
- */
-export const NetworkModeSchema: GenEnum =
- /*@__PURE__*/
- enumDesc(file_metald_v1_network, 0);
diff --git a/internal/proto/generated/metald/v1/storage_pb.ts b/internal/proto/generated/metald/v1/storage_pb.ts
deleted file mode 100644
index ae751e678c..0000000000
--- a/internal/proto/generated/metald/v1/storage_pb.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-// @generated by protoc-gen-es v2.8.0 with parameter "target=ts"
-// @generated from file metald/v1/storage.proto (package metald.v1, syntax proto3)
-/* eslint-disable */
-
-import type { Message } from "@bufbuild/protobuf";
-import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv2";
-import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv2";
-
-/**
- * Describes the file metald/v1/storage.proto.
- */
-export const file_metald_v1_storage: GenFile =
- /*@__PURE__*/
- fileDesc(
- "ChdtZXRhbGQvdjEvc3RvcmFnZS5wcm90bxIJbWV0YWxkLnYxImwKDFN0b3JhZ2VTdGF0cxISCgpieXRlc19yZWFkGAEgASgDEhUKDWJ5dGVzX3dyaXR0ZW4YAiABKAMSFwoPcmVhZF9vcGVyYXRpb25zGAMgASgDEhgKEHdyaXRlX29wZXJhdGlvbnMYBCABKAMi1AEKDVN0b3JhZ2VEZXZpY2USCgoCaWQYASABKAkSDAoEcGF0aBgCIAEoCRIRCglyZWFkX29ubHkYAyABKAgSFgoOaXNfcm9vdF9kZXZpY2UYBCABKAgSFgoOaW50ZXJmYWNlX3R5cGUYBSABKAkSNgoHb3B0aW9ucxgGIAMoCzIlLm1ldGFsZC52MS5TdG9yYWdlRGV2aWNlLk9wdGlvbnNFbnRyeRouCgxPcHRpb25zRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4AUI6WjhnaXRodWIuY29tL3Vua2V5ZWQvdW5rZXkvZ28vZ2VuL3Byb3RvL21ldGFsZC92MTttZXRhbGR2MWIGcHJvdG8z",
- );
-
-/**
- * @generated from message metald.v1.StorageStats
- */
-export type StorageStats = Message<"metald.v1.StorageStats"> & {
- /**
- * @generated from field: int64 bytes_read = 1;
- */
- bytesRead: bigint;
-
- /**
- * @generated from field: int64 bytes_written = 2;
- */
- bytesWritten: bigint;
-
- /**
- * @generated from field: int64 read_operations = 3;
- */
- readOperations: bigint;
-
- /**
- * @generated from field: int64 write_operations = 4;
- */
- writeOperations: bigint;
-};
-
-/**
- * Describes the message metald.v1.StorageStats.
- * Use `create(StorageStatsSchema)` to create a new message.
- */
-export const StorageStatsSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_storage, 0);
-
-/**
- * @generated from message metald.v1.StorageDevice
- */
-export type StorageDevice = Message<"metald.v1.StorageDevice"> & {
- /**
- * Unique identifier for this storage device
- *
- * @generated from field: string id = 1;
- */
- id: string;
-
- /**
- * Path to the backing file or block device
- *
- * @generated from field: string path = 2;
- */
- path: string;
-
- /**
- * Whether this device is read-only
- *
- * @generated from field: bool read_only = 3;
- */
- readOnly: boolean;
-
- /**
- * Whether this is the root/boot device
- *
- * @generated from field: bool is_root_device = 4;
- */
- isRootDevice: boolean;
-
- /**
- * Storage interface type (virtio-blk, nvme, etc.)
- *
- * @generated from field: string interface_type = 5;
- */
- interfaceType: string;
-
- /**
- * Additional storage options
- *
- * @generated from field: map options = 6;
- */
- options: { [key: string]: string };
-};
-
-/**
- * Describes the message metald.v1.StorageDevice.
- * Use `create(StorageDeviceSchema)` to create a new message.
- */
-export const StorageDeviceSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_storage, 1);
diff --git a/internal/proto/generated/metald/v1/vm_pb.ts b/internal/proto/generated/metald/v1/vm_pb.ts
deleted file mode 100644
index 06802ff2f4..0000000000
--- a/internal/proto/generated/metald/v1/vm_pb.ts
+++ /dev/null
@@ -1,853 +0,0 @@
-// @generated by protoc-gen-es v2.8.0 with parameter "target=ts"
-// @generated from file metald/v1/vm.proto (package metald.v1, syntax proto3)
-/* eslint-disable */
-
-import type { Message } from "@bufbuild/protobuf";
-import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv2";
-import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv2";
-import type { Timestamp } from "@bufbuild/protobuf/wkt";
-import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt";
-import type { NetworkStats } from "./network_pb";
-import { file_metald_v1_network } from "./network_pb";
-import type { StorageDevice, StorageStats } from "./storage_pb";
-import { file_metald_v1_storage } from "./storage_pb";
-
-/**
- * Describes the file metald/v1/vm.proto.
- */
-export const file_metald_v1_vm: GenFile =
- /*@__PURE__*/
- fileDesc(
- "ChJtZXRhbGQvdjEvdm0ucHJvdG8SCW1ldGFsZC52MSKlAgoIVm1Db25maWcSEgoKdmNwdV9jb3VudBgBIAEoDRIXCg9tZW1vcnlfc2l6ZV9taWIYAiABKAQSDAoEYm9vdBgDIAEoCRIWCg5uZXR3b3JrX2NvbmZpZxgEIAEoCRIpCgdjb25zb2xlGAUgASgLMhgubWV0YWxkLnYxLkNvbnNvbGVDb25maWcSKQoHc3RvcmFnZRgGIAEoCzIYLm1ldGFsZC52MS5TdG9yYWdlRGV2aWNlEgoKAmlkGAcgASgJEjMKCG1ldGFkYXRhGAggAygLMiEubWV0YWxkLnYxLlZtQ29uZmlnLk1ldGFkYXRhRW50cnkaLwoNTWV0YWRhdGFFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBImEKDkxpc3RWbXNSZXF1ZXN0EigKDHN0YXRlX2ZpbHRlchgBIAMoDjISLm1ldGFsZC52MS5WbVN0YXRlEhEKCXBhZ2Vfc2l6ZRgCIAEoBRISCgpwYWdlX3Rva2VuGAMgASgJIl8KD0xpc3RWbXNSZXNwb25zZRIeCgN2bXMYASADKAsyES5tZXRhbGQudjEuVm1JbmZvEhcKD25leHRfcGFnZV90b2tlbhgCIAEoCRITCgt0b3RhbF9jb3VudBgDIAEoBCJFCg9DcmVhdGVWbVJlcXVlc3QSDQoFdm1faWQYASABKAkSIwoGY29uZmlnGAIgASgLMhMubWV0YWxkLnYxLlZtQ29uZmlnIiYKCEVuZHBvaW50EgwKBGhvc3QYASABKAkSDAoEcG9ydBgCIAEoDSJcChBDcmVhdGVWbVJlc3BvbnNlEiEKBXN0YXRlGAEgASgOMhIubWV0YWxkLnYxLlZtU3RhdGUSJQoIZW5kcG9pbnQYAiABKAsyEy5tZXRhbGQudjEuRW5kcG9pbnQiLwoPRGVsZXRlVm1SZXF1ZXN0Eg0KBXZtX2lkGAEgASgJEg0KBWZvcmNlGAIgASgIIiMKEERlbGV0ZVZtUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCCIeCg1Cb290Vm1SZXF1ZXN0Eg0KBXZtX2lkGAEgASgJIjMKDkJvb3RWbVJlc3BvbnNlEiEKBXN0YXRlGAIgASgOMhIubWV0YWxkLnYxLlZtU3RhdGUiSgoRU2h1dGRvd25WbVJlcXVlc3QSDQoFdm1faWQYASABKAkSDQoFZm9yY2UYAiABKAgSFwoPdGltZW91dF9zZWNvbmRzGAMgASgFIjcKElNodXRkb3duVm1SZXNwb25zZRIhCgVzdGF0ZRgCIAEoDjISLm1ldGFsZC52MS5WbVN0YXRlIh8KDlBhdXNlVm1SZXF1ZXN0Eg0KBXZtX2lkGAEgASgJIjQKD1BhdXNlVm1SZXNwb25zZRIhCgVzdGF0ZRgCIAEoDjISLm1ldGFsZC52MS5WbVN0YXRlIiAKD1Jlc3VtZVZtUmVxdWVzdBINCgV2bV9pZBgBIAEoCSI1ChBSZXN1bWVWbVJlc3BvbnNlEiEKBXN0YXRlGAIgASgOMhIubWV0YWxkLnYxLlZtU3RhdGUiLwoPUmVib290Vm1SZXF1ZXN0Eg0KBXZtX2lkGAEgASgJEg0KBWZvcmNlGAIgASgIIjUKEFJlYm9vdFZtUmVzcG9uc2USIQoFc3RhdGUYAiABKA4yEi5tZXRhbGQudjEuVm1TdGF0ZSIhChBHZXRWbUluZm9SZXF1ZXN0Eg0KBXZtX2lkGAEgASgJIooCChFHZXRWbUluZm9SZXNwb25zZRINCgV2bV9pZBgBIAEoCRIjCgZjb25maWcYAiABKAsyEy5tZXRhbGQudjEuVm1Db25maWcSIQoFc3RhdGUYAyABKA4yEi5tZXRhbGQudjEuVm1TdGF0ZRIlCgdtZXRyaWNzGAQgASgLMhQubWV0YWxkLnYxLlZtTWV0cmljcxJDCgxiYWNrZW5kX2luZm8YBSADKAsyLS5tZXRhbGQudjEuR2V0Vm1JbmZvUmVzcG9uc2UuQmFja2VuZEluZm9FbnRyeRoyChBCYWNrZW5kSW5mb0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiuAEKCVZtTWV0cmljcxIZChFjcHVfdXNhZ2VfcGVyY2VudBgBIAEoARIYChBtZW1vcnlfdXNhZ2VfbWliGAIgASgEEi4KDW5ldHdvcmtfc3RhdHMYAyABKAsyFy5tZXRhbGQudjEuTmV0d29ya1N0YXRzEi4KDXN0b3JhZ2Vfc3RhdHMYBCABKAsyFy5tZXRhbGQudjEuU3RvcmFnZVN0YXRzEhYKDnVwdGltZV9zZWNvbmRzGAUgASgDItECCgZWbUluZm8SDQoFdm1faWQYASABKAkSIQoFc3RhdGUYAiABKA4yEi5tZXRhbGQudjEuVm1TdGF0ZRISCgp2Y3B1X2NvdW50GAMgASgFEhcKD21lbW9yeV9zaXplX21pYhgEIAEoBBI1ChFjcmVhdGVkX3RpbWVzdGFtcBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASNgoSbW9kaWZpZWRfdGltZXN0YW1wGAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIxCghtZXRhZGF0YRgHIAMoCzIfLm1ldGFsZC52MS5WbUluZm8uTWV0YWRhdGFFbnRyeRIVCg1kZXBsb3ltZW50X2lkGAggASgJGi8KDU1ldGFkYXRhRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASLIAQoJQ3B1Q29uZmlnEhIKCnZjcHVfY291bnQYASABKAUSFgoObWF4X3ZjcHVfY291bnQYAiABKAUSKAoIdG9wb2xvZ3kYAyABKAsyFi5tZXRhbGQudjEuQ3B1VG9wb2xvZ3kSNAoIZmVhdHVyZXMYBCADKAsyIi5tZXRhbGQudjEuQ3B1Q29uZmlnLkZlYXR1cmVzRW50cnkaLwoNRmVhdHVyZXNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIlIKC0NwdVRvcG9sb2d5Eg8KB3NvY2tldHMYASABKAUSGAoQY29yZXNfcGVyX3NvY2tldBgCIAEoBRIYChB0aHJlYWRzX3Blcl9jb3JlGAMgASgFIo4BCgxNZW1vcnlDb25maWcSFwoPbWVtb3J5X3NpemVfbWliGAEgASgDEjUKB2JhY2tpbmcYAiADKAsyJC5tZXRhbGQudjEuTWVtb3J5Q29uZmlnLkJhY2tpbmdFbnRyeRouCgxCYWNraW5nRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASK9AQoKQm9vdENvbmZpZxITCgtrZXJuZWxfcGF0aBgBIAEoCRITCgtpbml0cmRfcGF0aBgCIAEoCRITCgtrZXJuZWxfYXJncxgDIAEoCRI8Cgxib290X29wdGlvbnMYBCADKAsyJi5tZXRhbGQudjEuQm9vdENvbmZpZy5Cb290T3B0aW9uc0VudHJ5GjIKEEJvb3RPcHRpb25zRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJVCg1Db25zb2xlQ29uZmlnEg8KB2VuYWJsZWQYASABKAgSDgoGb3V0cHV0GAIgASgJEg0KBWlucHV0GAMgASgJEhQKDGNvbnNvbGVfdHlwZRgEIAEoCSp7CgdWbVN0YXRlEhgKFFZNX1NUQVRFX1VOU1BFQ0lGSUVEEAASFAoQVk1fU1RBVEVfQ1JFQVRFRBABEhQKEFZNX1NUQVRFX1JVTk5JTkcQAhITCg9WTV9TVEFURV9QQVVTRUQQAxIVChFWTV9TVEFURV9TSFVURE9XThAEQjpaOGdpdGh1Yi5jb20vdW5rZXllZC91bmtleS9nby9nZW4vcHJvdG8vbWV0YWxkL3YxO21ldGFsZHYxYgZwcm90bzM",
- [file_google_protobuf_timestamp, file_metald_v1_network, file_metald_v1_storage],
- );
-
-/**
- * Unified VM configuration that works across different hypervisors
- *
- * @generated from message metald.v1.VmConfig
- */
-export type VmConfig = Message<"metald.v1.VmConfig"> & {
- /**
- * CPU configuration
- *
- * @generated from field: uint32 vcpu_count = 1;
- */
- vcpuCount: number;
-
- /**
- * Memory configuration
- *
- * @generated from field: uint64 memory_size_mib = 2;
- */
- memorySizeMib: bigint;
-
- /**
- * Boot configuration
- *
- * @generated from field: string boot = 3;
- */
- boot: string;
-
- /**
- * Network configuration
- *
- * @generated from field: string network_config = 4;
- */
- networkConfig: string;
-
- /**
- * Console configuration
- *
- * @generated from field: metald.v1.ConsoleConfig console = 5;
- */
- console?: ConsoleConfig;
-
- /**
- * Storage configuration
- *
- * @generated from field: metald.v1.StorageDevice storage = 6;
- */
- storage?: StorageDevice;
-
- /**
- * VM Identifier
- *
- * @generated from field: string id = 7;
- */
- id: string;
-
- /**
- * Metadata and labels
- *
- * @generated from field: map metadata = 8;
- */
- metadata: { [key: string]: string };
-};
-
-/**
- * Describes the message metald.v1.VmConfig.
- * Use `create(VmConfigSchema)` to create a new message.
- */
-export const VmConfigSchema: GenMessage = /*@__PURE__*/ messageDesc(file_metald_v1_vm, 0);
-
-/**
- * @generated from message metald.v1.ListVmsRequest
- */
-export type ListVmsRequest = Message<"metald.v1.ListVmsRequest"> & {
- /**
- * Optional filter by state
- *
- * @generated from field: repeated metald.v1.VmState state_filter = 1;
- */
- stateFilter: VmState[];
-
- /**
- * Pagination
- *
- * @generated from field: int32 page_size = 2;
- */
- pageSize: number;
-
- /**
- * @generated from field: string page_token = 3;
- */
- pageToken: string;
-};
-
-/**
- * Describes the message metald.v1.ListVmsRequest.
- * Use `create(ListVmsRequestSchema)` to create a new message.
- */
-export const ListVmsRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 1);
-
-/**
- * @generated from message metald.v1.ListVmsResponse
- */
-export type ListVmsResponse = Message<"metald.v1.ListVmsResponse"> & {
- /**
- * @generated from field: repeated metald.v1.VmInfo vms = 1;
- */
- vms: VmInfo[];
-
- /**
- * @generated from field: string next_page_token = 2;
- */
- nextPageToken: string;
-
- /**
- * @generated from field: uint64 total_count = 3;
- */
- totalCount: bigint;
-};
-
-/**
- * Describes the message metald.v1.ListVmsResponse.
- * Use `create(ListVmsResponseSchema)` to create a new message.
- */
-export const ListVmsResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 2);
-
-/**
- * Request/Response messages
- *
- * @generated from message metald.v1.CreateVmRequest
- */
-export type CreateVmRequest = Message<"metald.v1.CreateVmRequest"> & {
- /**
- * Generated unique identifier for the VM
- *
- * @generated from field: string vm_id = 1;
- */
- vmId: string;
-
- /**
- * VM configuration
- *
- * @generated from field: metald.v1.VmConfig config = 2;
- */
- config?: VmConfig;
-};
-
-/**
- * Describes the message metald.v1.CreateVmRequest.
- * Use `create(CreateVmRequestSchema)` to create a new message.
- */
-export const CreateVmRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 3);
-
-/**
- * @generated from message metald.v1.Endpoint
- */
-export type Endpoint = Message<"metald.v1.Endpoint"> & {
- /**
- * @generated from field: string host = 1;
- */
- host: string;
-
- /**
- * @generated from field: uint32 port = 2;
- */
- port: number;
-};
-
-/**
- * Describes the message metald.v1.Endpoint.
- * Use `create(EndpointSchema)` to create a new message.
- */
-export const EndpointSchema: GenMessage = /*@__PURE__*/ messageDesc(file_metald_v1_vm, 4);
-
-/**
- * @generated from message metald.v1.CreateVmResponse
- */
-export type CreateVmResponse = Message<"metald.v1.CreateVmResponse"> & {
- /**
- * Current VM state after creation
- *
- * @generated from field: metald.v1.VmState state = 1;
- */
- state: VmState;
-
- /**
- * Endpoint is the host:port pair
- *
- * @generated from field: metald.v1.Endpoint endpoint = 2;
- */
- endpoint?: Endpoint;
-};
-
-/**
- * Describes the message metald.v1.CreateVmResponse.
- * Use `create(CreateVmResponseSchema)` to create a new message.
- */
-export const CreateVmResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 5);
-
-/**
- * @generated from message metald.v1.DeleteVmRequest
- */
-export type DeleteVmRequest = Message<"metald.v1.DeleteVmRequest"> & {
- /**
- * @generated from field: string vm_id = 1;
- */
- vmId: string;
-
- /**
- * Whether to force deletion even if VM is running
- *
- * @generated from field: bool force = 2;
- */
- force: boolean;
-};
-
-/**
- * Describes the message metald.v1.DeleteVmRequest.
- * Use `create(DeleteVmRequestSchema)` to create a new message.
- */
-export const DeleteVmRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 6);
-
-/**
- * @generated from message metald.v1.DeleteVmResponse
- */
-export type DeleteVmResponse = Message<"metald.v1.DeleteVmResponse"> & {
- /**
- * @generated from field: bool success = 1;
- */
- success: boolean;
-};
-
-/**
- * Describes the message metald.v1.DeleteVmResponse.
- * Use `create(DeleteVmResponseSchema)` to create a new message.
- */
-export const DeleteVmResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 7);
-
-/**
- * @generated from message metald.v1.BootVmRequest
- */
-export type BootVmRequest = Message<"metald.v1.BootVmRequest"> & {
- /**
- * @generated from field: string vm_id = 1;
- */
- vmId: string;
-};
-
-/**
- * Describes the message metald.v1.BootVmRequest.
- * Use `create(BootVmRequestSchema)` to create a new message.
- */
-export const BootVmRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 8);
-
-/**
- * @generated from message metald.v1.BootVmResponse
- */
-export type BootVmResponse = Message<"metald.v1.BootVmResponse"> & {
- /**
- * @generated from field: metald.v1.VmState state = 2;
- */
- state: VmState;
-};
-
-/**
- * Describes the message metald.v1.BootVmResponse.
- * Use `create(BootVmResponseSchema)` to create a new message.
- */
-export const BootVmResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 9);
-
-/**
- * @generated from message metald.v1.ShutdownVmRequest
- */
-export type ShutdownVmRequest = Message<"metald.v1.ShutdownVmRequest"> & {
- /**
- * @generated from field: string vm_id = 1;
- */
- vmId: string;
-
- /**
- * Whether to force shutdown (vs graceful)
- *
- * @generated from field: bool force = 2;
- */
- force: boolean;
-
- /**
- * Timeout for graceful shutdown (seconds)
- *
- * @generated from field: int32 timeout_seconds = 3;
- */
- timeoutSeconds: number;
-};
-
-/**
- * Describes the message metald.v1.ShutdownVmRequest.
- * Use `create(ShutdownVmRequestSchema)` to create a new message.
- */
-export const ShutdownVmRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 10);
-
-/**
- * @generated from message metald.v1.ShutdownVmResponse
- */
-export type ShutdownVmResponse = Message<"metald.v1.ShutdownVmResponse"> & {
- /**
- * @generated from field: metald.v1.VmState state = 2;
- */
- state: VmState;
-};
-
-/**
- * Describes the message metald.v1.ShutdownVmResponse.
- * Use `create(ShutdownVmResponseSchema)` to create a new message.
- */
-export const ShutdownVmResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 11);
-
-/**
- * @generated from message metald.v1.PauseVmRequest
- */
-export type PauseVmRequest = Message<"metald.v1.PauseVmRequest"> & {
- /**
- * @generated from field: string vm_id = 1;
- */
- vmId: string;
-};
-
-/**
- * Describes the message metald.v1.PauseVmRequest.
- * Use `create(PauseVmRequestSchema)` to create a new message.
- */
-export const PauseVmRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 12);
-
-/**
- * @generated from message metald.v1.PauseVmResponse
- */
-export type PauseVmResponse = Message<"metald.v1.PauseVmResponse"> & {
- /**
- * @generated from field: metald.v1.VmState state = 2;
- */
- state: VmState;
-};
-
-/**
- * Describes the message metald.v1.PauseVmResponse.
- * Use `create(PauseVmResponseSchema)` to create a new message.
- */
-export const PauseVmResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 13);
-
-/**
- * @generated from message metald.v1.ResumeVmRequest
- */
-export type ResumeVmRequest = Message<"metald.v1.ResumeVmRequest"> & {
- /**
- * @generated from field: string vm_id = 1;
- */
- vmId: string;
-};
-
-/**
- * Describes the message metald.v1.ResumeVmRequest.
- * Use `create(ResumeVmRequestSchema)` to create a new message.
- */
-export const ResumeVmRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 14);
-
-/**
- * @generated from message metald.v1.ResumeVmResponse
- */
-export type ResumeVmResponse = Message<"metald.v1.ResumeVmResponse"> & {
- /**
- * @generated from field: metald.v1.VmState state = 2;
- */
- state: VmState;
-};
-
-/**
- * Describes the message metald.v1.ResumeVmResponse.
- * Use `create(ResumeVmResponseSchema)` to create a new message.
- */
-export const ResumeVmResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 15);
-
-/**
- * @generated from message metald.v1.RebootVmRequest
- */
-export type RebootVmRequest = Message<"metald.v1.RebootVmRequest"> & {
- /**
- * @generated from field: string vm_id = 1;
- */
- vmId: string;
-
- /**
- * Whether to force reboot (vs graceful)
- *
- * @generated from field: bool force = 2;
- */
- force: boolean;
-};
-
-/**
- * Describes the message metald.v1.RebootVmRequest.
- * Use `create(RebootVmRequestSchema)` to create a new message.
- */
-export const RebootVmRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 16);
-
-/**
- * @generated from message metald.v1.RebootVmResponse
- */
-export type RebootVmResponse = Message<"metald.v1.RebootVmResponse"> & {
- /**
- * @generated from field: metald.v1.VmState state = 2;
- */
- state: VmState;
-};
-
-/**
- * Describes the message metald.v1.RebootVmResponse.
- * Use `create(RebootVmResponseSchema)` to create a new message.
- */
-export const RebootVmResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 17);
-
-/**
- * @generated from message metald.v1.GetVmInfoRequest
- */
-export type GetVmInfoRequest = Message<"metald.v1.GetVmInfoRequest"> & {
- /**
- * @generated from field: string vm_id = 1;
- */
- vmId: string;
-};
-
-/**
- * Describes the message metald.v1.GetVmInfoRequest.
- * Use `create(GetVmInfoRequestSchema)` to create a new message.
- */
-export const GetVmInfoRequestSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 18);
-
-/**
- * @generated from message metald.v1.GetVmInfoResponse
- */
-export type GetVmInfoResponse = Message<"metald.v1.GetVmInfoResponse"> & {
- /**
- * @generated from field: string vm_id = 1;
- */
- vmId: string;
-
- /**
- * @generated from field: metald.v1.VmConfig config = 2;
- */
- config?: VmConfig;
-
- /**
- * @generated from field: metald.v1.VmState state = 3;
- */
- state: VmState;
-
- /**
- * @generated from field: metald.v1.VmMetrics metrics = 4;
- */
- metrics?: VmMetrics;
-
- /**
- * Backend-specific information
- *
- * @generated from field: map backend_info = 5;
- */
- backendInfo: { [key: string]: string };
-};
-
-/**
- * Describes the message metald.v1.GetVmInfoResponse.
- * Use `create(GetVmInfoResponseSchema)` to create a new message.
- */
-export const GetVmInfoResponseSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 19);
-
-/**
- * @generated from message metald.v1.VmMetrics
- */
-export type VmMetrics = Message<"metald.v1.VmMetrics"> & {
- /**
- * CPU usage percentage (0-100)
- *
- * @generated from field: double cpu_usage_percent = 1;
- */
- cpuUsagePercent: number;
-
- /**
- * Memory usage in MiB
- *
- * @generated from field: uint64 memory_usage_mib = 2;
- */
- memoryUsageMib: bigint;
-
- /**
- * Network I/O statistics
- *
- * @generated from field: metald.v1.NetworkStats network_stats = 3;
- */
- networkStats?: NetworkStats;
-
- /**
- * Storage I/O statistics
- *
- * @generated from field: metald.v1.StorageStats storage_stats = 4;
- */
- storageStats?: StorageStats;
-
- /**
- * VM uptime in seconds
- *
- * @generated from field: int64 uptime_seconds = 5;
- */
- uptimeSeconds: bigint;
-};
-
-/**
- * Describes the message metald.v1.VmMetrics.
- * Use `create(VmMetricsSchema)` to create a new message.
- */
-export const VmMetricsSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 20);
-
-/**
- * @generated from message metald.v1.VmInfo
- */
-export type VmInfo = Message<"metald.v1.VmInfo"> & {
- /**
- * @generated from field: string vm_id = 1;
- */
- vmId: string;
-
- /**
- * @generated from field: metald.v1.VmState state = 2;
- */
- state: VmState;
-
- /**
- * Basic config info (subset of full config)
- *
- * @generated from field: int32 vcpu_count = 3;
- */
- vcpuCount: number;
-
- /**
- * @generated from field: uint64 memory_size_mib = 4;
- */
- memorySizeMib: bigint;
-
- /**
- * Creation and modification timestamps
- *
- * @generated from field: google.protobuf.Timestamp created_timestamp = 5;
- */
- createdTimestamp?: Timestamp;
-
- /**
- * @generated from field: google.protobuf.Timestamp modified_timestamp = 6;
- */
- modifiedTimestamp?: Timestamp;
-
- /**
- * Metadata
- *
- * @generated from field: map metadata = 7;
- */
- metadata: { [key: string]: string };
-
- /**
- * deployment_id vm is attached to
- *
- * @generated from field: string deployment_id = 8;
- */
- deploymentId: string;
-};
-
-/**
- * Describes the message metald.v1.VmInfo.
- * Use `create(VmInfoSchema)` to create a new message.
- */
-export const VmInfoSchema: GenMessage = /*@__PURE__*/ messageDesc(file_metald_v1_vm, 21);
-
-/**
- * @generated from message metald.v1.CpuConfig
- */
-export type CpuConfig = Message<"metald.v1.CpuConfig"> & {
- /**
- * Number of virtual CPUs to allocate at boot
- *
- * @generated from field: int32 vcpu_count = 1;
- */
- vcpuCount: number;
-
- /**
- * Maximum number of virtual CPUs (for hotplug)
- *
- * @generated from field: int32 max_vcpu_count = 2;
- */
- maxVcpuCount: number;
-
- /**
- * CPU topology (optional)
- *
- * @generated from field: metald.v1.CpuTopology topology = 3;
- */
- topology?: CpuTopology;
-
- /**
- * CPU features and model (backend-specific)
- *
- * @generated from field: map features = 4;
- */
- features: { [key: string]: string };
-};
-
-/**
- * Describes the message metald.v1.CpuConfig.
- * Use `create(CpuConfigSchema)` to create a new message.
- */
-export const CpuConfigSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 22);
-
-/**
- * @generated from message metald.v1.CpuTopology
- */
-export type CpuTopology = Message<"metald.v1.CpuTopology"> & {
- /**
- * @generated from field: int32 sockets = 1;
- */
- sockets: number;
-
- /**
- * @generated from field: int32 cores_per_socket = 2;
- */
- coresPerSocket: number;
-
- /**
- * @generated from field: int32 threads_per_core = 3;
- */
- threadsPerCore: number;
-};
-
-/**
- * Describes the message metald.v1.CpuTopology.
- * Use `create(CpuTopologySchema)` to create a new message.
- */
-export const CpuTopologySchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 23);
-
-/**
- * @generated from message metald.v1.MemoryConfig
- */
-export type MemoryConfig = Message<"metald.v1.MemoryConfig"> & {
- /**
- * Memory size in MiB
- *
- * @generated from field: int64 memory_size_mib = 1;
- */
- memorySizeMib: bigint;
-
- /**
- * Memory backing options (hugepages, etc.)
- *
- * @generated from field: map backing = 2;
- */
- backing: { [key: string]: string };
-};
-
-/**
- * Describes the message metald.v1.MemoryConfig.
- * Use `create(MemoryConfigSchema)` to create a new message.
- */
-export const MemoryConfigSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 24);
-
-/**
- * @generated from message metald.v1.BootConfig
- */
-export type BootConfig = Message<"metald.v1.BootConfig"> & {
- /**
- * Path to kernel image
- *
- * @generated from field: string kernel_path = 1;
- */
- kernelPath: string;
-
- /**
- * Path to initial ramdisk (optional)
- *
- * @generated from field: string initrd_path = 2;
- */
- initrdPath: string;
-
- /**
- * Kernel command line arguments
- *
- * @generated from field: string kernel_args = 3;
- */
- kernelArgs: string;
-
- /**
- * Boot order and options
- *
- * @generated from field: map boot_options = 4;
- */
- bootOptions: { [key: string]: string };
-};
-
-/**
- * Describes the message metald.v1.BootConfig.
- * Use `create(BootConfigSchema)` to create a new message.
- */
-export const BootConfigSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 25);
-
-/**
- * @generated from message metald.v1.ConsoleConfig
- */
-export type ConsoleConfig = Message<"metald.v1.ConsoleConfig"> & {
- /**
- * Whether console is enabled
- *
- * @generated from field: bool enabled = 1;
- */
- enabled: boolean;
-
- /**
- * Console output destination (file path, pty, etc.)
- *
- * @generated from field: string output = 2;
- */
- output: string;
-
- /**
- * Console input source (optional)
- *
- * @generated from field: string input = 3;
- */
- input: string;
-
- /**
- * Console type (serial, virtio-console, etc.)
- *
- * @generated from field: string console_type = 4;
- */
- consoleType: string;
-};
-
-/**
- * Describes the message metald.v1.ConsoleConfig.
- * Use `create(ConsoleConfigSchema)` to create a new message.
- */
-export const ConsoleConfigSchema: GenMessage =
- /*@__PURE__*/
- messageDesc(file_metald_v1_vm, 26);
-
-/**
- * VM lifecycle states
- *
- * @generated from enum metald.v1.VmState
- */
-export enum VmState {
- /**
- * @generated from enum value: VM_STATE_UNSPECIFIED = 0;
- */
- UNSPECIFIED = 0,
-
- /**
- * @generated from enum value: VM_STATE_CREATED = 1;
- */
- CREATED = 1,
-
- /**
- * @generated from enum value: VM_STATE_RUNNING = 2;
- */
- RUNNING = 2,
-
- /**
- * @generated from enum value: VM_STATE_PAUSED = 3;
- */
- PAUSED = 3,
-
- /**
- * @generated from enum value: VM_STATE_SHUTDOWN = 4;
- */
- SHUTDOWN = 4,
-}
-
-/**
- * Describes the enum metald.v1.VmState.
- */
-export const VmStateSchema: GenEnum = /*@__PURE__*/ enumDesc(file_metald_v1_vm, 0);