From d560757fd9ce06d7298ee3caea0eb54b5d2c767d Mon Sep 17 00:00:00 2001 From: Chimera Date: Thu, 18 Jun 2026 20:38:53 +0800 Subject: [PATCH 1/2] P0: project scaffolding and foundations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements milestone P0 (#1–#5): - Go module + package layout (cmd/macvz-kubelet, pkg/{runtime,provider, network,config,metrics}, internal/{types,version}) - CLI entrypoint with flags, YAML config loading, klog logging, --version - runtime.Runtime interface + provider.Provider stub implementing virtual-kubelet node.PodLifecycleHandler (compile-time asserted) - CI workflow (build/vet/test/lint) on macOS Apple Silicon runners - Makefile with ldflags version stamping; example config Pins virtual-kubelet v1.12.0 + k8s.io v0.35.0 (requires Go 1.25). Closes #1 Closes #2 Closes #3 Closes #4 Closes #5 Co-Authored-By: Claude Opus 4.8 (1M context) --- .editorconfig | 17 ++++ .github/workflows/ci.yml | 45 ++++++++++ .gitignore | 1 - Makefile | 52 ++++++++++++ cmd/macvz-kubelet/main.go | 60 +++++++++++++ config.example.yaml | 16 ++++ go.mod | 52 ++++++++++++ go.sum | 163 ++++++++++++++++++++++++++++++++++++ internal/types/types.go | 34 ++++++++ internal/version/version.go | 25 ++++++ pkg/config/config.go | 75 +++++++++++++++++ pkg/config/config_test.go | 52 ++++++++++++ pkg/metrics/doc.go | 3 + pkg/network/doc.go | 4 + pkg/provider/provider.go | 64 ++++++++++++++ pkg/runtime/runtime.go | 77 +++++++++++++++++ 16 files changed, 739 insertions(+), 1 deletion(-) create mode 100644 .editorconfig create mode 100644 .github/workflows/ci.yml create mode 100644 Makefile create mode 100644 cmd/macvz-kubelet/main.go create mode 100644 config.example.yaml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/types/types.go create mode 100644 internal/version/version.go create mode 100644 pkg/config/config.go create mode 100644 pkg/config/config_test.go create mode 100644 pkg/metrics/doc.go create mode 100644 pkg/network/doc.go create mode 100644 pkg/provider/provider.go create mode 100644 pkg/runtime/runtime.go diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5f1edb5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.go] +indent_style = tab + +[*.{yml,yaml,json,md}] +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e2ba52b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + build-test: + # darwin/arm64-only project: build and test on Apple Silicon runners. + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Build + run: go build ./... + + - name: Vet + run: go vet ./... + + - name: Test + run: go test -race ./... + + lint: + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest diff --git a/.gitignore b/.gitignore index 47ad5e0..3747641 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # Binaries /bin/ /dist/ -macvz-kubelet *.exe *.test *.out diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8631077 --- /dev/null +++ b/Makefile @@ -0,0 +1,52 @@ +# macvz-kubelet build tooling + +BINARY := macvz-kubelet +PKG := github.com/chimerakang/macvz +CMD := ./cmd/macvz-kubelet +BIN_DIR := bin + +VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) +COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo none) +DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ) + +VPKG := $(PKG)/internal/version +LDFLAGS := -s -w \ + -X $(VPKG).Version=$(VERSION) \ + -X $(VPKG).Commit=$(COMMIT) \ + -X $(VPKG).Date=$(DATE) + +.DEFAULT_GOAL := build + +.PHONY: build +build: ## Build the binary into bin/ with version stamping + @mkdir -p $(BIN_DIR) + go build -ldflags "$(LDFLAGS)" -o $(BIN_DIR)/$(BINARY) $(CMD) + +.PHONY: test +test: ## Run unit tests + go test ./... + +.PHONY: vet +vet: ## Run go vet + go vet ./... + +.PHONY: lint +lint: ## Run golangci-lint (must be installed) + golangci-lint run + +.PHONY: fmt +fmt: ## Format all Go source + gofmt -s -w . + +.PHONY: tidy +tidy: ## Tidy module dependencies + go mod tidy + +.PHONY: clean +clean: ## Remove build artifacts + rm -rf $(BIN_DIR) + +.PHONY: help +help: ## Show this help + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-12s\033[0m %s\n", $$1, $$2}' diff --git a/cmd/macvz-kubelet/main.go b/cmd/macvz-kubelet/main.go new file mode 100644 index 0000000..4228c64 --- /dev/null +++ b/cmd/macvz-kubelet/main.go @@ -0,0 +1,60 @@ +// Command macvz-kubelet runs on each Apple Silicon Mac and registers it as a +// Kubernetes node via Virtual Kubelet, launching Pods as native micro-VMs. +// +// P0 wires configuration, logging, and version reporting. Node registration and +// the Pod lifecycle are implemented in P2. +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/chimerakang/macvz/internal/version" + "github.com/chimerakang/macvz/pkg/config" + "k8s.io/klog/v2" +) + +func main() { + var ( + configPath string + showVersion bool + ) + flag.StringVar(&configPath, "config", "", "path to the YAML config file") + flag.BoolVar(&showVersion, "version", false, "print version and exit") + + // Register klog's flags (e.g. -v, --logtostderr) on the default FlagSet. + klog.InitFlags(nil) + flag.Parse() + + if showVersion { + fmt.Println(version.String()) + return + } + + if err := run(configPath); err != nil { + klog.ErrorS(err, "macvz-kubelet exited with error") + klog.Flush() + os.Exit(1) + } + klog.Flush() +} + +func run(configPath string) error { + cfg, err := config.Load(configPath) + if err != nil { + return fmt.Errorf("load config: %w", err) + } + + klog.InfoS("starting macvz-kubelet", + "version", version.Version, + "node", cfg.NodeName, + "runtimeSocket", cfg.RuntimeSocket, + "logLevel", cfg.LogLevel, + ) + + // TODO(P2): build the runtime driver, construct the provider, and start the + // Virtual Kubelet node controller here. + klog.InfoS("node registration not yet implemented (lands in P2); exiting cleanly") + return nil +} diff --git a/config.example.yaml b/config.example.yaml new file mode 100644 index 0000000..6f48d39 --- /dev/null +++ b/config.example.yaml @@ -0,0 +1,16 @@ +# Example macvz-kubelet configuration. +# All fields are optional; omitted values fall back to built-in defaults. +# Run with: macvz-kubelet --config ./config.yaml + +# Kubernetes node name this process registers as. Defaults to the OS hostname. +nodeName: mac-mini-01 + +# Path to the kubeconfig used to reach the API server. +# Leave empty to use in-cluster config or the KUBECONFIG environment variable. +kubeconfigPath: "" + +# Path to the apple/container service API socket. +runtimeSocket: /var/run/com.apple.container.sock + +# Log verbosity: "info" or "debug" (klog -v levels also honored via flags). +logLevel: info diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1180756 --- /dev/null +++ b/go.mod @@ -0,0 +1,52 @@ +module github.com/chimerakang/macvz + +go 1.25.0 + +require ( + github.com/virtual-kubelet/virtual-kubelet v1.12.0 + gopkg.in/yaml.v3 v3.0.1 + k8s.io/api v0.35.0 + k8s.io/klog/v2 v2.130.1 +) + +require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/oauth2 v0.32.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/term v0.37.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/time v0.14.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + k8s.io/apimachinery v0.35.0 // indirect + k8s.io/client-go v0.35.0 // indirect + k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..dfd5bce --- /dev/null +++ b/go.sum @@ -0,0 +1,163 @@ +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bombsimon/logrusr/v3 v3.1.0 h1:zORbLM943D+hDMGgyjMhSAz/iDz86ZV72qaak/CA0zQ= +github.com/bombsimon/logrusr/v3 v3.1.0/go.mod h1:PksPPgSFEL2I52pla2glgCyyd2OqOHAnFF5E+g8Ixco= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= +github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= +github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= +github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +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/virtual-kubelet/virtual-kubelet v1.12.0 h1:6RnRE3egGnqw3BDL9PBbP5DPV6OaXC2h/nfq5c7VsF4= +github.com/virtual-kubelet/virtual-kubelet v1.12.0/go.mod h1:dVlVEsFfrrwAcj/v0eDGgkTF5r+eAsBnn9gDxx3au2s= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY= +k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8= +k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= +k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE= +k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A= +sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/internal/types/types.go b/internal/types/types.go new file mode 100644 index 0000000..bab08d1 --- /dev/null +++ b/internal/types/types.go @@ -0,0 +1,34 @@ +// Package types holds small value types shared between the runtime driver and +// the Virtual Kubelet provider, so neither package needs to import the other. +package types + +// PodRef identifies a Kubernetes Pod by namespace and name. +type PodRef struct { + Namespace string + Name string +} + +// String returns the "namespace/name" form. +func (r PodRef) String() string { + return r.Namespace + "/" + r.Name +} + +// ContainerSpec is the minimal description the runtime needs to launch one +// workload as a micro-VM. It is intentionally decoupled from the Kubernetes API +// types; the provider translates a Pod into one or more ContainerSpecs. +type ContainerSpec struct { + // Name is unique within a Pod. + Name string + // Image is an OCI image reference (e.g. "docker.io/library/alpine:3.20"). + Image string + // Command overrides the image entrypoint when non-empty. + Command []string + // Args overrides the image CMD when non-empty. + Args []string + // Env is the environment passed to the workload. + Env map[string]string + // CPUMillis is the CPU request in milli-cores (1000 = 1 vCPU); 0 means unset. + CPUMillis int64 + // MemoryBytes is the memory request/limit in bytes; 0 means unset. + MemoryBytes int64 +} diff --git a/internal/version/version.go b/internal/version/version.go new file mode 100644 index 0000000..e697495 --- /dev/null +++ b/internal/version/version.go @@ -0,0 +1,25 @@ +// Package version exposes build metadata, populated at link time via -ldflags. +package version + +import ( + "fmt" + "runtime" +) + +// These values are overridden at build time with: +// +// -ldflags "-X github.com/chimerakang/macvz/internal/version.Version=..." +var ( + // Version is the semantic version or git describe of the build. + Version = "dev" + // Commit is the git commit SHA the binary was built from. + Commit = "none" + // Date is the build timestamp (RFC3339). + Date = "unknown" +) + +// String returns a human-readable one-line version summary. +func String() string { + return fmt.Sprintf("macvz-kubelet %s (commit %s, built %s, %s/%s, %s)", + Version, Commit, Date, runtime.GOOS, runtime.GOARCH, runtime.Version()) +} diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..24fdb6f --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,75 @@ +// Package config loads macvz-kubelet configuration from a YAML file, applying +// sensible defaults and validating required fields. +package config + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v3" +) + +// Config is the on-disk configuration for a macvz-kubelet node process. +type Config struct { + // NodeName is the Kubernetes node name this process registers as. + // Defaults to the OS hostname when empty. + NodeName string `yaml:"nodeName"` + + // KubeconfigPath points at the kubeconfig used to reach the API server. + // When empty, in-cluster config (or KUBECONFIG env) is used. + KubeconfigPath string `yaml:"kubeconfigPath"` + + // RuntimeSocket is the path to the apple/container service API socket. + RuntimeSocket string `yaml:"runtimeSocket"` + + // LogLevel is the klog verbosity ("info", "debug") or a numeric V level. + LogLevel string `yaml:"logLevel"` +} + +// Default returns a Config populated with built-in defaults. +func Default() Config { + host, _ := os.Hostname() + return Config{ + NodeName: host, + RuntimeSocket: "/var/run/com.apple.container.sock", + LogLevel: "info", + } +} + +// Load reads and parses the YAML config at path, layering it over defaults. +// A non-existent path is not an error: defaults are returned. This lets the +// binary run with zero config during early development. +func Load(path string) (Config, error) { + cfg := Default() + if path == "" { + return cfg, nil + } + + data, err := os.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + return cfg, nil + } + return cfg, fmt.Errorf("read config %q: %w", path, err) + } + + if err := yaml.Unmarshal(data, &cfg); err != nil { + return cfg, fmt.Errorf("parse config %q: %w", path, err) + } + + if err := cfg.Validate(); err != nil { + return cfg, err + } + return cfg, nil +} + +// Validate checks that required fields are present and coherent. +func (c Config) Validate() error { + if c.NodeName == "" { + return fmt.Errorf("nodeName must be set (hostname lookup failed)") + } + if c.RuntimeSocket == "" { + return fmt.Errorf("runtimeSocket must be set") + } + return nil +} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go new file mode 100644 index 0000000..e7d1ccc --- /dev/null +++ b/pkg/config/config_test.go @@ -0,0 +1,52 @@ +package config + +import ( + "os" + "path/filepath" + "testing" +) + +func TestLoadMissingPathReturnsDefaults(t *testing.T) { + cfg, err := Load(filepath.Join(t.TempDir(), "nope.yaml")) + if err != nil { + t.Fatalf("expected no error for missing file, got %v", err) + } + if cfg.RuntimeSocket == "" { + t.Fatal("expected default RuntimeSocket to be set") + } +} + +func TestLoadOverridesDefaults(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "config.yaml") + const body = "nodeName: mac-mini-01\nlogLevel: debug\n" + if err := os.WriteFile(p, []byte(body), 0o600); err != nil { + t.Fatal(err) + } + + cfg, err := Load(p) + if err != nil { + t.Fatalf("Load: %v", err) + } + if cfg.NodeName != "mac-mini-01" { + t.Errorf("NodeName = %q, want mac-mini-01", cfg.NodeName) + } + if cfg.LogLevel != "debug" { + t.Errorf("LogLevel = %q, want debug", cfg.LogLevel) + } + // Unspecified field keeps its default. + if cfg.RuntimeSocket == "" { + t.Error("RuntimeSocket should retain its default when not overridden") + } +} + +func TestValidate(t *testing.T) { + c := Default() + if err := c.Validate(); err != nil { + t.Fatalf("default config should validate: %v", err) + } + c.RuntimeSocket = "" + if err := c.Validate(); err == nil { + t.Error("expected error when RuntimeSocket is empty") + } +} diff --git a/pkg/metrics/doc.go b/pkg/metrics/doc.go new file mode 100644 index 0000000..85ece0a --- /dev/null +++ b/pkg/metrics/doc.go @@ -0,0 +1,3 @@ +// Package metrics will report node and Pod resource usage back to Kubernetes +// (capacity, allocatable, and per-Pod stats). Implemented in milestone P4. +package metrics diff --git a/pkg/network/doc.go b/pkg/network/doc.go new file mode 100644 index 0000000..80153f3 --- /dev/null +++ b/pkg/network/doc.go @@ -0,0 +1,4 @@ +// Package network will provide cross-host Pod connectivity: a WireGuard mesh +// between Macs plus Pod IPAM coordinated through Kubernetes, so Pods scheduled +// on different nodes reach each other directly. Implemented in milestone P3. +package network diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go new file mode 100644 index 0000000..b99c60b --- /dev/null +++ b/pkg/provider/provider.go @@ -0,0 +1,64 @@ +// Package provider implements the Virtual Kubelet PodLifecycleHandler, turning +// an Apple Silicon Mac into a Kubernetes node. Each Pod is realized as a +// micro-VM through the runtime.Runtime abstraction. +// +// This is the P0 skeleton: the type satisfies the interface and depends only on +// runtime.Runtime (never a concrete driver). Real translation and lifecycle +// logic land in P2. +package provider + +import ( + "context" + "errors" + + "github.com/chimerakang/macvz/pkg/runtime" + "github.com/virtual-kubelet/virtual-kubelet/node" + corev1 "k8s.io/api/core/v1" +) + +// errNotImplemented is returned by every method until P2 fills them in. +var errNotImplemented = errors.New("macvz provider: not implemented yet") + +// Provider realizes Kubernetes Pods as micro-VMs via a runtime.Runtime. +type Provider struct { + nodeName string + rt runtime.Runtime +} + +// New constructs a Provider bound to a node name and runtime driver. +func New(nodeName string, rt runtime.Runtime) *Provider { + return &Provider{nodeName: nodeName, rt: rt} +} + +// Compile-time assertion that Provider satisfies the Virtual Kubelet contract. +var _ node.PodLifecycleHandler = (*Provider)(nil) + +// CreatePod takes a Kubernetes Pod and launches it as a micro-VM. +func (p *Provider) CreatePod(ctx context.Context, pod *corev1.Pod) error { + return errNotImplemented +} + +// UpdatePod reconciles an existing Pod's desired state. +func (p *Provider) UpdatePod(ctx context.Context, pod *corev1.Pod) error { + return errNotImplemented +} + +// DeletePod tears down the micro-VM backing a Pod. +func (p *Provider) DeletePod(ctx context.Context, pod *corev1.Pod) error { + return errNotImplemented +} + +// GetPod returns the Pod for namespace/name, or nil if not found. +func (p *Provider) GetPod(ctx context.Context, namespace, name string) (*corev1.Pod, error) { + return nil, errNotImplemented +} + +// GetPodStatus returns the status of the Pod identified by namespace/name. +func (p *Provider) GetPodStatus(ctx context.Context, namespace, name string) (*corev1.PodStatus, error) { + return nil, errNotImplemented +} + +// GetPods lists all Pods known to this provider. +func (p *Provider) GetPods(ctx context.Context) ([]*corev1.Pod, error) { + return nil, errNotImplemented +} diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go new file mode 100644 index 0000000..394cc63 --- /dev/null +++ b/pkg/runtime/runtime.go @@ -0,0 +1,77 @@ +// Package runtime defines the contract between the Virtual Kubelet provider and +// the host container runtime (apple/container), which runs each workload as an +// isolated Linux micro-VM via Apple's Virtualization.framework. +// +// The interface is deliberately concrete-driver-agnostic so the provider can be +// developed and tested against a fake. The apple/container driver lands in P1. +package runtime + +import ( + "context" + "io" + "time" + + "github.com/chimerakang/macvz/internal/types" +) + +// Phase is the lifecycle state of a workload's micro-VM. +type Phase string + +const ( + PhaseUnknown Phase = "Unknown" + PhaseCreated Phase = "Created" + PhaseRunning Phase = "Running" + PhaseStopped Phase = "Stopped" + PhaseFailed Phase = "Failed" +) + +// Status reports the observed state of a single workload. +type Status struct { + ID string + Phase Phase + ExitCode int + // Message carries human-readable detail for failures. + Message string + StartedAt time.Time + // IP is the workload's address once networking is wired up (P3). + IP string +} + +// LogOptions controls log retrieval. +type LogOptions struct { + // Follow streams new output until the context is cancelled. + Follow bool + // Tail limits output to the last N lines when > 0. + Tail int +} + +// ExecIO wires standard streams for an Exec session. +type ExecIO struct { + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer + TTY bool +} + +// Runtime drives the full lifecycle of a workload micro-VM on a single host. +// +// Implementations must be safe for concurrent use; the provider may operate on +// multiple workloads at once. +type Runtime interface { + // Pull fetches an OCI image into the local store. + Pull(ctx context.Context, image string) error + // Create provisions (but does not start) a workload, returning its ID. + Create(ctx context.Context, spec types.ContainerSpec) (id string, err error) + // Start boots the workload's micro-VM. + Start(ctx context.Context, id string) error + // Stop requests graceful shutdown, forcing after timeout. + Stop(ctx context.Context, id string, timeout time.Duration) error + // Destroy removes the workload and reclaims its resources. + Destroy(ctx context.Context, id string) error + // Status returns the current observed state of the workload. + Status(ctx context.Context, id string) (Status, error) + // Logs returns a reader over the workload's output. Caller closes it. + Logs(ctx context.Context, id string, opts LogOptions) (io.ReadCloser, error) + // Exec runs a command inside the workload, wiring the given streams. + Exec(ctx context.Context, id string, cmd []string, sio ExecIO) error +} From c735ddf0bf9e68ad9f43961d0afd227523069c3d Mon Sep 17 00:00:00 2001 From: Chimera Date: Thu, 18 Jun 2026 20:42:49 +0800 Subject: [PATCH 2/2] Fix CI lint: use golangci-lint v2 (action v7) and gofmt golangci-lint v1.64.8 (Go 1.24) cannot lint a go 1.25 module. Pin golangci-lint-action@v7 + v2.10.1, add v2 config, gofmt struct alignment. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 5 +++-- .golangci.yml | 8 ++++++++ pkg/runtime/runtime.go | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 .golangci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e2ba52b..e1f4e85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,7 @@ jobs: cache: true - name: golangci-lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v7 with: - version: latest + # v2.10.x is built with Go 1.25; v1.x cannot lint a go 1.25 module. + version: v2.10.1 diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..8bdf603 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,8 @@ +version: "2" + +linters: + default: standard # errcheck, govet, ineffassign, staticcheck, unused + +formatters: + enable: + - gofmt diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index 394cc63..0ef4a51 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -31,7 +31,7 @@ type Status struct { Phase Phase ExitCode int // Message carries human-readable detail for failures. - Message string + Message string StartedAt time.Time // IP is the workload's address once networking is wired up (P3). IP string