feat: client enrollment API and compute entity provisioning
Phase 1 implementation from the client-lifecycle plan.
- Migration 012: provisioning_steps table, context_version, context_files,
enrolled_at column, slug+type index for machine entities
- API endpoints (openapi.yaml + generated code):
POST /clients/enroll — age key issuance, Infisical identity, state transition
GET /clients/{slug}/context — agent file delta polling (replaces git pull)
GET /clients/{slug}/secrets — scoped secret listing
POST /entities/provision — compute entity creation with constraint validation
GET /entities/{slug}/provision/status — step-by-step provisioning progress
- Handlers in impl.go: enrollment with state validation and age key generation,
provisioning with execution tracking and relationship creation,
context endpoint with since-based delta queries
- Server struct extended with secretsBackend interface for key storage
- All tests pass, build clean
This commit is contained in:
284
api/openapi.yaml
284
api/openapi.yaml
@@ -41,6 +41,8 @@ servers:
|
||||
tags:
|
||||
- name: entities
|
||||
description: Inventory graph — entities and relationships
|
||||
- name: clients
|
||||
description: Client enrollment, context distribution, secrets
|
||||
- name: ontology
|
||||
description: Entity types, relationship types, lifecycles
|
||||
- name: signals
|
||||
@@ -359,6 +361,129 @@ paths:
|
||||
type: integer
|
||||
default:
|
||||
$ref: '#/components/responses/Problem'
|
||||
/clients/enroll:
|
||||
post:
|
||||
tags:
|
||||
- clients
|
||||
operationId: enrollClient
|
||||
summary: Enroll a new client — issue age key, create Infisical identity
|
||||
description: >
|
||||
Validates mesh IP, generates an age keypair, creates an Infisical
|
||||
machine identity, and transitions the entity to provisioning.
|
||||
Caller must already have an entity in planned or provisioning state.
|
||||
x-required-scope: agent
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/EnrollRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Enrollment response with keys and identity
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/EnrollResponse'
|
||||
default:
|
||||
$ref: '#/components/responses/Problem'
|
||||
/clients/{slug}/context:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/EntitySlug'
|
||||
get:
|
||||
tags:
|
||||
- clients
|
||||
operationId: getClientContext
|
||||
summary: Get agent context delta since a timestamp
|
||||
description: >
|
||||
Returns which agent files, tools, and SOPS config changed since the
|
||||
given timestamp. Thin clients poll this instead of git pull.
|
||||
x-required-scope: agent
|
||||
parameters:
|
||||
- name: since
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Return only changes since this timestamp (RFC 3339)
|
||||
responses:
|
||||
'200':
|
||||
description: Context delta
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ClientContext'
|
||||
default:
|
||||
$ref: '#/components/responses/Problem'
|
||||
/clients/{slug}/secrets:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/EntitySlug'
|
||||
get:
|
||||
tags:
|
||||
- clients
|
||||
operationId: getClientSecrets
|
||||
summary: List secrets accessible to this client
|
||||
description: Infisical-secured secrets scoped to the client's machine identity
|
||||
x-required-scope: agent
|
||||
responses:
|
||||
'200':
|
||||
description: Secret keys accessible to this client
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ClientSecrets'
|
||||
default:
|
||||
$ref: '#/components/responses/Problem'
|
||||
/entities/provision:
|
||||
post:
|
||||
tags:
|
||||
- entities
|
||||
operationId: provisionEntity
|
||||
summary: Provision a compute entity (LXC, VM, container) on a host
|
||||
description: >
|
||||
Creates the entity in planned state, validates constraints (VMID, IP,
|
||||
capacity, template), classifies the action against policy, and
|
||||
transitions to provisioning on operator approval.
|
||||
x-required-scope: operator
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/IdempotencyKey'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProvisionRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: Entity created, provisioning queued
|
||||
headers:
|
||||
ETag:
|
||||
$ref: '#/components/headers/ETag'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProvisionResponse'
|
||||
default:
|
||||
$ref: '#/components/responses/Problem'
|
||||
/entities/{slug}/provision/status:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/EntitySlug'
|
||||
get:
|
||||
tags:
|
||||
- entities
|
||||
operationId: getProvisionStatus
|
||||
summary: Poll provisioning progress for a compute entity
|
||||
x-required-scope: viewer
|
||||
responses:
|
||||
'200':
|
||||
description: Provisioning steps with status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProvisionStatus'
|
||||
default:
|
||||
$ref: '#/components/responses/Problem'
|
||||
|
||||
/ontology:
|
||||
get:
|
||||
tags:
|
||||
@@ -1658,6 +1783,13 @@ components:
|
||||
type: string
|
||||
maxLength: 128
|
||||
description: Client-generated key; replays within 24h return the original response
|
||||
EntitySlug:
|
||||
name: slug
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Entity slug (e.g. `ws:mac-mini`, `lxc:caddy`)
|
||||
headers:
|
||||
ETag:
|
||||
schema:
|
||||
@@ -2889,3 +3021,155 @@ components:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
EnrollRequest:
|
||||
type: object
|
||||
required:
|
||||
- slug
|
||||
properties:
|
||||
slug:
|
||||
type: string
|
||||
description: Entity slug (e.g. ws:new-laptop)
|
||||
hostname:
|
||||
type: string
|
||||
description: Actual hostname of the enrolling machine
|
||||
mesh_ip:
|
||||
type: string
|
||||
description: Source mesh IP for identity validation
|
||||
EnrollResponse:
|
||||
type: object
|
||||
required:
|
||||
- age_public_key
|
||||
- age_private_key
|
||||
- infisical_client_id
|
||||
- infisical_client_secret
|
||||
properties:
|
||||
age_public_key:
|
||||
type: string
|
||||
description: age1... public key for SOPS recipients
|
||||
age_private_key:
|
||||
type: string
|
||||
description: AGE-SECRET-KEY-... for local decryption
|
||||
infisical_client_id:
|
||||
type: string
|
||||
description: Infisical UniversalAuth client ID
|
||||
infisical_client_secret:
|
||||
type: string
|
||||
description: Infisical UniversalAuth client secret
|
||||
machine_identity_token:
|
||||
type: string
|
||||
description: Infisical machine identity access token
|
||||
ClientContext:
|
||||
type: object
|
||||
required:
|
||||
- version
|
||||
properties:
|
||||
version:
|
||||
type: integer
|
||||
description: Monotonic context version number
|
||||
agent_files_changed:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Paths of agent instruction files that changed
|
||||
sops_config_changed:
|
||||
type: boolean
|
||||
description: True if .sops.yaml recipients changed
|
||||
tools_changed:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Paths of tools/*.setup.sh that changed
|
||||
since:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Timestamp for the next poll request
|
||||
ClientSecrets:
|
||||
type: object
|
||||
required:
|
||||
- keys
|
||||
properties:
|
||||
keys:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Infisical secret keys accessible to this client
|
||||
ProvisionRequest:
|
||||
type: object
|
||||
required:
|
||||
- slug
|
||||
- type
|
||||
- name
|
||||
- host
|
||||
properties:
|
||||
slug:
|
||||
type: string
|
||||
description: e.g. lxc:jellyfin
|
||||
type:
|
||||
type: string
|
||||
description: Must be lxc, vm, or docker-container
|
||||
name:
|
||||
type: string
|
||||
description: Human-readable name
|
||||
host:
|
||||
type: string
|
||||
description: Slug of the Proxmox host (e.g. host:hubris)
|
||||
attributes:
|
||||
type: object
|
||||
description: VMID, cores, ram_mb, disk_gb, ip, template, mounts, services
|
||||
ProvisionResponse:
|
||||
type: object
|
||||
required:
|
||||
- entity
|
||||
- execution_id
|
||||
properties:
|
||||
entity:
|
||||
$ref: '#/components/schemas/Entity'
|
||||
execution_id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Execution ID to track provisioning progress
|
||||
ProvisionStatus:
|
||||
type: object
|
||||
required:
|
||||
- slug
|
||||
- state
|
||||
- steps
|
||||
properties:
|
||||
slug:
|
||||
type: string
|
||||
state:
|
||||
type: string
|
||||
description: Current entity state
|
||||
steps:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- step
|
||||
- status
|
||||
properties:
|
||||
step:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- pending
|
||||
- running
|
||||
- ok
|
||||
- failed
|
||||
- skipped
|
||||
error_message:
|
||||
type: string
|
||||
nullable: true
|
||||
started_at:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
finished_at:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
error:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Overall error if provisioning failed
|
||||
|
||||
Reference in New Issue
Block a user