feat: cluster backend abstraction (colima + kind + dory)#70
feat: cluster backend abstraction (colima + kind + dory)#70patrickleet wants to merge 6 commits into
Conversation
|
Warning Review limit reached
Next review available in: 43 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (24)
📝 WalkthroughWalkthroughAdds a unified local-cluster backend abstraction supporting Colima, kind, and Dory. Lifecycle, registry wiring, installation, context handling, documentation, and backend-specific smoke validation are updated accordingly. ChangesLocal backend abstraction
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/commands/config/install.rs (1)
178-185: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract
prepare_local_registryto a shared module to eliminate duplication.This function is identical to the one in
src/commands/provider/install.rs:141-148. Both callensure_registry()then delegate tobackend::wire_local_registry_for_target. Extracting it topackage_install.rs(which both files already import from) orbackend/mod.rswould keep the registry-preparation contract in one place and prevent divergence.♻️ Proposed extraction to `package_install.rs`
In
src/commands/local/package_install.rs:use crate::commands::local::backend::{self, Backend}; pub fn prepare_local_registry( backend: Backend, backend_flag: Option<Backend>, context: Option<&str>, ) -> Result<(), Box<dyn Error>> { ensure_registry()?; backend::wire_local_registry_for_target(backend, backend_flag, context) }Then in both
config/install.rsandprovider/install.rs, replace the localprepare_local_registrywith an import:-fn prepare_local_registry( - backend: Backend, - backend_flag: Option<Backend>, - context: Option<&str>, -) -> Result<(), Box<dyn Error>> { - ensure_registry()?; - backend::wire_local_registry_for_target(backend, backend_flag, context) -} +use crate::commands::local::package_install::prepare_local_registry;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/commands/config/install.rs` around lines 178 - 185, Extract the duplicated prepare_local_registry function from config/install.rs and provider/install.rs into the shared local package_install module. Make it public there, preserve its ensure_registry call and backend::wire_local_registry_for_target delegation, then import and use the shared function from both install modules and remove their local copies.src/commands/local/mod.rs (1)
176-186: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsolidate kubectl/helm blocks to reduce duplication.
The helm blocks in
run_cmdandrun_cmd_outputare near-identical to the kubectl blocks, differing only in the context-injection helper. Extracting a shared helper would eliminate the copy-paste and make it easier to add future backends or command wrappers.♻️ Proposed consolidation for run_cmd
pub fn run_cmd(program: &str, args: &[&str]) -> Result<(), Box<dyn Error>> { - if program == "kubectl" { - let full = with_kube_context(args); - let refs: Vec<&str> = full.iter().map(|s| s.as_str()).collect(); - return run_cmd_with_logged_args(program, &refs, &refs); - } - if program == "helm" { - let full = with_helm_kube_context(args); + if program == "kubectl" || program == "helm" { + let full = if program == "kubectl" { + with_kube_context(args) + } else { + with_helm_kube_context(args) + }; let refs: Vec<&str> = full.iter().map(|s| s.as_str()).collect(); return run_cmd_with_logged_args(program, &refs, &refs); } run_cmd_with_logged_args(program, args, args) }♻️ Proposed consolidation for run_cmd_output
pub fn run_cmd_output(program: &str, args: &[&str]) -> Result<String, Box<dyn Error>> { - if program == "kubectl" { - let full = with_kube_context(args); - log::debug!("Running: {} {}", program, full.join(" ")); - let output = Command::new(program).args(&full).output()?; - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - return Err(format!("{} exited with {}: {}", program, output.status, stderr).into()); - } - return Ok(String::from_utf8_lossy(&output.stdout).to_string()); - } - if program == "helm" { - let full = with_helm_kube_context(args); + if program == "kubectl" || program == "helm" { + let full = if program == "kubectl" { + with_kube_context(args) + } else { + with_helm_kube_context(args) + }; log::debug!("Running: {} {}", program, full.join(" ")); let output = Command::new(program).args(&full).output()?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); return Err(format!("{} exited with {}: {}", program, output.status, stderr).into()); } return Ok(String::from_utf8_lossy(&output.stdout).to_string()); }Also applies to: 198-207
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/commands/local/mod.rs` around lines 176 - 186, Consolidate the duplicated kubectl/helm handling in run_cmd and run_cmd_output by extracting a shared helper for context injection and command execution. Have the helper select the appropriate context-injection function based on the program, while preserving unchanged behavior for other programs and both logged and output-capturing execution paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/commands/local/mod.rs`:
- Around line 114-116: Update the doc comments for the Install and Reset
variants in the Backend enum to include Dory alongside colima and kind,
accurately documenting all supported local backends while preserving the
existing operation descriptions.
---
Nitpick comments:
In `@src/commands/config/install.rs`:
- Around line 178-185: Extract the duplicated prepare_local_registry function
from config/install.rs and provider/install.rs into the shared local
package_install module. Make it public there, preserve its ensure_registry call
and backend::wire_local_registry_for_target delegation, then import and use the
shared function from both install modules and remove their local copies.
In `@src/commands/local/mod.rs`:
- Around line 176-186: Consolidate the duplicated kubectl/helm handling in
run_cmd and run_cmd_output by extracting a shared helper for context injection
and command execution. Have the helper select the appropriate context-injection
function based on the program, while preserving unchanged behavior for other
programs and both logged and output-capturing execution paths.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e496c838-001d-41dd-ba0b-9e88e832a36b
📒 Files selected for processing (22)
.github/workflows/on-pr-kind-smoke.yamlREADME.mdsrc/commands/auth/bootstrap.rssrc/commands/config/install.rssrc/commands/local/backend/colima.rssrc/commands/local/backend/dory.rssrc/commands/local/backend/kind.rssrc/commands/local/backend/mod.rssrc/commands/local/destroy.rssrc/commands/local/doctor.rssrc/commands/local/install.rssrc/commands/local/mod.rssrc/commands/local/package_install.rssrc/commands/local/reset.rssrc/commands/local/resize.rssrc/commands/local/start.rssrc/commands/local/stop.rssrc/commands/local/uninstall.rssrc/commands/provider/install.rssrc/commands/secrets/list.rssrc/commands/vars/mod.rssrc/commands/vars/sync.rs
| /// Install the local cluster backend (colima or kind) via Homebrew | ||
| Install, | ||
| /// Reset local Colima Kubernetes state | ||
| /// Reset local Kubernetes state (colima: k8s reset; kind: recreate cluster) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Doc comments omit Dory backend.
The Install and Reset variant doc comments mention only "colima or kind" but the Backend enum now includes Dory. Update to reflect all supported backends.
📝 Proposed doc updates
- /// Install the local cluster backend (colima or kind) via Homebrew
+ /// Install the local cluster backend (colima, kind, or dory)
Install,
- /// Reset local Kubernetes state (colima: k8s reset; kind: recreate cluster)
+ /// Reset local Kubernetes state (colima: k8s reset; kind: recreate cluster; dory: recreate cluster)
Reset,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /// Install the local cluster backend (colima or kind) via Homebrew | |
| Install, | |
| /// Reset local Colima Kubernetes state | |
| /// Reset local Kubernetes state (colima: k8s reset; kind: recreate cluster) | |
| /// Install the local cluster backend (colima, kind, or dory) | |
| Install, | |
| /// Reset local Kubernetes state (colima: k8s reset; kind: recreate cluster; dory: recreate cluster) | |
| Reset, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/commands/local/mod.rs` around lines 114 - 116, Update the doc comments
for the Install and Reset variants in the Backend enum to include Dory alongside
colima and kind, accurately documenting all supported local backends while
preserving the existing operation descriptions.
Move all colima-specific operations behind a Backend enum in src/commands/local/backend/ and rename ColimaSizeArgs to SizeArgs. Registry wiring (hosts sync) now flows through backend::wire_local_registry so provider/config installs stay backend-agnostic. No behavior change: identical command invocations on the colima path. Implements [[tasks/cluster-backend-abstraction]] (phase 1)
Adds --backend <colima|kind> (global on hops local) with resolution order flag > persisted (~/.hops/local/backend) > detected cluster (colima wins for back-compat) > platform default (macOS colima, else kind). kind backend: create with a pinned 127.0.0.1:30500 port mapping for the in-cluster registry NodePort, docker start/stop of the node container for resume, destroy/reset via kind delete + recreate, and containerd certs.d hosts.toml aliases (both registry names -> registry ClusterIP over HTTP) written idempotently after the registry deploys. Sizing flags error on kind. HOPS_KUBE_CONTEXT is auto-set from the backend when --context is absent. Preflight enforces kind >= 0.27 (certs.d config_path default). Implements [[tasks/cluster-backend-abstraction]] (phase 2)
GH Actions workflow exercises the CI acceptance path on ubuntu-latest: hops local start --backend kind, doctor, a registry round-trip through both pull names (Service name and localhost:30500), the stop/start resume path via the persisted backend, and destroy. README documents backend selection, persistence/resolution order, and the dory-via-kind recipe. Implements [[tasks/cluster-backend-abstraction]] (phases 3-4)
* feat: native dory backend for hops local Backend::Dory drives dory's built-in k3s headlessly via the dory CLI (k8s enable/disable/status) and the engine docker socket: - start: writes ~/.dory/k8s/registries.yaml (k3s-native trust aliasing both registry pull names to the Service hostname over HTTP; read at boot via dory's bind mount), then 'dory k8s enable --publish 30500:30500'. The Dory app's port forwarder makes the published NodePort host-reachable. - wire_registry: syncs hostname -> ClusterIP in the node's /etc/hosts per start (in-place rewrite — /etc/hosts is a bind mount, sed -i fails). - stop/resume via docker stop/start of dory-k8s; destroy/reset via dory k8s disable (+ enable); sizing flags error (VM sized in the app). - kube_context 'dory' (dory names its kubeconfig context that); hops prepends ~/.kube/dory-config to KUBECONFIG for its kubectl/helm children. - detection order: colima > kind > dory. Also: 'helm repo update crossplane-stable' instead of bare update — a stale unrelated repo in the user's helm config no longer breaks start. Verified end-to-end locally: start (Crossplane 2.3.3 + providers + registry), doctor all green, registry round-trip through BOTH pull names to pod Ready, stop/start resume, doctor green again. Implements [[tasks/dory-native-backend]] * fix: harden dory integration contract (#74) * fix: harden dory integration contract [[tasks/rr-2-dory-contract]] * fix: skip KUBECONFIG plumbing when dory context is already merged Current dory merges the dory context into ~/.kube/config at enable time, so hops no longer needs to mutate KUBECONFIG for child processes; the side-file prepend remains as a fallback for pre-merge dory versions. Implements [[tasks/dory-kubeconfig-merge]] * fix: hold dory cluster through the app's engine provisioning window Launching Dory.app re-provisions its engine for ~90s and restarts dockerd in the VM at the end, SIGTERMing every container — a k3s node enabled during that window reports Ready and then dies mid-bootstrap. The engine socket's mtime marks the session start, so when it is younger than 180s, start/reset now watch the node until the window passes and re-enable it (up to 3x) if the engine restart takes it down. Steady-state starts pay one container inspect. Implements [[tasks/rr-2-dory-contract]] * fix: unify kube context/backend targeting (#75) [[tasks/rr-1-context-targeting]]
Match main's on-pr-quality trigger so stacked PRs into this branch also get kind-smoke and quality.
4297cd2 to
29b3db5
Compare
* ci: add macOS colima backend smoke workflow Mirror kind smoke on macos-15-intel with nested-virt pin, --backend colima, and kubectl --context colima. Implements [[tasks/hops-cli-colima-macos-smoke]] under [[tasks/hops-cli-macos-backend-smoke]]. * test: structural checks for colima macOS smoke workflow Assert the shipped on-pr-colima-smoke.yaml pins macos-15-intel and mirrors kind smoke without a full GHA run. * ci: run colima smoke for all PR base branches Drop pull_request.branches: [main] so this job runs on stacked PRs into feat/cluster-backend-abstraction. * ci: size colima smoke VM for macos-15-intel runners Hops defaults (8 CPU / 16 GiB / 60 GiB) exceed standard GHA intel runners (~4 / ~14), so VZ hostagent exits during start. Pass explicit smaller sizes and dump lima ha.stderr on failure. * ci: give colima smoke enough memory for Crossplane 2 CPU / 4 GiB booted Colima but helm --wait for Crossplane hit 5m with Available: 0/1. Use 3/8 on macos-15-intel (~14 GiB host) and raise Crossplane helm --wait timeout to 10m for nested virt. * fix: wait longer for Crossplane on nested-virt colima Helm --wait hit 10m with Available:0/1 on GHA macos-15-intel. Apply the chart without --wait, poll deployments for ~15m with periodic pod dumps, wait for nodes Ready after docker restart, and capture cluster state before destroy on CI failure. * fix: harden kubectl apply under nested-virt API load Crossplane came Ready on GHA colima but ProviderConfigs apply failed with openapi schema download timeouts. Apply with --validate=false, retry transient failures, and re-wait for the API after Crossplane becomes leader. Soft-wait rbac-manager when core is healthy. * fix: wait for Established CRDs and room for registry PVC ProviderConfig apply raced CRD discovery; wait for Established. Registry PVC requests 20Gi — give colima smoke a 40Gi VM disk so local-path can bind, and wait longer with diagnostics for registry. * ci: recover colima stop/start resume after VM container churn After colima stop/start, pods Error on missing docker containers. Wait longer for Available and rollout-restart crossplane/registry if the first wait stalls. * fix: survive apiserver overload when applying registry Nested-virt colima CI loses the apiserver (TLS timeouts) right after Crossplane/provider install. Wait longer for /readyz, retry kubectl apply with API re-probes, and pre-pull registry:2 before deploy. * fix(ci): give colima smoke more RAM and settle time for registry pulls At 3/8/40, dual-name registry smoke pods sat in ContainerCreating without IPs while CoreDNS and metrics-server thrashed. Bump VM memory to 10Gi, wait for node/CoreDNS before the push, extend Ready timeout, and dump smoke pod describe on failure.
Summary
hops localnow supports three backends behind the same commands:How it works
--backend <colima|kind>onhops local; persisted to~/.hops/local/backendon successful start. Resolution: flag > persisted > existing-cluster detection (colima wins for back-compat) > platform default (macOS colima, else kind).HOPS_KUBE_CONTEXTauto-set from the backend (colima/kind-hops) when--contextis absent.127.0.0.1:30500 -> 30500; stop/start resume viadocker stop/startof the node container; reset recreates the cluster.hosts.tomlfiles alias both pull names (registry.crossplane-system.svc.cluster.local:5000andlocalhost:30500, which provider runtime pods use) to the registry Service ClusterIP over HTTP. Written idempotently after the registry deploys; survives node restarts; no containerd restart needed (requires kind >= 0.27, enforced in preflight).resizefail with actionable errors on kind.provider install/config installregistry wiring flows through the same backend seam (was hardcodedcolima sshhosts sync).dory gets no native backend (only 6443 published, no CLI, trust wiped on recreate — see task doc); dory users run the kind backend against dory's docker socket. Documented in README.
Commits
refactor:extract backend seam, colima-only, no behavior changefeat:kind backend + flag/persistence/detectionci+docs:kind smoke workflow + READMETest output
cargo test: 116 passed (new: resolution order, kind config gen, hosts.toml content, kind version parse, sizing rejection)hops local doctoron a running colima cluster resolves backend to colima via detection and targets contextcolima;hops local resize --backend kindandstart --backend kind --cpus 4fail with the intended errors.kind backend smokeworkflow in this PR (doctor + dual-name registry round-trip + stop/start resume + destroy).Implements [[tasks/cluster-backend-abstraction]]
🤖 Generated with Claude Code
https://claude.ai/code/session_011Ei12zcYdwvUoZ3jCbwiWf
Summary by CodeRabbit
New Features
Documentation
Bug Fixes