builds

package
v0.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 12, 2026 License: MIT Imports: 25 Imported by: 0

README

Build System

The build system provides source-to-image builds inside ephemeral Cloud Hypervisor microVMs, enabling secure multi-tenant isolation with rootless BuildKit.

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                         Hypeman API                              │
│  POST /builds  →  BuildManager  →  BuildQueue                   │
│                        │                                         │
│              Start() → VsockHandler (port 5001)                 │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                     Builder MicroVM                              │
│  ┌─────────────────────────────────────────────────────────────┐│
│  │  Volumes Mounted:                                            ││
│  │  - /src (source code, read-write)                           ││
│  │  - /config/build.json (build configuration, read-only)      ││
│  ├─────────────────────────────────────────────────────────────┤│
│  │  Builder Agent                                               ││
│  │  ┌─────────────┐  ┌──────────────┐  ┌────────────────────┐  ││
│  │  │ Load Config │→ │ Read User's  │→ │ Run BuildKit       │  ││
│  │  │ /config/    │  │ Dockerfile   │  │ (buildctl)         │  ││
│  │  └─────────────┘  └──────────────┘  └────────────────────┘  ││
│  │                                              │               ││
│  │                                              ▼               ││
│  │                                     Push to Registry         ││
│  │                                     (JWT token auth)         ││
│  └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                       OCI Registry                               │
│              {REGISTRY_URL}/builds/{build-id}                    │
│              (default: 10.102.0.1:8083 from VM)                 │
└─────────────────────────────────────────────────────────────────┘

Components

Core Types (types.go)
Type Description
Build Build job status and metadata
CreateBuildRequest API request to create a build
BuildConfig Configuration passed to builder VM
BuildResult Result returned by builder agent
BuildProvenance Audit trail for reproducibility
BuildPolicy Resource limits and network policy
Build Queue (queue.go)

In-memory queue with configurable concurrency:

queue := NewBuildQueue(maxConcurrent)
position := queue.Enqueue(buildID, request, startFunc)
queue.Cancel(buildID)
queue.GetPosition(buildID)

Recovery: On startup, listPendingBuilds() scans disk metadata for incomplete builds and re-enqueues them in FIFO order.

Storage (storage.go)

Builds are persisted to $DATA_DIR/builds/{id}/:

builds/
└── {build-id}/
    ├── metadata.json    # Build status, provenance
    ├── config.json      # Config for builder VM
    ├── source/
    │   └── source.tar.gz
    └── logs/
        └── build.log
Build Manager (manager.go)

Orchestrates the build lifecycle:

  1. Validate request and store source
  2. Write build config to disk
  3. Enqueue build job
  4. Create source volume from archive
  5. Create config volume with build.json
  6. Create builder VM with both volumes attached
  7. Wait for build completion
  8. Update metadata and cleanup

Important: The Start() method must be called to start the vsock handler for builder communication.

Cache System (cache.go)

Registry-based caching with tenant isolation:

{registry}/cache/{tenant_scope}/{runtime}/{lockfile_hash}
gen := NewCacheKeyGenerator("localhost:8080")
key, _ := gen.GenerateCacheKey("my-tenant", "myapp", lockfileHashes)
// key.ImportCacheArg() → "type=registry,ref=localhost:8080/cache/my-tenant/myapp/abc123"
// key.ExportCacheArg() → "type=registry,ref=localhost:8080/cache/my-tenant/myapp/abc123,mode=max"
Registry Token System (registry_token.go)

JWT-based authentication for builder VMs to push images:

generator := NewRegistryTokenGenerator(jwtSecret)
token, _ := generator.GeneratePushToken(buildID, []string{"builds/abc123", "cache/tenant-x"}, 30*time.Minute)
// Token grants push access only to specified repositories
// Validated by middleware on /v2/* registry endpoints
Field Description
BuildID Build job identifier for audit
Repositories Allowed repository paths
Scope Access scope: push or pull
ExpiresAt Token expiry (matches build timeout)
Metrics (metrics.go)

OpenTelemetry metrics for monitoring:

Metric Type Description
hypeman_build_duration_seconds Histogram Build duration
hypeman_builds_total Counter Total builds by status/runtime
hypeman_build_queue_length Gauge Pending builds in queue
hypeman_builds_active Gauge Currently running builds
Builder Agent (builder_agent/main.go)

Guest binary that runs inside builder VMs:

  1. Reads config from /config/build.json
  2. Fetches secrets from host via vsock (if any)
  3. Uses user-provided Dockerfile (from source or config)
  4. Runs buildctl-daemonless.sh with cache and insecure registry flags
  5. Computes provenance (lockfile hashes, source hash)
  6. Reports result back via vsock

Note: The agent requires a Dockerfile to be provided. It can be included in the source tarball or passed via the dockerfile config parameter.

Key Details:

  • Config path: /config/build.json
  • Source path: /src
  • Uses registry.insecure=true for HTTP registries
  • Inherits BUILDKITD_FLAGS from environment

API Endpoints

Method Path Description
POST /builds Submit build (multipart form)
GET /builds List all builds
GET /builds/{id} Get build details
DELETE /builds/{id} Cancel build
GET /builds/{id}/logs Stream logs (SSE)
Submit Build Example
# Option 1: Dockerfile in source tarball
curl -X POST http://localhost:8083/builds \
  -H "Authorization: Bearer $TOKEN" \
  -F "source=@source.tar.gz" \
  -F "cache_scope=tenant-123" \
  -F "timeout_seconds=300"

# Option 2: Dockerfile as parameter
curl -X POST http://localhost:8083/builds \
  -H "Authorization: Bearer $TOKEN" \
  -F "source=@source.tar.gz" \
  -F "dockerfile=FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci
CMD [\"node\", \"index.js\"]" \
  -F "cache_scope=tenant-123"
Response
{
  "id": "abc123",
  "status": "queued",
  "created_at": "2025-01-15T10:00:00Z"
}

Configuration

Environment Variable Default Description
MAX_CONCURRENT_SOURCE_BUILDS 2 Max parallel builds
BUILDER_IMAGE hypeman/builder:latest Builder VM image
REGISTRY_URL localhost:8080 Registry for built images
BUILD_TIMEOUT 600 Default timeout (seconds)
Registry URL Configuration

The REGISTRY_URL must be accessible from inside builder VMs. Since localhost in the VM refers to the VM itself, you need to use the host's gateway IP:

# In .env
REGISTRY_URL=10.102.0.1:8083  # Gateway IP accessible from VM network
Registry Authentication

Builder VMs authenticate to the registry using short-lived JWT tokens:

  1. Token Generation: The build manager generates a scoped token for each build
  2. Token Scope: Grants push access only to builds/{build_id} and cache/{cache_scope}
  3. Token TTL: Matches build timeout (minimum 30 minutes)
  4. Authentication: Builder agent sends token via Basic auth (token: format)

Build Status Flow

queued → building → pushing → ready
                 ↘         ↗
                   failed
                      ↑
                  cancelled

Security Model

  1. Isolation: Each build runs in a fresh microVM (Cloud Hypervisor)
  2. Rootless: BuildKit runs without root privileges
  3. Network Control: network_mode: isolated or egress with optional domain allowlist
  4. Secret Handling: Secrets fetched via vsock, never written to disk in guest
  5. Cache Isolation: Per-tenant cache scopes prevent cross-tenant cache poisoning
  6. Registry Auth: Short-lived JWT tokens scoped to specific repositories (builds/{id}, cache/{scope})

Builder Images

The generic builder image is in images/generic/:

  • generic/Dockerfile - Minimal Alpine + BuildKit + agent (runtime-agnostic)

The generic builder does not include any runtime (Node.js, Python, etc.). Users provide their own Dockerfile which specifies the runtime. BuildKit pulls the runtime as part of the build process.

Required Components

Builder images must include:

Component Source Purpose
buildctl moby/buildkit:rootless BuildKit CLI
buildctl-daemonless.sh moby/buildkit:rootless Daemonless wrapper
buildkitd moby/buildkit:rootless BuildKit daemon
buildkit-runc moby/buildkit:rootless Container runtime (as /usr/bin/runc)
builder-agent Built from builder_agent/main.go Hypeman agent
fuse-overlayfs apk/apt Overlay filesystem support
Build and Push

See images/README.md for detailed build instructions.

# Build and push the builder image
docker build \
  -t yourregistry/builder:latest \
  -f lib/builds/images/generic/Dockerfile \
  .

docker push yourregistry/builder:latest
Environment Variables

The builder image should set:

# Empty or minimal flags - cgroups are mounted in microVM
ENV BUILDKITD_FLAGS=""
ENV HOME=/home/builder
ENV XDG_RUNTIME_DIR=/home/builder/.local/share

MicroVM Requirements

Builder VMs require specific kernel and init script features:

Cgroups

The init script mounts cgroups for BuildKit/runc:

# Cgroup v2 (preferred)
mount -t cgroup2 none /sys/fs/cgroup

# Or cgroup v1 fallback
mount -t tmpfs cgroup /sys/fs/cgroup
for ctrl in cpu cpuacct memory devices freezer blkio pids; do
  mkdir -p /sys/fs/cgroup/$ctrl
  mount -t cgroup -o $ctrl cgroup /sys/fs/cgroup/$ctrl
done
Volume Mounts

Two volumes are attached to builder VMs:

  1. Source volume (/src, read-write): Contains extracted source tarball
  2. Config volume (/config, read-only): Contains build.json

The source is mounted read-write so the generated Dockerfile can be written.

Provenance

Each build records provenance for reproducibility:

{
  "base_image_digest": "sha256:abc123...",
  "source_hash": "sha256:def456...",
  "lockfile_hashes": {
    "package-lock.json": "sha256:..."
  },
  "toolchain_version": "v20.10.0",
  "buildkit_version": "v0.12.0",
  "timestamp": "2025-01-15T10:05:00Z"
}

Testing

Unit Tests
# Run unit tests
go test ./lib/builds/... -v

# Test specific components
go test ./lib/builds/queue_test.go ./lib/builds/queue.go ./lib/builds/types.go -v
go test ./lib/builds/cache_test.go ./lib/builds/cache.go ./lib/builds/types.go ./lib/builds/errors.go -v
go test ./lib/builds/registry_token_test.go ./lib/builds/registry_token.go -v
E2E Testing
  1. Start the server:

    make dev
    
  2. Ensure builder image is available:

    TOKEN=$(make gen-jwt | tail -1)
    curl -X POST http://localhost:8083/images \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"name": "hirokernel/builder-generic:latest"}'
    
  3. Create test source with Dockerfile:

    mkdir -p /tmp/test-app
    echo '{"name": "test", "version": "1.0.0", "dependencies": {}}' > /tmp/test-app/package.json
    echo 'console.log("Hello!");' > /tmp/test-app/index.js
    cat > /tmp/test-app/Dockerfile << 'EOF'
    FROM node:20-alpine
    WORKDIR /app
    COPY package.json index.js ./
    CMD ["node", "index.js"]
    EOF
    tar -czf /tmp/source.tar.gz -C /tmp/test-app .
    
  4. Submit build:

    curl -X POST http://localhost:8083/builds \
      -H "Authorization: Bearer $TOKEN" \
      -F "source=@/tmp/source.tar.gz"
    
  5. Poll for completion:

    BUILD_ID="<id-from-response>"
    curl http://localhost:8083/builds/$BUILD_ID \
      -H "Authorization: Bearer $TOKEN"
    
  6. Run the built image:

    curl -X POST http://localhost:8083/instances \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "test-app",
        "image": "builds/'$BUILD_ID':latest",
        "size": "1GB",
        "vcpus": 1
      }'
    

Troubleshooting

Common Issues
Error Cause Solution
image not found Builder image not imported Import image using POST /images endpoint
no cgroup mount found Cgroups not mounted in VM Update init script to mount cgroups
http: server gave HTTP response to HTTPS client BuildKit using HTTPS for HTTP registry Add registry.insecure=true to output flags
connection refused to localhost:8080 Registry URL not accessible from VM Use gateway IP (10.102.0.1) instead of localhost
401 Unauthorized Registry auth issue Check registry_token in config.json; verify middleware handles Basic auth
No space left on device Instance memory too small for image Use at least 1GB RAM for Node.js images
can't enable NoProcessSandbox without Rootless Wrong BUILDKITD_FLAGS Use empty flags or remove the flag
Debug Builder VM

Check logs of the builder instance:

# List instances
curl http://localhost:8083/instances -H "Authorization: Bearer $TOKEN" | jq

# Get builder instance logs
INSTANCE_ID="<builder-instance-id>"
curl http://localhost:8083/instances/$INSTANCE_ID/logs \
  -H "Authorization: Bearer $TOKEN"
Verify Build Config

Check the config volume contents:

cat $DATA_DIR/builds/$BUILD_ID/config.json

Expected format:

{
  "job_id": "abc123",
  "registry_url": "10.102.0.1:8083",
  "registry_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "cache_scope": "my-tenant",
  "source_path": "/src",
  "dockerfile": "FROM node:20-alpine\nWORKDIR /app\n...",
  "timeout_seconds": 300,
  "network_mode": "egress"
}

Note: registry_token is a short-lived JWT granting push access to builds/abc123 and cache/my-tenant.

Documentation

Overview

Package builds implements registry token generation for secure builder VM authentication.

Package builds implements a secure build system that runs rootless BuildKit inside ephemeral Cloud Hypervisor microVMs for multi-tenant isolation.

Index

Constants

View Source
const (
	StatusQueued    = "queued"
	StatusBuilding  = "building"
	StatusPushing   = "pushing"
	StatusReady     = "ready"
	StatusFailed    = "failed"
	StatusCancelled = "cancelled"
)

Build status constants

View Source
const (
	EventTypeLog       = "log"
	EventTypeStatus    = "status"
	EventTypeHeartbeat = "heartbeat"
)

BuildEvent type constants

View Source
const (
	// BuildAgentVsockPort is the port the builder agent listens on inside the guest
	BuildAgentVsockPort = 5001

	// SecretsVsockPort is the port the host listens on for secret requests from builder agents
	SecretsVsockPort = 5002
)

Variables

View Source
var (
	// ErrNotFound is returned when a build is not found
	ErrNotFound = errors.New("build not found")

	// ErrAlreadyExists is returned when a build with the same ID already exists
	ErrAlreadyExists = errors.New("build already exists")

	// ErrDockerfileRequired is returned when no Dockerfile is provided
	ErrDockerfileRequired = errors.New("dockerfile required: provide dockerfile parameter or include Dockerfile in source tarball")

	// ErrBuildFailed is returned when a build fails
	ErrBuildFailed = errors.New("build failed")

	// ErrBuildTimeout is returned when a build exceeds its timeout
	ErrBuildTimeout = errors.New("build timeout")

	// ErrBuildCancelled is returned when a build is cancelled
	ErrBuildCancelled = errors.New("build cancelled")

	// ErrInvalidSource is returned when the source tarball is invalid
	ErrInvalidSource = errors.New("invalid source")

	// ErrSourceHashMismatch is returned when the source hash doesn't match
	ErrSourceHashMismatch = errors.New("source hash mismatch")

	// ErrBuilderNotReady is returned when the builder image is not available
	ErrBuilderNotReady = errors.New("builder image not ready")

	// ErrBuildInProgress is returned when trying to cancel a build that's already complete
	ErrBuildInProgress = errors.New("build in progress")
)

Functions

func GetCacheKeyFromConfig

func GetCacheKeyFromConfig(registryURL, cacheScope, runtime string, lockfileHashes map[string]string) (importArg, exportArg string, err error)

GetCacheKeyFromConfig extracts cache configuration for the builder agent

func ValidateCacheScope

func ValidateCacheScope(scope string) error

ValidateCacheScope validates that a cache scope is safe to use

Types

type Build

type Build struct {
	ID            string           `json:"id"`
	Status        string           `json:"status"`
	QueuePosition *int             `json:"queue_position,omitempty"`
	ImageDigest   *string          `json:"image_digest,omitempty"`
	ImageRef      *string          `json:"image_ref,omitempty"`
	Error         *string          `json:"error,omitempty"`
	Provenance    *BuildProvenance `json:"provenance,omitempty"`
	CreatedAt     time.Time        `json:"created_at"`
	StartedAt     *time.Time       `json:"started_at,omitempty"`
	CompletedAt   *time.Time       `json:"completed_at,omitempty"`
	DurationMS    *int64           `json:"duration_ms,omitempty"`
}

Build represents a source-to-image build job

type BuildConfig

type BuildConfig struct {
	// JobID is the build job identifier
	JobID string `json:"job_id"`

	// Dockerfile content (if not provided in source tarball)
	Dockerfile string `json:"dockerfile,omitempty"`

	// BaseImageDigest optionally pins the base image
	BaseImageDigest string `json:"base_image_digest,omitempty"`

	// RegistryURL is where to push the built image
	RegistryURL string `json:"registry_url"`

	// RegistryToken is a short-lived JWT granting push access to specific repositories.
	// The builder agent uses this token to authenticate with the registry.
	RegistryToken string `json:"registry_token,omitempty"`

	// CacheScope is the tenant-specific cache key prefix
	CacheScope string `json:"cache_scope,omitempty"`

	// SourcePath is the path to source in the guest (typically /src)
	SourcePath string `json:"source_path"`

	// BuildArgs are ARG values for the Dockerfile
	BuildArgs map[string]string `json:"build_args,omitempty"`

	// Secrets are secret references to fetch from host
	Secrets []SecretRef `json:"secrets,omitempty"`

	// TimeoutSeconds is the build timeout
	TimeoutSeconds int `json:"timeout_seconds"`

	// NetworkMode is "isolated" or "egress"
	NetworkMode string `json:"network_mode"`
}

BuildConfig is the configuration passed to the builder VM via config disk This is read by the builder agent inside the guest

type BuildEvent

type BuildEvent struct {
	// Type is one of "log", "status", or "heartbeat"
	Type string `json:"type"`

	// Timestamp is when the event occurred
	Timestamp time.Time `json:"timestamp"`

	// Content is the log line content (only for type="log")
	Content string `json:"content,omitempty"`

	// Status is the new build status (only for type="status")
	Status string `json:"status,omitempty"`
}

BuildEvent represents a typed SSE event for build streaming

type BuildPolicy

type BuildPolicy struct {
	// TimeoutSeconds is the maximum build duration (default: 600)
	TimeoutSeconds int `json:"timeout_seconds,omitempty"`

	// MemoryMB is the memory limit for the builder VM (default: 2048)
	MemoryMB int `json:"memory_mb,omitempty"`

	// CPUs is the number of vCPUs for the builder VM (default: 2)
	CPUs int `json:"cpus,omitempty"`

	// NetworkMode controls network access during build
	// "isolated" = no network, "egress" = outbound allowed
	NetworkMode string `json:"network_mode,omitempty"`

	// AllowedDomains restricts egress to specific domains (only when NetworkMode="egress")
	AllowedDomains []string `json:"allowed_domains,omitempty"`
}

BuildPolicy defines resource limits and network policy for a build

func DefaultBuildPolicy

func DefaultBuildPolicy() BuildPolicy

DefaultBuildPolicy returns the default build policy

func (*BuildPolicy) ApplyDefaults

func (p *BuildPolicy) ApplyDefaults()

ApplyDefaults fills in default values for a build policy

type BuildProvenance

type BuildProvenance struct {
	// BaseImageDigest is the pinned base image used
	BaseImageDigest string `json:"base_image_digest"`

	// SourceHash is the SHA256 of the source tarball
	SourceHash string `json:"source_hash"`

	// LockfileHashes maps lockfile names to their SHA256 hashes
	LockfileHashes map[string]string `json:"lockfile_hashes,omitempty"`

	// BuildkitVersion is the BuildKit version used
	BuildkitVersion string `json:"buildkit_version,omitempty"`

	// Timestamp is when the build completed
	Timestamp time.Time `json:"timestamp"`
}

BuildProvenance records the inputs and toolchain used for a build This enables reproducibility verification and audit trails

type BuildQueue

type BuildQueue struct {
	// contains filtered or unexported fields
}

BuildQueue manages concurrent builds with a configurable limit. Following the pattern from lib/images/queue.go.

Design notes (see plan for full context): - Queue state is in-memory (lost on restart) - Build metadata is persisted to disk - On startup, pending builds are recovered via listPendingBuilds()

Future migration path if needed: - Add BuildQueue interface with Enqueue/Dequeue/Ack/Nack - Implement adapters: memoryQueue, redisQueue, natsQueue - Use BUILD_QUEUE_BACKEND env var to select implementation

func NewBuildQueue

func NewBuildQueue(maxConcurrent int) *BuildQueue

NewBuildQueue creates a new build queue with the given concurrency limit

func (*BuildQueue) ActiveCount

func (q *BuildQueue) ActiveCount() int

ActiveCount returns the number of actively building builds

func (*BuildQueue) Cancel

func (q *BuildQueue) Cancel(buildID string) bool

Cancel removes a build from the pending queue. Returns true if the build was cancelled, false if it was not in the queue (already running or not found).

func (*BuildQueue) Enqueue

func (q *BuildQueue) Enqueue(buildID string, req CreateBuildRequest, startFn func()) int

Enqueue adds a build to the queue. Returns queue position (0 if started immediately, >0 if queued). If the build is already building or queued, returns its current position without re-enqueueing.

func (*BuildQueue) GetPosition

func (q *BuildQueue) GetPosition(buildID string) *int

GetPosition returns the queue position for a build. Returns nil if the build is actively running or not in queue.

func (*BuildQueue) IsActive

func (q *BuildQueue) IsActive(buildID string) bool

IsActive returns true if the build is actively running

func (*BuildQueue) MarkComplete

func (q *BuildQueue) MarkComplete(buildID string)

MarkComplete marks a build as complete and starts the next pending build if any

func (*BuildQueue) PendingCount

func (q *BuildQueue) PendingCount() int

PendingCount returns the number of queued builds

func (*BuildQueue) QueueLength

func (q *BuildQueue) QueueLength() int

QueueLength returns the total number of builds (active + pending)

type BuildResult

type BuildResult struct {
	// Success indicates whether the build succeeded
	Success bool `json:"success"`

	// ImageDigest is the digest of the pushed image (only on success)
	ImageDigest string `json:"image_digest,omitempty"`

	// Error is the error message (only on failure)
	Error string `json:"error,omitempty"`

	// Logs is the full build log output
	Logs string `json:"logs,omitempty"`

	// Provenance records build inputs for reproducibility
	Provenance BuildProvenance `json:"provenance"`

	// DurationMS is the build duration in milliseconds
	DurationMS int64 `json:"duration_ms"`
}

BuildResult is returned by the builder agent after a build completes

type CacheKey

type CacheKey struct {
	// Full reference for BuildKit --import-cache / --export-cache
	Reference string

	// Components
	TenantScope  string
	Runtime      string
	LockfileHash string
}

CacheKey represents a validated cache key

func (*CacheKey) ExportCacheArg

func (k *CacheKey) ExportCacheArg() string

ExportCacheArg returns the BuildKit --export-cache argument

func (*CacheKey) ImportCacheArg

func (k *CacheKey) ImportCacheArg() string

ImportCacheArg returns the BuildKit --import-cache argument

type CacheKeyGenerator

type CacheKeyGenerator struct {
	// contains filtered or unexported fields
}

CacheKeyGenerator generates cache keys for builds with tenant isolation

func NewCacheKeyGenerator

func NewCacheKeyGenerator(registryURL string) *CacheKeyGenerator

NewCacheKeyGenerator creates a new cache key generator

func (*CacheKeyGenerator) GenerateCacheKey

func (g *CacheKeyGenerator) GenerateCacheKey(tenantScope, runtime string, lockfileHashes map[string]string) (*CacheKey, error)

GenerateCacheKey generates a cache key for a build.

Cache key structure:

{registry}/cache/{tenant_scope}/{runtime}/{lockfile_hash}

This structure provides: - Tenant isolation: each tenant's cache is isolated by scope - Runtime separation: Node.js and Python caches don't mix - Lockfile-based keying: same lockfile = cache hit

type Config

type Config struct {
	// MaxConcurrentBuilds is the maximum number of concurrent builds
	MaxConcurrentBuilds int

	// BuilderImage is the OCI image to use for builder VMs
	// This should contain rootless BuildKit and the builder agent
	BuilderImage string

	// RegistryURL is the URL of the registry to push built images to
	RegistryURL string

	// DefaultTimeout is the default build timeout in seconds
	DefaultTimeout int

	// RegistrySecret is the secret used to sign registry access tokens
	// This should be the same secret used by the registry middleware
	RegistrySecret string
}

Config holds configuration for the build manager

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the default build manager configuration

type CreateBuildRequest

type CreateBuildRequest struct {
	// Dockerfile content. Required if not included in the source tarball.
	// The Dockerfile specifies the runtime (e.g., FROM node:20-alpine).
	Dockerfile string `json:"dockerfile,omitempty"`

	// BaseImageDigest optionally pins the base image by digest for reproducibility
	BaseImageDigest string `json:"base_image_digest,omitempty"`

	// SourceHash is the SHA256 hash of the source tarball for verification
	SourceHash string `json:"source_hash,omitempty"`

	// BuildPolicy contains resource limits and network policy for the build
	BuildPolicy *BuildPolicy `json:"build_policy,omitempty"`

	// CacheScope is the tenant-specific cache key prefix for isolation
	CacheScope string `json:"cache_scope,omitempty"`

	// BuildArgs are ARG values to pass to the Dockerfile
	BuildArgs map[string]string `json:"build_args,omitempty"`

	// Secrets are secret references to inject during build
	Secrets []SecretRef `json:"secrets,omitempty"`
}

CreateBuildRequest represents a request to create a new build

type FileSecretProvider

type FileSecretProvider struct {
	// contains filtered or unexported fields
}

FileSecretProvider reads secrets from files in a directory. Each secret is stored as a file named by its ID, with the secret value as the file content. Example: /etc/hypeman/secrets/npm_token contains the npm token value.

func NewFileSecretProvider

func NewFileSecretProvider(secretsDir string) *FileSecretProvider

NewFileSecretProvider creates a new file-based secret provider. secretsDir is the directory containing secret files (e.g., /etc/hypeman/secrets/).

func (*FileSecretProvider) GetSecrets

func (p *FileSecretProvider) GetSecrets(ctx context.Context, secretIDs []string) (map[string]string, error)

GetSecrets returns the values for the given secret IDs by reading files from the secrets directory. Missing secrets are silently skipped (not an error). Returns an error only if a secret file exists but cannot be read.

type Manager

type Manager interface {
	// Start starts the build manager's background services (vsock handler, etc.)
	// This should be called once when the API server starts.
	Start(ctx context.Context) error

	// CreateBuild starts a new build job
	CreateBuild(ctx context.Context, req CreateBuildRequest, sourceData []byte) (*Build, error)

	// GetBuild returns a build by ID
	GetBuild(ctx context.Context, id string) (*Build, error)

	// ListBuilds returns all builds
	ListBuilds(ctx context.Context) ([]*Build, error)

	// CancelBuild cancels a pending or running build
	CancelBuild(ctx context.Context, id string) error

	// GetBuildLogs returns the logs for a build
	GetBuildLogs(ctx context.Context, id string) ([]byte, error)

	// StreamBuildEvents streams build events (logs, status changes, heartbeats)
	// With follow=false, returns existing logs then closes
	// With follow=true, continues streaming until build completes or context cancels
	StreamBuildEvents(ctx context.Context, id string, follow bool) (<-chan BuildEvent, error)

	// RecoverPendingBuilds recovers builds that were interrupted on restart
	RecoverPendingBuilds()
}

Manager interface for the build system

func NewManager

func NewManager(
	p *paths.Paths,
	config Config,
	instanceMgr instances.Manager,
	volumeMgr volumes.Manager,
	secretProvider SecretProvider,
	logger *slog.Logger,
	meter metric.Meter,
) (Manager, error)

NewManager creates a new build manager

type Metrics

type Metrics struct {
	// contains filtered or unexported fields
}

Metrics provides Prometheus metrics for the build system

func NewMetrics

func NewMetrics(meter metric.Meter) (*Metrics, error)

NewMetrics creates a new Metrics instance

func (*Metrics) RecordBuild

func (m *Metrics) RecordBuild(ctx context.Context, status string, duration time.Duration)

RecordBuild records metrics for a completed build

func (*Metrics) RegisterQueueCallbacks

func (m *Metrics) RegisterQueueCallbacks(queue *BuildQueue, meter metric.Meter) error

RegisterQueueCallbacks registers callbacks for queue metrics

type NoOpSecretProvider

type NoOpSecretProvider struct{}

NoOpSecretProvider returns empty secrets (for builds without secrets)

func (*NoOpSecretProvider) GetSecrets

func (p *NoOpSecretProvider) GetSecrets(ctx context.Context, secretIDs []string) (map[string]string, error)

type QueuedBuild

type QueuedBuild struct {
	BuildID string
	Request CreateBuildRequest
	StartFn func()
}

QueuedBuild represents a build waiting to be executed

type RegistryTokenClaims

type RegistryTokenClaims struct {
	jwt.RegisteredClaims

	// BuildID is the build job identifier for audit purposes
	BuildID string `json:"build_id"`

	// Repositories is the list of allowed repository paths (e.g., ["builds/abc123", "cache/tenant-x"])
	Repositories []string `json:"repos"`

	// Scope is the access scope: "push" for write access, "pull" for read-only
	Scope string `json:"scope"`
}

RegistryTokenClaims contains the claims for a scoped registry access token. These tokens are issued to builder VMs to grant limited push access to specific repositories.

func (*RegistryTokenClaims) IsPullAllowed

func (c *RegistryTokenClaims) IsPullAllowed() bool

IsPullAllowed returns true if the token grants pull (read) access. Push tokens also implicitly grant pull access.

func (*RegistryTokenClaims) IsPushAllowed

func (c *RegistryTokenClaims) IsPushAllowed() bool

IsPushAllowed returns true if the token grants push (write) access.

func (*RegistryTokenClaims) IsRepositoryAllowed

func (c *RegistryTokenClaims) IsRepositoryAllowed(repo string) bool

IsRepositoryAllowed checks if the given repository path is allowed by the token claims.

type RegistryTokenGenerator

type RegistryTokenGenerator struct {
	// contains filtered or unexported fields
}

RegistryTokenGenerator creates scoped registry access tokens

func NewRegistryTokenGenerator

func NewRegistryTokenGenerator(secret string) *RegistryTokenGenerator

NewRegistryTokenGenerator creates a new token generator with the given secret

func (*RegistryTokenGenerator) GeneratePushToken

func (g *RegistryTokenGenerator) GeneratePushToken(buildID string, repos []string, ttl time.Duration) (string, error)

GeneratePushToken creates a short-lived token granting push access to specific repositories. The token expires after the specified duration (typically matching the build timeout).

func (*RegistryTokenGenerator) ValidateToken

func (g *RegistryTokenGenerator) ValidateToken(tokenString string) (*RegistryTokenClaims, error)

ValidateToken parses and validates a registry token, returning the claims if valid.

type SecretProvider

type SecretProvider interface {
	// GetSecrets returns the values for the given secret IDs
	GetSecrets(ctx context.Context, secretIDs []string) (map[string]string, error)
}

SecretProvider provides secrets for builds

type SecretRef

type SecretRef struct {
	// ID is the secret identifier (used in --mount=type=secret,id=...)
	ID string `json:"id"`

	// EnvVar is the environment variable name to expose the secret as
	EnvVar string `json:"env_var,omitempty"`
}

SecretRef references a secret to inject during build

type SecretsRequest

type SecretsRequest struct {
	SecretIDs []string `json:"secret_ids"`
}

SecretsRequest is sent by the builder agent to fetch secrets

type SecretsResponse

type SecretsResponse struct {
	Secrets map[string]string `json:"secrets"`
}

SecretsResponse contains the requested secrets

type VsockMessage

type VsockMessage struct {
	Type      string            `json:"type"`
	Result    *BuildResult      `json:"result,omitempty"`
	Log       string            `json:"log,omitempty"`
	SecretIDs []string          `json:"secret_ids,omitempty"` // For secrets request
	Secrets   map[string]string `json:"secrets,omitempty"`    // For secrets response
}

VsockMessage is the envelope for vsock communication with builder agents

Directories

Path Synopsis
Package main implements the builder agent that runs inside builder microVMs.
Package main implements the builder agent that runs inside builder microVMs.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL