diff --git a/.gitignore b/.gitignore index 4cabe5c..963df31 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,10 @@ coverage.out coverage.html .task/ pkg/infra/vm/initbin/waggle-init -pkg/infra/vm/runtimebin/propolis-runner +pkg/infra/vm/runtimebin/go-microvm-runner pkg/infra/vm/runtimebin/libkrun.* pkg/infra/vm/runtimebin/libkrunfw.* +pkg/infra/vm/runtimebin/sha256sums.txt pkg/infra/vm/runtimebin/VERSION pkg/infra/vm/runtimebin/LICENSE-GPL diff --git a/.ko.yaml b/.ko.yaml index 9cef3c0..c6aa6ca 100644 --- a/.ko.yaml +++ b/.ko.yaml @@ -11,7 +11,7 @@ builds: - -X main.buildDate={{.Env.CREATION_TIME}} labels: org.opencontainers.image.created: "{{.Env.CREATION_TIME}}" - org.opencontainers.image.description: "Waggle - MCP server for isolated code execution via propolis microVMs" + org.opencontainers.image.description: "Waggle - MCP server for isolated code execution via go-microvm microVMs" org.opencontainers.image.licenses: "Apache-2.0" org.opencontainers.image.revision: "{{.Env.GITHUB_SHA}}" org.opencontainers.image.source: "{{.Env.GITHUB_SERVER_URL}}/{{.Env.GITHUB_REPOSITORY}}" diff --git a/CLAUDE.md b/CLAUDE.md index 2f87400..d45351c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,11 +1,11 @@ # waggle -MCP server for isolated code execution via propolis microVMs. Go + mcp-go, Streamable HTTP transport. Module: `github.com/stacklok/waggle`. +MCP server for isolated code execution via go-microvm microVMs. Go + mcp-go, Streamable HTTP transport. Module: `github.com/stacklok/waggle`. ## Commands ```bash -task build # Build self-contained binary with embedded propolis runtime +task build # Build self-contained binary with embedded go-microvm runtime task test # go test -v -race ./... task lint # golangci-lint run ./... task verify # fmt + lint + test (CI gate) @@ -20,19 +20,19 @@ Run a single test: `go test -v -race -run TestName ./pkg/path/to/package` - `pkg/domain/` — Pure types and interfaces, no external deps. `environment/` is the aggregate root with a state machine (Creating→Running→Destroying→Destroyed|Error) - `pkg/service/` — Orchestration: `EnvironmentService`, `ExecutionService`, `FilesystemService` -- `pkg/infra/` — Adapters: `vm/` (propolis), `ssh/` (executor+filesystem), `store/` (in-memory repo) +- `pkg/infra/` — Adapters: `vm/` (go-microvm), `ssh/` (executor+filesystem), `store/` (in-memory repo) - `pkg/mcp/` — 8 MCP tool definitions + handlers + server assembly - `pkg/cleanup/` — Background reaper for expired environments - Entry point: `cmd/waggle/main.go` wires everything with DI, handles signals ## Things That Will Bite You -- **propolis is a tagged dependency (v0.0.16)**: `task build` embeds propolis-runner, libkrun, and libkrunfw into the binary (downloaded via `task fetch-runtime`/`task fetch-firmware`, verified with sha256sums). Use `build-dev-system` for the system libkrun-devel path. +- **go-microvm is a tagged dependency (v0.0.22)**: `task build` embeds go-microvm-runner, libkrun, and libkrunfw into the binary (downloaded via `task fetch-runtime`/`task fetch-firmware`, verified with sha256sums). Use `build-dev-system` for the system libkrun-devel path. - **Image cache and log level**: `WAGGLE_IMAGE_CACHE_DIR` (default `~/.cache/waggle/images/`) enables shared OCI image cache with layer-level caching and COW rootfs cloning. `WAGGLE_IMAGE_CACHE_MAX_AGE` (default `168h`/7d) controls eviction. `WAGGLE_LOG_LEVEL` (0-5, default 0) sets libkrun verbosity; levels > 3 leak hypervisor internals. - **Layered images**: Runtime images (python/node/shell) inherit from `images/base/` via `ARG BASE_IMAGE`. Build base first: `task build-image-base`. All runtime image tasks depend on it automatically. - **MCP error handling has two paths**: Return `mcp.NewToolResultError("msg"), nil` for user-facing errors (bad input, not found). Return `nil, err` only for internal server failures. Mixing these up breaks the MCP protocol. - **Code execution uses temp files, not `-c`**: Multi-line code is written to `/tmp/waggle_.` in the VM via heredoc, executed, then cleaned up. Using `python3 -c` or `node -e` breaks on complex code. -- **Shell escaping is mandatory**: Always use `propolis/ssh.ShellEscape()` for any user-provided string passed to SSH commands. Missing this is a command injection vulnerability. +- **Shell escaping is mandatory**: Always use `go-microvm/ssh.ShellEscape()` for any user-provided string passed to SSH commands. Missing this is a command injection vulnerability. - **MemoryStore returns copies**: Both `Save()` and `FindByID()` copy the struct to prevent aliasing bugs. Mutating a returned `*Environment` does not affect the store — you must call `Save()` again. - **Port allocator verifies with net.Listen**: It doesn't just track allocations — it probes each port. Tests must call `portAlloc.SetListenCheck(func(_ uint16) error { return nil })` to skip real binding. - **SPDX headers on every file**: Every `.go` and `.yaml` file needs both `SPDX-FileCopyrightText: Copyright 2025 Stacklok, Inc.` and `SPDX-License-Identifier: Apache-2.0`. Linting will fail without them. diff --git a/README.md b/README.md index 6834ac7..d200013 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ All file operations take `environment_id` and `path`. Files persist within the e ## Why MicroVMs -Most code execution MCP servers use containers or V8 isolates. Waggle uses microVMs ([propolis](https://github.com/stacklok/propolis) + [libkrun](https://github.com/containers/libkrun)): +Most code execution MCP servers use containers or V8 isolates. Waggle uses microVMs ([go-microvm](https://github.com/stacklok/go-microvm) + [libkrun](https://github.com/containers/libkrun)): | | Containers | V8 Isolates | Waggle (microVMs) | |---|---|---|---| @@ -118,7 +118,7 @@ Most code execution MCP servers use containers or V8 isolates. Waggle uses micro The trade-off is startup time (seconds vs milliseconds), but you get a real Linux environment where `pip install numpy` and `apt-get install ffmpeg` just work. -**Ecosystem**: [ToolHive](https://github.com/stacklok/toolhive) (platform) → [Propolis](https://github.com/stacklok/propolis) (VM substrate) → **Waggle** (MCP interface) +**Ecosystem**: [ToolHive](https://github.com/stacklok/toolhive) (platform) → [go-microvm](https://github.com/stacklok/go-microvm) (VM substrate) → **Waggle** (MCP interface) ## Quick Start @@ -126,7 +126,7 @@ The trade-off is startup time (seconds vs milliseconds), but you get a real Linu - [Go](https://go.dev/dl/) 1.26+ - [Task](https://taskfile.dev/) (`go install github.com/go-task/task/v3/cmd/task@latest`) -- [GitHub CLI](https://cli.github.com/) (`gh`) — used to download the propolis runtime +- [GitHub CLI](https://cli.github.com/) (`gh`) — used to download the go-microvm runtime - KVM access on Linux (`/dev/kvm`) or Hypervisor.framework on macOS ### Build and Run @@ -134,7 +134,7 @@ The trade-off is startup time (seconds vs milliseconds), but you get a real Linu ```bash git clone https://github.com/stacklok/waggle.git cd waggle -task build # Downloads propolis runtime + firmware, builds a self-contained binary +task build # Downloads go-microvm runtime + firmware, builds a self-contained binary task run # Starts the MCP server ``` @@ -215,7 +215,7 @@ Runtime command selection order: 2) Probed commands inside the VM (if available) 3) Built-in fallbacks (`python3`, `pip install`, `node`, `npm install -g`, `sh`, `apk add --no-cache`) -No SSH server is needed — `waggle-init` is injected into the VM at boot time by propolis and handles all communication. +No SSH server is needed — `waggle-init` is injected into the VM at boot time by go-microvm and handles all communication. ## Development diff --git a/Taskfile.yaml b/Taskfile.yaml index 536cdcb..e59ccfe 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -16,8 +16,8 @@ vars: sh: git rev-parse --short HEAD 2>/dev/null || echo "unknown" BUILD_DATE: sh: date -u +"%Y-%m-%dT%H:%M:%SZ" - PROPOLIS_VERSION: - sh: go list -m github.com/stacklok/propolis | awk '{print $2}' + MICROVM_VERSION: + sh: go list -m github.com/stacklok/go-microvm | awk '{print $2}' HOST_ARCH: sh: uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/' HOST_OS: @@ -32,13 +32,13 @@ vars: -X main.version={{.VERSION}} -X main.commit={{.COMMIT}} -X main.buildDate={{.BUILD_DATE}} - -X github.com/stacklok/waggle/pkg/infra/vm/runtimebin.Version={{.PROPOLIS_VERSION}} + -X github.com/stacklok/waggle/pkg/infra/vm/runtimebin.Version={{.MICROVM_VERSION}} # Quick reference: -# task build → Build self-contained waggle with embedded propolis runtime -# task build-dev-system → Build waggle + propolis-runner from system libkrun -# task fetch-runtime → Download pre-built propolis runtime from GitHub Release -# task fetch-firmware → Download pre-built propolis firmware from GitHub Release +# task build → Build self-contained waggle with embedded go-microvm runtime +# task build-dev-system → Build waggle + go-microvm-runner from system libkrun +# task fetch-runtime → Download pre-built go-microvm runtime from GitHub Release +# task fetch-firmware → Download pre-built go-microvm firmware from GitHub Release # task test → Run tests with race detector # task lint → Run linter # task verify → fmt + lint + test @@ -50,7 +50,7 @@ tasks: - task --list build: - desc: Build self-contained waggle with embedded propolis runtime + desc: Build self-contained waggle with embedded go-microvm runtime deps: [build-init, fetch-runtime, fetch-firmware] env: CGO_ENABLED: "0" @@ -65,65 +65,65 @@ tasks: - '{{.BUILD_DIR}}/{{.BINARY_NAME}}' build-dev-system: - desc: Build waggle + propolis-runner from system libkrun (requires libkrun-devel) + desc: Build waggle + go-microvm-runner from system libkrun (requires libkrun-devel) platforms: [linux] cmds: - task: build - mkdir -p {{.BUILD_DIR}} - - CGO_ENABLED=1 go build -o {{.BUILD_DIR}}/propolis-runner github.com/stacklok/propolis/runner/cmd/propolis-runner + - CGO_ENABLED=1 go build -o {{.BUILD_DIR}}/go-microvm-runner github.com/stacklok/go-microvm/runner/cmd/go-microvm-runner build-dev-system-darwin: - desc: Build waggle + propolis-runner from system libkrun (macOS, requires Homebrew libkrun) + desc: Build waggle + go-microvm-runner from system libkrun (macOS, requires Homebrew libkrun) platforms: [darwin] cmds: - task: build - mkdir -p {{.BUILD_DIR}} - - CGO_ENABLED=1 go build -o {{.BUILD_DIR}}/propolis-runner github.com/stacklok/propolis/runner/cmd/propolis-runner - - codesign --entitlements assets/entitlements.plist --force -s - {{.BUILD_DIR}}/propolis-runner + - CGO_ENABLED=1 go build -o {{.BUILD_DIR}}/go-microvm-runner github.com/stacklok/go-microvm/runner/cmd/go-microvm-runner + - codesign --entitlements assets/entitlements.plist --force -s - {{.BUILD_DIR}}/go-microvm-runner fetch-runtime: - desc: Download pre-built propolis runtime from GitHub Release + desc: Download pre-built go-microvm runtime from GitHub Release status: - - test -f pkg/infra/vm/runtimebin/propolis-runner + - test -f pkg/infra/vm/runtimebin/go-microvm-runner cmds: - mkdir -p pkg/infra/vm/runtimebin - >- - gh release download {{.PROPOLIS_VERSION}} - --repo stacklok/propolis - --pattern "propolis-runtime-{{.HOST_OS}}-{{.HOST_ARCH}}.tar.gz" + gh release download {{.MICROVM_VERSION}} + --repo stacklok/go-microvm + --pattern "go-microvm-runtime-{{.HOST_OS}}-{{.HOST_ARCH}}.tar.gz" --dir pkg/infra/vm/runtimebin/ --clobber - >- - gh release download {{.PROPOLIS_VERSION}} - --repo stacklok/propolis + gh release download {{.MICROVM_VERSION}} + --repo stacklok/go-microvm --pattern "sha256sums.txt" --dir pkg/infra/vm/runtimebin/ --clobber - cd pkg/infra/vm/runtimebin/ && sha256sum --check --ignore-missing sha256sums.txt - >- - tar -xzf pkg/infra/vm/runtimebin/propolis-runtime-{{.HOST_OS}}-{{.HOST_ARCH}}.tar.gz + tar -xzf pkg/infra/vm/runtimebin/go-microvm-runtime-{{.HOST_OS}}-{{.HOST_ARCH}}.tar.gz -C pkg/infra/vm/runtimebin/ --strip-components=1 - - rm -f pkg/infra/vm/runtimebin/propolis-runtime-{{.HOST_OS}}-{{.HOST_ARCH}}.tar.gz + - rm -f pkg/infra/vm/runtimebin/go-microvm-runtime-{{.HOST_OS}}-{{.HOST_ARCH}}.tar.gz fetch-firmware: - desc: Download pre-built propolis firmware from GitHub Release + desc: Download pre-built go-microvm firmware from GitHub Release status: - ls pkg/infra/vm/runtimebin/libkrunfw.* >/dev/null 2>&1 cmds: - mkdir -p pkg/infra/vm/runtimebin - >- - gh release download {{.PROPOLIS_VERSION}} - --repo stacklok/propolis - --pattern "propolis-firmware-{{.HOST_OS}}-{{.HOST_ARCH}}.tar.gz" + gh release download {{.MICROVM_VERSION}} + --repo stacklok/go-microvm + --pattern "go-microvm-firmware-{{.HOST_OS}}-{{.HOST_ARCH}}.tar.gz" --dir pkg/infra/vm/runtimebin/ --clobber - >- - gh release download {{.PROPOLIS_VERSION}} - --repo stacklok/propolis + gh release download {{.MICROVM_VERSION}} + --repo stacklok/go-microvm --pattern "sha256sums.txt" --dir pkg/infra/vm/runtimebin/ --clobber - cd pkg/infra/vm/runtimebin/ && sha256sum --check --ignore-missing sha256sums.txt - >- - tar -xzf pkg/infra/vm/runtimebin/propolis-firmware-{{.HOST_OS}}-{{.HOST_ARCH}}.tar.gz + tar -xzf pkg/infra/vm/runtimebin/go-microvm-firmware-{{.HOST_OS}}-{{.HOST_ARCH}}.tar.gz -C pkg/infra/vm/runtimebin/ --strip-components=1 - - rm -f pkg/infra/vm/runtimebin/propolis-firmware-{{.HOST_OS}}-{{.HOST_ARCH}}.tar.gz + - rm -f pkg/infra/vm/runtimebin/go-microvm-firmware-{{.HOST_OS}}-{{.HOST_ARCH}}.tar.gz build-init: desc: Build the waggle-init binary (guest VM init) @@ -202,7 +202,7 @@ tasks: - rm -rf {{.BUILD_DIR}}/ - rm -f coverage.out coverage.html - rm -f pkg/infra/vm/initbin/waggle-init - - rm -f pkg/infra/vm/runtimebin/propolis-runner + - rm -f pkg/infra/vm/runtimebin/go-microvm-runner - rm -f pkg/infra/vm/runtimebin/libkrun.so.1 pkg/infra/vm/runtimebin/libkrun.1.dylib - rm -f pkg/infra/vm/runtimebin/libkrunfw.so.5 pkg/infra/vm/runtimebin/libkrunfw.5.dylib - rm -f pkg/infra/vm/runtimebin/VERSION pkg/infra/vm/runtimebin/LICENSE-GPL diff --git a/cmd/waggle-init/doc.go b/cmd/waggle-init/doc.go index 3e4eeca..16c7de5 100644 --- a/cmd/waggle-init/doc.go +++ b/cmd/waggle-init/doc.go @@ -6,5 +6,5 @@ // Package main provides the entry point for waggle-init, a minimal init // process that runs as PID 1 inside guest VMs. It starts a zombie reaper, // configures the system (mounts, network, hardening), and launches an -// embedded SSH server via propolis guest/boot. +// embedded SSH server via go-microvm guest/boot. package main diff --git a/cmd/waggle-init/main.go b/cmd/waggle-init/main.go index 9be95fb..bfb11ae 100644 --- a/cmd/waggle-init/main.go +++ b/cmd/waggle-init/main.go @@ -11,8 +11,8 @@ import ( "os/signal" "syscall" - "github.com/stacklok/propolis/guest/boot" - "github.com/stacklok/propolis/guest/reaper" + "github.com/stacklok/go-microvm/guest/boot" + "github.com/stacklok/go-microvm/guest/reaper" ) func main() { diff --git a/cmd/waggle/main.go b/cmd/waggle/main.go index c300d71..2ed14f2 100644 --- a/cmd/waggle/main.go +++ b/cmd/waggle/main.go @@ -20,7 +20,7 @@ import ( "github.com/adrg/xdg" "github.com/mark3labs/mcp-go/server" - "github.com/stacklok/propolis/image" + "github.com/stacklok/go-microvm/image" "github.com/stacklok/waggle/pkg/cleanup" "github.com/stacklok/waggle/pkg/config" @@ -114,10 +114,10 @@ func run(logFile string) error { vm.WithRuntimeSource(runtimebin.RuntimeSource()), vm.WithFirmwareSource(runtimebin.FirmwareSource()), ) - slog.Info("using embedded propolis runtime", "version", runtimebin.Version) + slog.Info("using embedded go-microvm runtime", "version", runtimebin.Version) } - provider := vm.NewPropolisProvider(providerOpts...) + provider := vm.NewMicroVMProvider(providerOpts...) portAlloc := vm.NewPortAllocator(cfg.SSHPortBase, cfg.SSHPortMax) // Create domain adapters. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index ebbbdac..2f60b75 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -4,7 +4,7 @@ This document describes Waggle's internal architecture, design decisions, and ex ## System Overview -Waggle is an MCP (Model Context Protocol) server that provides AI agents with isolated code execution environments. Each environment is a lightweight microVM powered by [propolis](https://github.com/stacklok/propolis)/[libkrun](https://github.com/containers/libkrun), giving true VM-level isolation with near-container startup times. +Waggle is an MCP (Model Context Protocol) server that provides AI agents with isolated code execution environments. Each environment is a lightweight microVM powered by [go-microvm](https://github.com/stacklok/go-microvm)/[libkrun](https://github.com/containers/libkrun), giving true VM-level isolation with near-container startup times. ``` AI Agent (Claude, etc.) @@ -28,7 +28,7 @@ Waggle is an MCP (Model Context Protocol) server that provides AI agents with is | | +-------v------+ +-------v------+ | VMProvider | | SSH | pkg/infra/ - | (Propolis) | | Executor + | vm/propolis.go, ssh/executor.go, + | (go-microvm)| | Executor + | vm/microvm.go, ssh/executor.go, | PortAlloc | | FileSystem | ssh/filesystem.go +-------+------+ +-------+------+ | | @@ -85,7 +85,7 @@ Application services orchestrate domain objects and infrastructure adapters. 1. Validate runtime, check capacity (`MaxEnvironments`) 2. Allocate SSH port from `PortAllocator` 3. Create `Environment` in `Creating` state -4. Call `VMProvider.CreateVM()` (SSH key gen, propolis.Run, SSH readiness wait) +4. Call `VMProvider.CreateVM()` (SSH key gen, microvm.Run, SSH readiness wait) 5. Transition to `Running` (or `Error` on failure, releasing the port) **ExecutionService** -- Code execution: @@ -103,8 +103,8 @@ Application services orchestrate domain objects and infrastructure adapters. Adapters implementing domain interfaces using concrete technologies. -**PropolisProvider** (`pkg/infra/vm/propolis.go`): -- Wraps `propolis.Run()` with SSH key injection and readiness wait +**MicroVMProvider** (`pkg/infra/vm/microvm.go`): +- Wraps `microvm.Run()` with SSH key injection and readiness wait - Maintains in-memory map of `envID -> vmEntry{vm, sshKeyPath}` - Uses `WithRootFSHook` to inject authorized_keys before boot - Uses `WithPostBoot` to wait for SSH via `ssh.Client.WaitForReady()` @@ -165,7 +165,7 @@ This is the critical path -- how user code goes from MCP tool call to execution f. Delegates to Executor.Execute() 4. SSHExecutor: a. Looks up environment SSH port and key path - b. Creates propolis SSH client + b. Creates go-microvm SSH client c. Calls RunStream() with separate stdout/stderr buffers d. Extracts exit code from SSH error (if non-zero) e. Returns ExecResult{Stdout, Stderr, ExitCode, DurationMs} @@ -176,8 +176,8 @@ This is the critical path -- how user code goes from MCP tool call to execution - **New runtimes**: Add to `Runtime` enum in `pkg/domain/environment/runtime.go`, add image config - **Persistent storage**: Implement `environment.Repository` backed by SQLite/Postgres -- **Network policies**: Use propolis `WithEgressPolicy()` or `WithFirewallRules()` in PropolisProvider -- **VirtioFS mounts**: Add shared host directories via `propolis.WithVirtioFS()` +- **Network policies**: Use go-microvm `WithEgressPolicy()` or `WithFirewallRules()` in MicroVMProvider +- **VirtioFS mounts**: Add shared host directories via `microvm.WithVirtioFS()` - **Custom images**: Build OCI images with pre-installed tools, configure via `WAGGLE_IMAGE_*` ## Concurrency Model @@ -185,6 +185,6 @@ This is the critical path -- how user code goes from MCP tool call to execution - MCP server handles concurrent requests via Go's HTTP goroutine model - `MemoryStore` uses `sync.RWMutex` for concurrent environment access - `PortAllocator` uses `sync.Mutex` for allocation/release -- `PropolisProvider` uses `sync.RWMutex` for VM handle map +- `MicroVMProvider` uses `sync.RWMutex` for VM handle map - Each environment is an independent VM -- no shared state between environments - Reaper runs in its own goroutine, accesses environments through the store diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 1afd402..d66af24 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -6,12 +6,12 @@ |-------------|---------| | [Go](https://go.dev/dl/) 1.26+ | Language runtime | | [Task](https://taskfile.dev/) | Build system (`go install github.com/go-task/task/v3/cmd/task@latest`) | -| [GitHub CLI](https://cli.github.com/) (`gh`) | Downloads propolis runtime from GitHub Releases | +| [GitHub CLI](https://cli.github.com/) (`gh`) | Downloads go-microvm runtime from GitHub Releases | | [golangci-lint](https://golangci-lint.run/) | Linting | | [goimports](https://pkg.go.dev/golang.org/x/tools/cmd/goimports) | Import formatting | | KVM access | Linux: ensure `/dev/kvm` is accessible to your user | -For the `build-dev-system` target (builds propolis-runner from source instead of downloading): +For the `build-dev-system` target (builds go-microvm-runner from source instead of downloading): | Requirement | Purpose | |-------------|---------| @@ -32,7 +32,7 @@ waggle/ │ │ ├── execution.go # Execute code, InstallPackages │ │ └── filesystem.go # WriteFile, ReadFile, ListFiles │ ├── infra/ # Infrastructure adapters -│ │ ├── vm/ # PropolisProvider, PortAllocator +│ │ ├── vm/ # MicroVMProvider, PortAllocator │ │ ├── ssh/ # SSHExecutor, SSHFilesystem │ │ └── store/ # InMemoryStore │ ├── mcp/ # MCP tool definitions + handlers @@ -47,7 +47,7 @@ waggle/ ## Getting Started ```bash -# Build (automatically downloads propolis runtime + firmware from GitHub Releases) +# Build (automatically downloads go-microvm runtime + firmware from GitHub Releases) task build # Run tests diff --git a/go.mod b/go.mod index ac3a4bc..0401320 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/adrg/xdg v0.5.3 github.com/google/uuid v1.6.0 github.com/mark3labs/mcp-go v0.45.0 - github.com/stacklok/propolis v0.0.20 + github.com/stacklok/go-microvm v0.0.22 ) require ( diff --git a/go.sum b/go.sum index 5fbfb5c..92a1100 100644 --- a/go.sum +++ b/go.sum @@ -132,8 +132,8 @@ github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/stacklok/propolis v0.0.20 h1:IT+WeR0R0dPeevGyspFmzX0QonbQUZArKBrBoncnOEM= -github.com/stacklok/propolis v0.0.20/go.mod h1:Sxx2/4oajARWD0LtPf1tJ4ohKHMm4aLdIuwvtCcXfc0= +github.com/stacklok/go-microvm v0.0.22 h1:8mnOqUKIVJK50ib0PUtzVmzQMe4171Kf/Hj3k64W6Nc= +github.com/stacklok/go-microvm v0.0.22/go.mod h1:0ZQq+17VGGMJEQVYsTnOouEd7+CVb98Ific7fqQHVSc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= diff --git a/pkg/config/config.go b/pkg/config/config.go index d9c0fda..b08e773 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -76,7 +76,7 @@ type Config struct { // DataDir is the directory for environment state, SSH keys, and cache. DataDir string - // RunnerPath is an optional explicit path to the propolis-runner binary. + // RunnerPath is an optional explicit path to the go-microvm-runner binary. RunnerPath string // InitPath is an optional explicit path to the waggle-init binary diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index c438562..3ede70b 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -158,7 +158,7 @@ func TestLoadFromEnv(t *testing.T) { t.Setenv("WAGGLE_SSH_PORT_BASE", "20000") t.Setenv("WAGGLE_SSH_PORT_MAX", "21000") t.Setenv("WAGGLE_DATA_DIR", "/tmp/waggle-test") - t.Setenv("WAGGLE_RUNNER_PATH", "/usr/bin/propolis-runner") + t.Setenv("WAGGLE_RUNNER_PATH", "/usr/bin/go-microvm-runner") t.Setenv("WAGGLE_INIT_PATH", "/usr/bin/waggle-init") t.Setenv("WAGGLE_LIB_DIR", "/usr/lib") t.Setenv("WAGGLE_REAPER_INTERVAL", "2m") @@ -195,8 +195,8 @@ func TestLoadFromEnv(t *testing.T) { if cfg.DataDir != "/tmp/waggle-test" { t.Errorf("DataDir = %q, want %q", cfg.DataDir, "/tmp/waggle-test") } - if cfg.RunnerPath != "/usr/bin/propolis-runner" { - t.Errorf("RunnerPath = %q, want %q", cfg.RunnerPath, "/usr/bin/propolis-runner") + if cfg.RunnerPath != "/usr/bin/go-microvm-runner" { + t.Errorf("RunnerPath = %q, want %q", cfg.RunnerPath, "/usr/bin/go-microvm-runner") } if cfg.InitPath != "/usr/bin/waggle-init" { t.Errorf("InitPath = %q, want %q", cfg.InitPath, "/usr/bin/waggle-init") diff --git a/pkg/domain/environment/environment.go b/pkg/domain/environment/environment.go index 7e019bb..5868e6d 100644 --- a/pkg/domain/environment/environment.go +++ b/pkg/domain/environment/environment.go @@ -36,7 +36,7 @@ var validTransitions = map[Status][]Status{ } // Environment is the aggregate root for an isolated code execution context. -// Each environment corresponds to a single propolis microVM. +// Each environment corresponds to a single go-microvm microVM. type Environment struct { ID string Name string diff --git a/pkg/infra/ssh/doc.go b/pkg/infra/ssh/doc.go index 6a459e9..6397ebd 100644 --- a/pkg/infra/ssh/doc.go +++ b/pkg/infra/ssh/doc.go @@ -3,5 +3,5 @@ // Package ssh provides infrastructure adapters that implement domain // interfaces (Executor, FileSystem) using SSH connections to microVMs -// via the propolis SSH client. +// via the go-microvm SSH client. package ssh diff --git a/pkg/infra/ssh/executor.go b/pkg/infra/ssh/executor.go index c497b73..d9ca609 100644 --- a/pkg/infra/ssh/executor.go +++ b/pkg/infra/ssh/executor.go @@ -12,7 +12,7 @@ import ( "time" "github.com/google/uuid" - propolisssh "github.com/stacklok/propolis/ssh" + microvmssh "github.com/stacklok/go-microvm/ssh" "github.com/stacklok/waggle/pkg/domain/execution" ) @@ -31,7 +31,7 @@ func NewExecutor() *Executor { func (e *Executor) ExecuteCode( ctx context.Context, _ string, conn execution.ConnInfo, req *execution.CodeExecution, ) (*execution.ExecResult, error) { - client := propolisssh.NewClient(conn.Host, conn.Port, "sandbox", conn.KeyPath) + client := microvmssh.NewClient(conn.Host, conn.Port, "sandbox", conn.KeyPath) timeout := time.Duration(req.TimeoutMs) * time.Millisecond @@ -40,13 +40,13 @@ func (e *Executor) ExecuteCode( // Build the temp file path and shell-escape it for safe interpolation. tempFile := fmt.Sprintf("/tmp/waggle_%s%s", uuid.New().String()[:12], req.FileExtension) - escapedTempFile := propolisssh.ShellEscape(tempFile) + escapedTempFile := microvmssh.ShellEscape(tempFile) // Write code via base64 decode into the temp file, then execute and clean up. // Using printf | base64 -d avoids heredoc quoting issues entirely. command := fmt.Sprintf( "printf '%%s' %s | base64 -d > %s; %s %s; __exit=$?; rm -f %s; exit $__exit", - propolisssh.ShellEscape(encoded), + microvmssh.ShellEscape(encoded), escapedTempFile, req.ExecCommand, escapedTempFile, @@ -61,7 +61,7 @@ func (e *Executor) ExecuteCode( func (e *Executor) InstallPackages( ctx context.Context, _ string, conn execution.ConnInfo, req *execution.PackageInstallation, ) (*execution.ExecResult, error) { - client := propolisssh.NewClient(conn.Host, conn.Port, "sandbox", conn.KeyPath) + client := microvmssh.NewClient(conn.Host, conn.Port, "sandbox", conn.KeyPath) command := buildInstallCommand(req.InstallCommand, req.Packages) return e.run(ctx, client, command, 0) } @@ -71,7 +71,7 @@ func (e *Executor) InstallPackages( func buildInstallCommand(installCmd string, packages []string) string { escapedPkgs := make([]string, len(packages)) for i, pkg := range packages { - escapedPkgs[i] = propolisssh.ShellEscape(pkg) + escapedPkgs[i] = microvmssh.ShellEscape(pkg) } return fmt.Sprintf("%s %s", installCmd, strings.Join(escapedPkgs, " ")) } @@ -79,7 +79,7 @@ func buildInstallCommand(installCmd string, packages []string) string { // run executes a command via SSH and returns the result. // If timeout is 0, no timeout is applied beyond the context deadline. func (*Executor) run( - ctx context.Context, client *propolisssh.Client, command string, timeout time.Duration, + ctx context.Context, client *microvmssh.Client, command string, timeout time.Duration, ) (*execution.ExecResult, error) { execCtx := ctx if timeout > 0 { diff --git a/pkg/infra/ssh/filesystem.go b/pkg/infra/ssh/filesystem.go index 875c00e..f82d538 100644 --- a/pkg/infra/ssh/filesystem.go +++ b/pkg/infra/ssh/filesystem.go @@ -14,7 +14,7 @@ import ( "strings" "time" - propolisssh "github.com/stacklok/propolis/ssh" + microvmssh "github.com/stacklok/go-microvm/ssh" "github.com/stacklok/waggle/pkg/domain/filesystem" ) @@ -36,12 +36,12 @@ func NewFileSystem() *FileSystem { func (*FileSystem) WriteFile( ctx context.Context, conn filesystem.ConnInfo, path string, content []byte, mode os.FileMode, ) error { - client := propolisssh.NewClient(conn.Host, conn.Port, "sandbox", conn.KeyPath) + client := microvmssh.NewClient(conn.Host, conn.Port, "sandbox", conn.KeyPath) // Create parent directory if needed. dir := parentDir(path) if dir != "" && dir != "." && dir != "/" { - mkdirCmd := fmt.Sprintf("mkdir -p %s", propolisssh.ShellEscape(dir)) + mkdirCmd := fmt.Sprintf("mkdir -p %s", microvmssh.ShellEscape(dir)) if _, mkdirErr := client.Run(ctx, mkdirCmd); mkdirErr != nil { return fmt.Errorf("mkdir for write: %w", mkdirErr) } @@ -49,14 +49,14 @@ func (*FileSystem) WriteFile( // Write content via stdin pipe to cat, then chmod. cmd := fmt.Sprintf("cat > %s && chmod %o %s", - propolisssh.ShellEscape(path), mode, propolisssh.ShellEscape(path)) + microvmssh.ShellEscape(path), mode, microvmssh.ShellEscape(path)) var stdout, stderr bytes.Buffer stdinReader := bytes.NewReader(content) // We need to use a lower-level approach since CopyTo expects a local file. // Use Run with stdin piped content. - // Actually, propolis ssh.Client doesn't expose stdin on Run, only on the + // Actually, go-microvm ssh.Client doesn't expose stdin on Run, only on the // session directly. We'll write via a temp approach: // base64 encode on host, decode on guest. _ = stdinReader // unused, we'll use a different approach @@ -93,9 +93,9 @@ func (*FileSystem) WriteFile( // ReadFile reads a file from the environment via SSH. func (*FileSystem) ReadFile(ctx context.Context, conn filesystem.ConnInfo, path string) ([]byte, error) { - client := propolisssh.NewClient(conn.Host, conn.Port, "sandbox", conn.KeyPath) + client := microvmssh.NewClient(conn.Host, conn.Port, "sandbox", conn.KeyPath) - cmd := fmt.Sprintf("cat %s", propolisssh.ShellEscape(path)) + cmd := fmt.Sprintf("cat %s", microvmssh.ShellEscape(path)) output, runErr := client.Run(ctx, cmd) if runErr != nil { return nil, fmt.Errorf("read file %s: %w", path, runErr) @@ -108,7 +108,7 @@ func (*FileSystem) ReadFile(ctx context.Context, conn filesystem.ConnInfo, path func (*FileSystem) ListFiles( ctx context.Context, conn filesystem.ConnInfo, path string, ) ([]filesystem.FileInfo, error) { - client := propolisssh.NewClient(conn.Host, conn.Port, "sandbox", conn.KeyPath) + client := microvmssh.NewClient(conn.Host, conn.Port, "sandbox", conn.KeyPath) cmd := listFilesCommand(path) output, runErr := client.Run(ctx, cmd) @@ -189,11 +189,11 @@ func listFilesCommand(path string) string { return fmt.Sprintf( template, - propolisssh.ShellEscape(encoded), - propolisssh.ShellEscape(path), - propolisssh.ShellEscape(listHeader), - propolisssh.ShellEscape(path), - propolisssh.ShellEscape(listHeader), + microvmssh.ShellEscape(encoded), + microvmssh.ShellEscape(path), + microvmssh.ShellEscape(listHeader), + microvmssh.ShellEscape(path), + microvmssh.ShellEscape(listHeader), listFilesScript, ) } diff --git a/pkg/infra/ssh/prober.go b/pkg/infra/ssh/prober.go index 7778d83..aa52f9d 100644 --- a/pkg/infra/ssh/prober.go +++ b/pkg/infra/ssh/prober.go @@ -10,7 +10,7 @@ import ( "strings" "time" - propolisssh "github.com/stacklok/propolis/ssh" + microvmssh "github.com/stacklok/go-microvm/ssh" "github.com/stacklok/waggle/pkg/domain/environment" ) @@ -29,7 +29,7 @@ func (*Prober) Probe( ) (environment.Capabilities, error) { const trueValue = "true" - client := propolisssh.NewClient(host, port, "sandbox", keyPath) + client := microvmssh.NewClient(host, port, "sandbox", keyPath) script := strings.TrimSpace(` python_cmd="" @@ -108,7 +108,7 @@ echo "IS_ROOT=$is_root" encoded := base64.StdEncoding.EncodeToString([]byte(script)) cmd := fmt.Sprintf( "printf '%%s' %s | base64 -d | sh", - propolisssh.ShellEscape(encoded), + microvmssh.ShellEscape(encoded), ) output, runErr := client.Run(ctx, cmd) diff --git a/pkg/infra/vm/hooks.go b/pkg/infra/vm/hooks.go index b3247ca..03a3d8c 100644 --- a/pkg/infra/vm/hooks.go +++ b/pkg/infra/vm/hooks.go @@ -8,7 +8,7 @@ import ( "os" "path/filepath" - "github.com/stacklok/propolis/image" + "github.com/stacklok/go-microvm/image" "github.com/stacklok/waggle/pkg/infra/vm/initbin" ) diff --git a/pkg/infra/vm/propolis.go b/pkg/infra/vm/microvm.go similarity index 66% rename from pkg/infra/vm/propolis.go rename to pkg/infra/vm/microvm.go index b6a3f4d..35483f9 100644 --- a/pkg/infra/vm/propolis.go +++ b/pkg/infra/vm/microvm.go @@ -11,29 +11,29 @@ import ( "path/filepath" "sync" - "github.com/stacklok/propolis" - "github.com/stacklok/propolis/extract" - "github.com/stacklok/propolis/hooks" - "github.com/stacklok/propolis/hypervisor/libkrun" - "github.com/stacklok/propolis/image" - propolisssh "github.com/stacklok/propolis/ssh" + "github.com/stacklok/go-microvm" + "github.com/stacklok/go-microvm/extract" + "github.com/stacklok/go-microvm/hooks" + "github.com/stacklok/go-microvm/hypervisor/libkrun" + "github.com/stacklok/go-microvm/image" + microvmssh "github.com/stacklok/go-microvm/ssh" "github.com/stacklok/waggle/pkg/domain/environment" ) // vmEntry holds the runtime state for a single VM. type vmEntry struct { - vm *propolis.VM + vm *microvm.VM sshKeyPath string } -// PropolisProvider implements Provider using propolis microVMs. -type PropolisProvider struct { +// MicroVMProvider implements Provider using go-microvm microVMs. +type MicroVMProvider struct { mu sync.RWMutex // vms maps environment ID to the running VM entry. vms map[string]*vmEntry - // runtimeSource optionally provides propolis-runner and libkrun via extraction. + // runtimeSource optionally provides go-microvm-runner and libkrun via extraction. runtimeSource extract.Source // firmwareSource optionally provides libkrunfw via extraction. @@ -46,33 +46,33 @@ type PropolisProvider struct { logLevel uint32 } -// ProviderOption configures a PropolisProvider. -type ProviderOption func(*PropolisProvider) +// ProviderOption configures a MicroVMProvider. +type ProviderOption func(*MicroVMProvider) -// WithRuntimeSource sets an extract.Source providing propolis-runner and libkrun. +// WithRuntimeSource sets an extract.Source providing go-microvm-runner and libkrun. func WithRuntimeSource(src extract.Source) ProviderOption { - return func(p *PropolisProvider) { p.runtimeSource = src } + return func(p *MicroVMProvider) { p.runtimeSource = src } } // WithFirmwareSource sets an extract.Source providing libkrunfw. func WithFirmwareSource(src extract.Source) ProviderOption { - return func(p *PropolisProvider) { p.firmwareSource = src } + return func(p *MicroVMProvider) { p.firmwareSource = src } } // WithImageCache sets a shared OCI image cache for layer-level // caching and COW rootfs cloning. func WithImageCache(cache *image.Cache) ProviderOption { - return func(p *PropolisProvider) { p.imageCache = cache } + return func(p *MicroVMProvider) { p.imageCache = cache } } // WithLogLevel sets the libkrun log verbosity (0=off through 5=trace). func WithLogLevel(level uint32) ProviderOption { - return func(p *PropolisProvider) { p.logLevel = level } + return func(p *MicroVMProvider) { p.logLevel = level } } -// NewPropolisProvider creates a new PropolisProvider. -func NewPropolisProvider(opts ...ProviderOption) *PropolisProvider { - p := &PropolisProvider{ +// NewMicroVMProvider creates a new MicroVMProvider. +func NewMicroVMProvider(opts ...ProviderOption) *MicroVMProvider { + p := &MicroVMProvider{ vms: make(map[string]*vmEntry), } for _, o := range opts { @@ -82,7 +82,7 @@ func NewPropolisProvider(opts ...ProviderOption) *PropolisProvider { } // CreateVM provisions a new microVM for the given environment. -func (p *PropolisProvider) CreateVM(ctx context.Context, env *environment.Environment, opts CreateVMOpts) (*Handle, error) { +func (p *MicroVMProvider) CreateVM(ctx context.Context, env *environment.Environment, opts CreateVMOpts) (*Handle, error) { // Create per-environment data directory. envDataDir := filepath.Join(opts.DataDir, "envs", env.ID) if err := os.MkdirAll(envDataDir, 0o700); err != nil { @@ -90,13 +90,13 @@ func (p *PropolisProvider) CreateVM(ctx context.Context, env *environment.Enviro } // Generate SSH keys for this environment. - privateKeyPath, publicKeyPath, err := propolisssh.GenerateKeyPair(envDataDir) + privateKeyPath, publicKeyPath, err := microvmssh.GenerateKeyPair(envDataDir) if err != nil { return nil, fmt.Errorf("generate SSH keys: %w", err) } // Read the public key content for injection into rootfs. - pubKeyContent, err := propolisssh.GetPublicKeyContent(publicKeyPath) + pubKeyContent, err := microvmssh.GetPublicKeyContent(publicKeyPath) if err != nil { return nil, fmt.Errorf("read SSH public key: %w", err) } @@ -108,7 +108,7 @@ func (p *PropolisProvider) CreateVM(ctx context.Context, env *environment.Enviro "ssh_port", env.SSHPort, ) - rootfsHooks := []propolis.RootFSHook{ + rootfsHooks := []microvm.RootFSHook{ hooks.InjectAuthorizedKeys(pubKeyContent, hooks.WithKeyUser("/home/sandbox", 1000, 1000)), } if opts.InitPath != "" { @@ -121,47 +121,47 @@ func (p *PropolisProvider) CreateVM(ctx context.Context, env *environment.Enviro rootfsHooks = append(rootfsHooks, InjectInitBinary()) } - // Build propolis options. - propolisOpts := []propolis.Option{ - propolis.WithName("waggle-" + env.ID), - propolis.WithCPUs(opts.CPUs), - propolis.WithMemory(opts.MemoryMB), - propolis.WithPorts(propolis.PortForward{ + // Build microvm options. + microvmOpts := []microvm.Option{ + microvm.WithName("waggle-" + env.ID), + microvm.WithCPUs(opts.CPUs), + microvm.WithMemory(opts.MemoryMB), + microvm.WithPorts(microvm.PortForward{ Host: env.SSHPort, Guest: 22, }), - propolis.WithDataDir(envDataDir), + microvm.WithDataDir(envDataDir), // Inject SSH authorized_keys and waggle-init into the rootfs before boot. - propolis.WithRootFSHook(rootfsHooks...), - propolis.WithInitOverride("/waggle-init"), + microvm.WithRootFSHook(rootfsHooks...), + microvm.WithInitOverride("/waggle-init"), // Wait for SSH to become ready after boot. - propolis.WithPostBoot(sshReadyWaiter(env.SSHPort, privateKeyPath)), + microvm.WithPostBoot(sshReadyWaiter(env.SSHPort, privateKeyPath)), } if p.imageCache != nil { - propolisOpts = append(propolisOpts, - propolis.WithImageCache(p.imageCache)) + microvmOpts = append(microvmOpts, + microvm.WithImageCache(p.imageCache)) } if p.logLevel > 0 { - propolisOpts = append(propolisOpts, - propolis.WithLogLevel(p.logLevel)) + microvmOpts = append(microvmOpts, + microvm.WithLogLevel(p.logLevel)) } if checks := extraPreflightChecks(); len(checks) > 0 { - propolisOpts = append(propolisOpts, - propolis.WithPreflightChecks(checks...)) + microvmOpts = append(microvmOpts, + microvm.WithPreflightChecks(checks...)) } - propolisOpts = append(propolisOpts, propolis.WithBackend( + microvmOpts = append(microvmOpts, microvm.WithBackend( libkrun.NewBackend(p.buildBackendOpts(opts)...), )) // Start the VM. - vm, err := propolis.Run(ctx, opts.ImageRef, propolisOpts...) + vm, err := microvm.Run(ctx, opts.ImageRef, microvmOpts...) if err != nil { - return nil, fmt.Errorf("propolis.Run: %w", err) + return nil, fmt.Errorf("microvm.Run: %w", err) } // Store the VM handle. @@ -184,7 +184,7 @@ func (p *PropolisProvider) CreateVM(ctx context.Context, env *environment.Enviro } // DestroyVM tears down the VM for the given environment. -func (p *PropolisProvider) DestroyVM(ctx context.Context, envID string) error { +func (p *MicroVMProvider) DestroyVM(ctx context.Context, envID string) error { p.mu.Lock() entry, ok := p.vms[envID] if ok { @@ -207,7 +207,7 @@ func (p *PropolisProvider) DestroyVM(ctx context.Context, envID string) error { } // IsRunning checks whether the VM for the given environment is alive. -func (p *PropolisProvider) IsRunning(_ context.Context, envID string) bool { +func (p *MicroVMProvider) IsRunning(_ context.Context, envID string) bool { p.mu.RLock() defer p.mu.RUnlock() @@ -216,7 +216,7 @@ func (p *PropolisProvider) IsRunning(_ context.Context, envID string) bool { } // SSHKeyPath returns the SSH private key path for the given environment. -func (p *PropolisProvider) SSHKeyPath(envID string) string { +func (p *MicroVMProvider) SSHKeyPath(envID string) string { p.mu.RLock() defer p.mu.RUnlock() @@ -229,7 +229,7 @@ func (p *PropolisProvider) SSHKeyPath(envID string) string { // buildBackendOpts constructs libkrun backend options, merging provider-level // sources with per-call opts. -func (p *PropolisProvider) buildBackendOpts(opts CreateVMOpts) []libkrun.Option { +func (p *MicroVMProvider) buildBackendOpts(opts CreateVMOpts) []libkrun.Option { runtimeSrc := opts.RuntimeSource if runtimeSrc == nil { runtimeSrc = p.runtimeSource @@ -271,7 +271,7 @@ func (p *PropolisProvider) buildBackendOpts(opts CreateVMOpts) []libkrun.Option // initInjector reads the waggle-init binary from disk and returns a // RootFSHook that injects it into the guest rootfs at /waggle-init. // The caller must ensure initPath is non-empty. -func initInjector(initPath string) (propolis.RootFSHook, error) { +func initInjector(initPath string) (microvm.RootFSHook, error) { data, err := os.ReadFile(initPath) //nolint:gosec // initPath comes from server config, not user input if err != nil { return nil, fmt.Errorf("read init binary %s: %w", initPath, err) @@ -281,9 +281,9 @@ func initInjector(initPath string) (propolis.RootFSHook, error) { // sshReadyWaiter returns a PostBootHook that waits for SSH to become // available on the given port. -func sshReadyWaiter(port uint16, keyPath string) propolis.PostBootHook { - return func(ctx context.Context, _ *propolis.VM) error { - client := propolisssh.NewClient("127.0.0.1", port, "sandbox", keyPath) +func sshReadyWaiter(port uint16, keyPath string) microvm.PostBootHook { + return func(ctx context.Context, _ *microvm.VM) error { + client := microvmssh.NewClient("127.0.0.1", port, "sandbox", keyPath) return client.WaitForReady(ctx) } } diff --git a/pkg/infra/vm/propolis_test.go b/pkg/infra/vm/microvm_test.go similarity index 92% rename from pkg/infra/vm/propolis_test.go rename to pkg/infra/vm/microvm_test.go index 7243199..d37db3d 100644 --- a/pkg/infra/vm/propolis_test.go +++ b/pkg/infra/vm/microvm_test.go @@ -8,16 +8,16 @@ import ( "path/filepath" "testing" - "github.com/stacklok/propolis/extract" - "github.com/stacklok/propolis/image" + "github.com/stacklok/go-microvm/extract" + "github.com/stacklok/go-microvm/image" ) -func TestNewPropolisProvider(t *testing.T) { +func TestNewMicroVMProvider(t *testing.T) { t.Parallel() t.Run("no options", func(t *testing.T) { t.Parallel() - p := NewPropolisProvider() + p := NewMicroVMProvider() if p.vms == nil { t.Fatal("vms map should be initialized") } @@ -33,7 +33,7 @@ func TestNewPropolisProvider(t *testing.T) { t.Parallel() rtSrc := extract.Dir(t.TempDir()) fwSrc := extract.Dir(t.TempDir()) - p := NewPropolisProvider( + p := NewMicroVMProvider( WithRuntimeSource(rtSrc), WithFirmwareSource(fwSrc), ) @@ -48,7 +48,7 @@ func TestNewPropolisProvider(t *testing.T) { t.Run("with image cache", func(t *testing.T) { t.Parallel() cache := image.NewCache(t.TempDir()) - p := NewPropolisProvider(WithImageCache(cache)) + p := NewMicroVMProvider(WithImageCache(cache)) if p.imageCache == nil { t.Error("imageCache should be set") } @@ -56,7 +56,7 @@ func TestNewPropolisProvider(t *testing.T) { t.Run("with log level", func(t *testing.T) { t.Parallel() - p := NewPropolisProvider(WithLogLevel(3)) + p := NewMicroVMProvider(WithLogLevel(3)) if p.logLevel != 3 { t.Errorf("logLevel = %d, want 3", p.logLevel) } diff --git a/pkg/infra/vm/preflight_linux.go b/pkg/infra/vm/preflight_linux.go index d586d22..5836b10 100644 --- a/pkg/infra/vm/preflight_linux.go +++ b/pkg/infra/vm/preflight_linux.go @@ -5,7 +5,7 @@ package vm -import "github.com/stacklok/propolis/preflight" +import "github.com/stacklok/go-microvm/preflight" // extraPreflightChecks returns additional preflight checks for Linux hosts. // Currently this adds a user namespace availability check, which catches diff --git a/pkg/infra/vm/preflight_other.go b/pkg/infra/vm/preflight_other.go index 0cfb5ba..492ab70 100644 --- a/pkg/infra/vm/preflight_other.go +++ b/pkg/infra/vm/preflight_other.go @@ -5,7 +5,7 @@ package vm -import "github.com/stacklok/propolis/preflight" +import "github.com/stacklok/go-microvm/preflight" // extraPreflightChecks returns nil on non-Linux platforms where user // namespace checks are not applicable. diff --git a/pkg/infra/vm/provider.go b/pkg/infra/vm/provider.go index 687020f..3976161 100644 --- a/pkg/infra/vm/provider.go +++ b/pkg/infra/vm/provider.go @@ -6,7 +6,7 @@ package vm import ( "context" - "github.com/stacklok/propolis/extract" + "github.com/stacklok/go-microvm/extract" "github.com/stacklok/waggle/pkg/domain/environment" ) @@ -18,7 +18,7 @@ type CreateVMOpts struct { ImageRef string DataDir string - // RunnerPath is an optional explicit path to propolis-runner. + // RunnerPath is an optional explicit path to go-microvm-runner. RunnerPath string // InitPath is an optional path to the waggle-init binary. @@ -29,7 +29,7 @@ type CreateVMOpts struct { // LibDir is an optional path to the libkrun library directory. LibDir string - // RuntimeSource provides propolis-runner and libkrun via extraction. + // RuntimeSource provides go-microvm-runner and libkrun via extraction. // Mutually exclusive with RunnerPath and LibDir. RuntimeSource extract.Source diff --git a/pkg/infra/vm/runtimebin/NOTICE.md b/pkg/infra/vm/runtimebin/NOTICE.md index ebaf612..2155bb1 100644 --- a/pkg/infra/vm/runtimebin/NOTICE.md +++ b/pkg/infra/vm/runtimebin/NOTICE.md @@ -9,7 +9,7 @@ libkrunfw is licensed under the **GNU General Public License v2.0 (GPL-2.0)**. libkrunfw is distributed as a separate shared library (`libkrunfw.so.5` on Linux, `libkrunfw.dylib` on macOS) that is loaded at runtime by the -`propolis-runner` subprocess. It is not statically linked into any binary +`go-microvm-runner` subprocess. It is not statically linked into any binary in this project. When distributed in binary form (embedded in the waggle binary), the diff --git a/pkg/infra/vm/runtimebin/doc.go b/pkg/infra/vm/runtimebin/doc.go index 510bb43..feb77d2 100644 --- a/pkg/infra/vm/runtimebin/doc.go +++ b/pkg/infra/vm/runtimebin/doc.go @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2025 Stacklok, Inc. // SPDX-License-Identifier: Apache-2.0 -// Package runtimebin optionally embeds propolis-runner and libkrun/libkrunfw +// Package runtimebin optionally embeds go-microvm-runner and libkrun/libkrunfw // shared libraries into the waggle binary. When built with the embed_runtime // tag, the package provides extract.Source instances that extract the embedded // binaries to a versioned cache directory on first run. Without the tag, all diff --git a/pkg/infra/vm/runtimebin/embed_darwin.go b/pkg/infra/vm/runtimebin/embed_darwin.go index 4d69690..221cb27 100644 --- a/pkg/infra/vm/runtimebin/embed_darwin.go +++ b/pkg/infra/vm/runtimebin/embed_darwin.go @@ -7,7 +7,7 @@ package runtimebin import _ "embed" -//go:embed propolis-runner +//go:embed go-microvm-runner var runner []byte //go:embed libkrun.1.dylib diff --git a/pkg/infra/vm/runtimebin/embed_linux.go b/pkg/infra/vm/runtimebin/embed_linux.go index d6710f0..9794f86 100644 --- a/pkg/infra/vm/runtimebin/embed_linux.go +++ b/pkg/infra/vm/runtimebin/embed_linux.go @@ -7,7 +7,7 @@ package runtimebin import _ "embed" -//go:embed propolis-runner +//go:embed go-microvm-runner var runner []byte //go:embed libkrun.so.1 diff --git a/pkg/infra/vm/runtimebin/runtimebin.go b/pkg/infra/vm/runtimebin/runtimebin.go index 6c56521..1d5881e 100644 --- a/pkg/infra/vm/runtimebin/runtimebin.go +++ b/pkg/infra/vm/runtimebin/runtimebin.go @@ -3,9 +3,9 @@ package runtimebin -import "github.com/stacklok/propolis/extract" +import "github.com/stacklok/go-microvm/extract" -// Version is the propolis version string used to key the extraction cache. +// Version is the go-microvm version string used to key the extraction cache. // It is set via ldflags at build time. var Version = "dev" @@ -14,7 +14,7 @@ func Available() bool { return available } -// RuntimeSource returns an extract.Source that provides propolis-runner and +// RuntimeSource returns an extract.Source that provides go-microvm-runner and // libkrun. Returns nil when the runtime is not embedded (stub build). func RuntimeSource() extract.Source { if !available {