Skip to content

feat(k8s): headless lifecycle CLI, extra ports, registries.yaml, merged dory kubeconfig context#18

Open
patrickleet wants to merge 9 commits into
Augani:mainfrom
patrickleet:feat/hops-local-integration
Open

feat(k8s): headless lifecycle CLI, extra ports, registries.yaml, merged dory kubeconfig context#18
patrickleet wants to merge 9 commits into
Augani:mainfrom
patrickleet:feat/hops-local-integration

Conversation

@patrickleet

Copy link
Copy Markdown

Adds four small, composable extension points to the built-in Kubernetes cluster so it can be driven and configured from scripts and CI — no GUI interaction required. Everything is opt-in; with no config files present and no CLI usage, behavior is unchanged.

1. Headless cluster lifecycle: dory k8s enable|disable|status

New CLI subcommands in scripts/dory manage the k3s cluster container directly against the engine socket, mirroring what the app's toggle does:

  • dory k8s enable [--publish HOST:CONTAINER[/proto]] [--recreate] — creates (or reuses/restarts) the dory-k8s container, waits for node Ready, and writes the kubeconfig. Port specs are validated (1–65535, tcp/udp).
  • dory k8s disable — removes the container and kubeconfig (and unmerges the dory context, see 4).
  • dory k8s status — reports container state, published ports, and config drift.

If the running container's actual state (published ports, bind mounts — checked via inspect, not a cached state file) no longer matches the requested config, enable exits with code 3 and a clear message instead of silently reusing a stale cluster or destructively recreating a running one. --recreate opts in to the recreate. The app's enable path throws the same drift error (K8sError.configDrift) and surfaces "disable and re-enable to apply".

2. Extra port publishings: ~/.dory/k8s/ports

One HOST:CONTAINER[/proto] per line (comments and blanks ignored). These are published on the cluster container at create, so the existing port forwarder makes them reachable on localhost — which makes k3s NodePorts host-reachable, e.g. 30500:30500 for an in-cluster registry.

3. Registry config: ~/.dory/k8s/registries.yaml

When present, this file is bind-mounted read-only to /etc/rancher/k3s/registries.yaml — k3s' native mirror/trust configuration, read at boot. Living on the host, it survives the cluster container being recreated. This is the standard way to let the cluster pull from private/insecure registries — and the mechanism behind the "push it to a registry the cluster can reach" guidance the provisioner docs added for #4.

4. dory context, merged into ~/.kube/config

k3s' generic default cluster/user/context is renamed to dory, and the context is merged into ~/.kube/config — so right after enable:

kubectl --context dory get pods -A

just works, with zero KUBECONFIG setup — same UX as docker-desktop, orbstack, and colima. Mechanics:

  • kubectl does the merge (the bundled copy, resolved via HostTools, so packaged installs need nothing installed): kubectl config view --flatten --raw over a main:side chain, written atomically at 0600. Dory never hand-rewrites the file holding your other clusters' credentials.
  • Your existing entries and current-context are always preserved (the main config leads the chain); stale dory entries are deleted before merging so fresh certs win after a cluster recreate.
  • disable unmerges: removes the dory context/cluster/user and unsets a dangling current-context.
  • No kubectl available, or the merge fails → the side file ~/.kube/dory-config remains the documented fallback (export KUBECONFIG=...), and the main config is left untouched.
  • Opt out entirely with dory k8s enable --no-kubeconfig-merge — keeps ~/.kube/config untouched and uses only the side file, for CI or deliberately isolated KUBECONFIG setups. disable's unmerge only deletes dory-named entries, so it's a safe no-op when the context was never merged.

⚠️ Migration note: anything referencing the old default context in ~/.kube/dory-config needs to switch to --context dory (and no longer needs the KUBECONFIG export when kubectl is present).

Both the app (KubernetesProvisioner) and the CLI read the same config files and share identical merge/drift semantics, so GUI- and script-managed clusters stay consistent.

Tests

  • DoryTests/KubernetesProvisionerImageTests.swift extended: port parsing/validation, structured create-body encoding, context rename (including YAML sequence items like - name: default), inspect-based exact-set drift coverage (removals included).
  • DoryTests/KubeContextHintTests.swift: merged and side-file hint forms.
  • Kubeconfig merge/unmerge verified live against real kubectl with fixture configs: stale dory entries replaced, foreign clusters + current-context preserved, dangling current-context unset on disable, first-ever ~/.kube/config created correctly.
  • Verified end to end: cluster create with published NodePort + registries.yaml bind, image pull from an in-cluster registry, stop/start resume, drift detection on config change.
  • Rebased onto current main (dory-hv engine); full suite green with the CI recipe (test.sh -skip-testing:DoryUITests).

Summary by CodeRabbit

  • New Features

    • Added built-in Kubernetes enable/disable/status support with kubeconfig context merging.
    • Added support for extra port mappings and optional registry mount configuration when starting Kubernetes.
    • Added clearer cluster status reporting, including startup and drift conditions.
  • Bug Fixes

    • Improved kubeconfig handling so the Kubernetes context is explicitly targeted.
    • Better detects configuration changes and avoids silently reusing mismatched setups.
    • Updated user-facing error handling for configuration drift.

@patrickleet

Copy link
Copy Markdown
Author

Theres a bunch of review comments for this in patrickleet#1

- scripts/dory: 'k8s enable [--publish H:C[/proto]] [--image IMG]',
  'k8s disable', 'k8s status' drive the same dory-k8s container the app
  manages, against the engine socket — no GUI needed. Reuses an existing
  container when its create-time port bindings still match, else recreates.
- KubernetesProvisioner: reads ~/.dory/k8s/ports (one H:C[/proto] per line)
  into extra ExposedPorts/PortBindings alongside 6443 (published ports are
  auto-forwarded to localhost by the existing forwarder), bind-mounts
  ~/.dory/k8s/registries.yaml read-only to /etc/rancher/k3s/registries.yaml
  when present (k3s' native registry trust; host-side so it survives
  container recreation), and renames the kubeconfig's generic 'default'
  cluster/user/context to 'dory'.
- Tests extended: create-body shapes with/without extras, port line parsing,
  kubeconfig rename.

Follow-up for shipping: the release pipeline should bundle scripts/dory into
Dory.app/Contents/Resources and the cask gain a binary stanza.
[[tasks/rr-2-dory-contract]]
…names

enable() now throws K8sError.configDrift instead of silently returning when
the existing container's create-time config no longer matches, matching the
CLI's drift-is-an-error behavior; the app surfaces the same guidance message.
renameContextLine accepts a leading sequence marker so k3s' 'users: - name:
default' entry is renamed consistently with the context that references it.

Addresses coderabbitai review comments on PR #1.

Implements [[tasks/dory-pr1-review-fixes]]
Implements [[tasks/dory-pr1-review-fixes]]
kubectl --context dory now works with zero KUBECONFIG setup, matching
docker-desktop/orbstack/colima. kubectl itself performs the merge (dory
hand-rolls YAML and must never rewrite the user's kubeconfig): stale dory
entries are deleted first so fresh certs win KUBECONFIG precedence after a
recreate, the main config leads the chain so the user's current-context is
preserved, and the flattened result replaces ~/.kube/config atomically at
0600. disable unmerges (entries + dangling current-context). No kubectl on
the host, or a failed merge, falls back to the ~/.kube/dory-config side
file with the export hint, as before. The shell sed rename also gains the
sequence-item (- name: default) handling the Swift side got in 569f65e.

Implements [[tasks/dory-kubeconfig-merge]]
Merging the dory context into ~/.kube/config is the right default, but
CI and deliberately isolated KUBECONFIG setups have good reasons to keep
the main config untouched. The flag skips the merge and prints the
side-file export hint; disable's unmerge is already a no-op when the
context was never merged, so no state is needed.

Implements [[tasks/dory-kubeconfig-merge]]
@patrickleet
patrickleet force-pushed the feat/hops-local-integration branch from 0e9b49e to 7d0e434 Compare July 16, 2026 16:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant