chamber

module
v0.1.0-beta.8 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2026 License: AGPL-3.0

README

Chamber

Chamber is a Go SDK for point-and-shoot OCI container execution. The SDK under pkg/ can pull an image into a caller-owned root, provision an OCI runtime bundle, run that bundle with runc, and read runtime logs. These primitives are intended to be a foundation for derived products such as a container daemon, workflow orchestration, and container orchestration layers.

The current repo is experimental and interfaces are volatile.

The daemon code in this repository is still experimental and is not part of the SDK contract.

SDK Scope

  • pkg/image: public image contracts and config; pkg/image/factory creates image pullers that store OCI image layouts under a caller-provided root.
  • pkg/bundle: public bundle contracts and config; pkg/bundle/factory creates bundle provisioners. The current internal implementation, directory, unpacks an OCI layout into a rootless OCI runtime bundle.
  • pkg/runtime: public runtime contracts and config; pkg/runtime/factory creates runtimes. The current internal implementation, runc, downloads or reuses a pinned runc binary and runs provisioned bundles.
  • pkg/shared: common error codes, filesystem policy, logging, image reference validation, container ID validation, and capability vocabulary.

SDK callers own storage placement, concurrency, cleanup, cancellation policy, and recovery. Constructors return ready objects or errors; there is no separate public setup phase.

Concurrency Warning

The SDK is not currently focused on thread-safe shared use. It does not provide automatic locking, operation records, leases, or cross-process coordination for shared roots and container IDs.

Callers that use Chamber from multiple goroutines or processes must provide their own concurrency management. In practice, that means serializing or locking access to shared image roots, bundle roots, runtime roots, container IDs, log paths, cleanup, and cancellation decisions.

Experimental Daemon

The daemon/ package contains the current daemon prototype. It is the first in-repo composition layer built from the public SDK packages rather than a separate container engine.

Today the daemon:

  • loads daemon config from defaults, a JSON config file, and command-line overrides;
  • composes the image puller, bundle provisioner, runtime, and metadata store;
  • exposes an HTTP API for health checks, OpenAPI docs, image pull, container run, container list, and stored container logs;
  • records image, operation, and container metadata under daemon-owned storage;
  • runs with go run ./daemon -http-addr 127.0.0.1:8080;
  • provides go run ./daemon storage remove --yes for deleting the derived Chamber storage root.

The daemon is still a prototype. It shows how a local authority can own metadata, operation state, runtime composition, and API responses, but it should not yet be treated as a stable production daemon with complete recovery, lease-aware garbage collection, cancellation, or multi-client coordination.

Cleanup Contract

The SDK does not provide all-in-one container cleanup. Callers are responsible for cleaning up the storage they asked each package to create.

For one container run, callers should:

  1. Call Container.Wait for every successful Runtime.Run call. This reaps the runc run process and closes Chamber-owned stdout/stderr log file handles.
  2. Call Container.Delete with force set to true when runtime state may still exist or the container may still be running. This delegates to runc delete --force.
  3. Remove ProvisionedBundle.BundlePath when the unpacked bundle is no longer needed.
  4. Call Container.DeleteLog for default stdout/stderr logs when they are no longer needed, or remove <RuntimeRoot>/logs/<containerID> directly.
  5. Decide separately when to remove shared image layouts and the cached runtime binary.

If a process crashes or a caller skips these steps, per-container runtime state, bundle directories, logs, or temporary files may remain in the caller-provided roots.

The context passed to Runtime.Run controls launch work only. After Run returns a Container, that container owns lifecycle operations; use Container.Signal, Container.Delete, and Container.Wait to stop, remove, and observe it.

Requirements

  • Go 1.26.4 or newer compatible with this module.
  • A Linux host or Linux VM for runtime execution.
  • Rootless container support for the current directory bundle provisioner and runc runtime.
  • Non-interactive processes. The current runc runtime does not allocate PTYs, so bundles with process.terminal=true are rejected. Set ProcessSpec.Terminal to false when running images that default to a terminal process.
  • The current rootless bundle provisioner maps only container UID/GID 0 to the current host user and group. Images or ProcessUser overrides that require unmapped UIDs or GIDs are rejected during bundle provisioning.
  • Network access when pulling images or when the pinned runc binary is not already present in the configured runtime binary directory.

Minimal SDK Flow

package main

import (
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"

	chamberBundle "github.com/donglin-wang/chamber/pkg/bundle"
	chamberBundleFactory "github.com/donglin-wang/chamber/pkg/bundle/factory"
	chamberImage "github.com/donglin-wang/chamber/pkg/image"
	chamberImageFactory "github.com/donglin-wang/chamber/pkg/image/factory"
	chamberRuntime "github.com/donglin-wang/chamber/pkg/runtime"
	chamberRuntimeFactory "github.com/donglin-wang/chamber/pkg/runtime/factory"
	"github.com/donglin-wang/chamber/pkg/shared/capability"
	"github.com/donglin-wang/chamber/pkg/shared/hostfs"
)

func main() {
	ctx := context.Background()
	root := "/tmp/chamber-sdk-demo"

	imageWorkspace, err := hostfs.NewWorkspace(hostfs.Config{
		Root:    filepath.Join(root, "images"),
		TmpRoot: filepath.Join(root, "tmp", "images"),
		Capabilities: hostfs.Capabilities{
			PrivateDirs:           true,
			FileFsync:             true,
			AtomicFileRename:      true,
			AtomicDirectoryRename: true,
		},
	})
	if err != nil {
		panic(err)
	}
	bundleWorkspace, err := hostfs.NewWorkspace(hostfs.Config{
		Root:    filepath.Join(root, "bundles"),
		TmpRoot: filepath.Join(root, "tmp", "bundles"),
		Capabilities: hostfs.Capabilities{
			PrivateDirs:           true,
			AtomicDirectoryRename: true,
		},
	})
	if err != nil {
		panic(err)
	}
	runtimeWorkspace, err := hostfs.NewWorkspace(hostfs.Config{
		Root:    filepath.Join(root, "run", "runtime"),
		TmpRoot: filepath.Join(root, "tmp", "runtime"),
		Capabilities: hostfs.Capabilities{
			PrivateDirs:      true,
			FileFsync:        true,
			AtomicFileRename: true,
		},
	})
	if err != nil {
		panic(err)
	}
	runtimeBinaryWorkspace, err := hostfs.NewWorkspace(hostfs.Config{
		Root:    filepath.Join(root, "bin"),
		TmpRoot: filepath.Join(root, "tmp", "runtime-bin"),
		Capabilities: hostfs.Capabilities{
			PrivateDirs:      true,
			FileFsync:        true,
			AtomicFileRename: true,
		},
	})
	if err != nil {
		panic(err)
	}

	imageStore, err := chamberImageFactory.NewStore(chamberImage.Config{
		Root: imageWorkspace.Root(),
	}, imageWorkspace)
	if err != nil {
		panic(err)
	}
	image, err := imageStore.Pull(ctx, chamberImage.PullRequest{
		Reference: "docker.io/library/alpine:latest",
	})
	if err != nil {
		panic(err)
	}
	imageLayout, err := imageStore.Layout(ctx)
	if err != nil {
		panic(err)
	}

	provisioner, err := chamberBundleFactory.NewProvisioner(chamberBundle.Config{
		Root:      bundleWorkspace.Root(),
		Name:      chamberBundle.ProvisionerNameDirectory,
		Privilege: capability.Rootless,
	}, bundleWorkspace)
	if err != nil {
		panic(err)
	}
	terminal := false
	provisioned, err := provisioner.Provision(ctx, chamberBundle.ProvisionRequest{
		ContainerID:   "demo",
		ImageLayout:   imageLayout,
		ImageRef:      image.Reference,
		ImageDigest:   image.Digest,
		ImagePlatform: image.Platform,
		Process: chamberBundle.ProcessSpec{
			Args:     []string{"/bin/sh", "-c", "echo hello from chamber"},
			Terminal: &terminal,
		},
	})
	if err != nil {
		panic(err)
	}

	runc, err := chamberRuntimeFactory.NewRuntime(ctx, chamberRuntime.Config{
		RuntimeRoot:   runtimeWorkspace.Root(),
		RuntimeBinDir: runtimeBinaryWorkspace.Root(),
		Name:          chamberRuntime.RuntimeNameRunc,
		Privilege:     capability.Rootless,
	}, runtimeWorkspace, runtimeBinaryWorkspace)
	if err != nil {
		panic(err)
	}
	container, err := runc.Run(ctx, chamberRuntime.RunRequest{
		Bundle: provisioned,
		Stdout: []io.Writer{os.Stdout},
		Stderr: []io.Writer{os.Stderr},
	})
	if err != nil {
		panic(err)
	}
	defer func() {
		_ = container.Delete(context.Background(), true)
		_ = container.DeleteLog(chamberRuntime.StdoutLogStream)
		_ = container.DeleteLog(chamberRuntime.StderrLogStream)
		_ = os.RemoveAll(provisioned.BundlePath)
	}()
	result, err := container.Wait()
	if err != nil {
		panic(err)
	}
	stdout, err := container.ReadLog(chamberRuntime.StdoutLogStream)
	if err != nil {
		panic(err)
	}
	fmt.Printf("exit=%d stdout=%s", result.ExitCode, stdout)
}

image.Store.Build uses an ephemeral rootless BuildKit daemon for Dockerfile builds. Set image.BuildKit.BuildctlPath, BuildkitdPath, RootlessKitPath, and RuncPath to use local tool binaries. When paths are empty, Chamber downloads and caches managed BuildKit, RootlessKit, and runc binaries below <image-root>/bin. Per-build daemon state, runtime directories, home/config directories, temporary files, and the OCI output archive stay below <image-root>/tmp and are removed after the build.

See host-assumption-validator-plan.md for the Linux/rootless host assumptions Chamber should validate before build, provision, and run workflows.

Validation

Use an explicit Go cache in restricted macOS environments:

GOCACHE=/tmp/chamber-go-cache go test ./pkg/... ./daemon/...
GOCACHE=/tmp/chamber-go-cache go vet ./pkg/... ./daemon/...

BuildKit builds require Linux rootless host support. Validate the normal package surface with:

GOCACHE=/tmp/chamber-go-cache go test ./...

The default test suite avoids real registry pulls and real Dockerfile builds. To include integration coverage, opt in explicitly on a rootless-capable Linux host:

CHAMBER_INTEGRATION=1 GOCACHE=/tmp/chamber-go-cache go test -count=1 ./pkg/image/internal/store -run TestStoreRealWorldBusybox
CHAMBER_INTEGRATION=1 GOCACHE=/tmp/chamber-go-cache go test -count=1 ./pkg/image/internal/store -run TestStoreRealBuildKit

Directories

Path Synopsis
cmd
ci command
pkg
bundle
Package bundle defines the public SDK contracts and configuration for Chamber bundle provisioning.
Package bundle defines the public SDK contracts and configuration for Chamber bundle provisioning.
bundle/factory
Package factory constructs Chamber's built-in bundle provisioners from public bundle package configs.
Package factory constructs Chamber's built-in bundle provisioners from public bundle package configs.
bundle/internal/directory
Package directory provides Chamber's directory-backed OCI bundle provisioner.
Package directory provides Chamber's directory-backed OCI bundle provisioner.
image
Package image defines the public SDK contracts and configuration for Chamber image operations.
Package image defines the public SDK contracts and configuration for Chamber image operations.
image/factory
Package factory constructs Chamber's built-in image operation implementations from public image package configs.
Package factory constructs Chamber's built-in image operation implementations from public image package configs.
image/internal/buildkit
Package buildkit contains BuildKit-backed Dockerfile build mechanics for Chamber's image store.
Package buildkit contains BuildKit-backed Dockerfile build mechanics for Chamber's image store.
image/internal/metadata
Package metadata persists image store records for Chamber's filesystem image store.
Package metadata persists image store records for Chamber's filesystem image store.
image/internal/registry
Package registry contains remote registry pull mechanics for Chamber's image store.
Package registry contains remote registry pull mechanics for Chamber's image store.
image/internal/store
Package store provides Chamber's filesystem-backed image store.
Package store provides Chamber's filesystem-backed image store.
runtime
Package runtime defines the public SDK contracts and configuration for Chamber runtime execution.
Package runtime defines the public SDK contracts and configuration for Chamber runtime execution.
runtime/factory
Package factory constructs Chamber's built-in runtimes from public runtime package configs.
Package factory constructs Chamber's built-in runtimes from public runtime package configs.
runtime/internal/runc
Package runc provides Chamber's runc-backed runtime implementation.
Package runc provides Chamber's runc-backed runtime implementation.
shared/capability
Package capability defines shared vocabulary for Chamber SDK implementation support declarations.
Package capability defines shared vocabulary for Chamber SDK implementation support declarations.
shared/containerid
Package containerid validates container IDs accepted by Chamber SDK bundle and runtime implementations.
Package containerid validates container IDs accepted by Chamber SDK bundle and runtime implementations.
shared/errors
Package errors defines durable Chamber error codes shared by SDK packages and daemon adapters.
Package errors defines durable Chamber error codes shared by SDK packages and daemon adapters.
shared/hostfs
Package hostfs provides scoped host-filesystem workspaces for Chamber package roots and temporary roots.
Package hostfs provides scoped host-filesystem workspaces for Chamber package roots and temporary roots.
shared/logging
Package logging owns Chamber SDK host-side logging defaults.
Package logging owns Chamber SDK host-side logging defaults.
shared/testutil
Package testutil provides helpers for Chamber package tests.
Package testutil provides helpers for Chamber package tests.

Jump to

Keyboard shortcuts

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