Skip to content

feat(recipes): add the bundled catalog rule and its three common recipes - #100

Merged
NovusEdge merged 20 commits into
mainfrom
feat/recipe-catalog
Sep 6, 2026
Merged

feat(recipes): add the bundled catalog rule and its three common recipes#100
NovusEdge merged 20 commits into
mainfrom
feat/recipe-catalog

Conversation

@NovusEdge

@NovusEdge NovusEdge commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Closes #84.

The bundled catalog had no rule for what belongs in it. This adds one, in
docs/recipes/catalog.md, and then adds the three recipes the rule admits.

The rule

A task that exists on every guest gets one recipe with a script per OS family,
not one recipe per OS. A recipe earns its place by needing real OS-specific
behaviour: a package group, a pattern, or a different init system. A package
name that differs is not enough on its own. At most three OS-specific recipes
per guest, for tasks with no counterpart elsewhere.

The page also lists the OS-specific candidates the rule rejects and says why.
Four of the five overlap a common recipe. Only the SELinux entry is separate,
and it needs a decision about whether a permissive-mode VM benefits from it.

The three recipes

build-deps installs what building someone else's source tree needs. The RPM
guests get the @development-tools group, openSUSE gets the devel_basis
pattern, Arch gets base-devel. Those three are the OS-specific behaviour the
recipe exists for. Outputs: compiler, make, pkg_config.

service-tools installs what inspecting a running service needs. It derives
service_manager in the guest rather than from a package: systemctl means
systemd, rc-status means openrc, neither is a named failure. Outputs:
service_manager, lsof, strace.

pkg-tools installs what querying the package manager needs beyond install and
remove. Outputs: manager, query_tool.

All three run on all eight guests, declare schema 3 outputs and a health check,
and name no user account.

Four defects the live matrix found

stoat up hung on Fedora 44. cloud-init 25.3 keeps /run/cloud-init at mode
0700, so the unprivileged cloud-init status --wait never reports a result.
cloud-init finished at 19:33 in the guest and stoat was still waiting at 19:43.
Stoat now polls cloud-init status on its own deadline and retries a probe
that returns no JSON under the guest's escalation. A running cloud-init is now
a reason to wait rather than a failure.

The service-tools health check failed on Fedora with exit 141. systemctl
runs its pager even when stdout is not a terminal, and the check saw SIGPIPE.
The check passes --no-pager --no-legend.

build-deps failed on AlmaLinux 9 with "Module or Group 'development-tools' is
not available". dnf5 on Fedora names that group development-tools and dnf4 on
AlmaLinux 9 and Rocky 9 names it development. Asked on the guest,
@development installs and @development-tools exits 1. The RPM script asks
for the first id and falls back to the second.

The pkg-tools health check failed on Ubuntu. apt-file has no --version
and exits 2 on it. The check probes apt-file with --help.

Live evidence

One VM per guest, three recipes selected at create, one attempt per row, every
phase retained under catalog-recipes/live/f/.

Run f, binary sha256 8ee01d14dd014d1f, built at d113453 with vcs.modified=false.

Guest build-deps service-tools pkg-tools service_manager manager query_tool
almalinux-9 ok ok ok systemd dnf repoquery
alpine-cloud ok ok ok openrc apk apk
arch-cloud ok ok ok systemd pacman pacman
debian-13 ok ok ok systemd apt-get apt-file
fedora-cloud ok ok ok systemd dnf repoquery
opensuse-leap-16.0 ok ok ok systemd zypper zypper
rocky-9 ok ok ok systemd dnf repoquery
ubuntu-24.04 ok ok ok systemd apt-get apt-file

Each row created the VM, waited for it to be reachable and healthy, then
checked that the recipe outputs resolve to executables in the guest, that the
compiler builds and runs a C program, that the service manager answers a status
query, that lsof and strace run, that a second apply plans a skip for all
three recipes, and that the applied timestamps and outputs are unchanged after
it. Every row then stopped its VM and confirmed the stopped state.

Tests

Each recipe has a contract test on the manifest and a hermetic per-family test
asserting the exact package request, using the fake package managers already in
internal/recipes/samples_test.go. Every bundled-script test now runs on a
hermetic PATH, so a test proves what the script does rather than what the test
host happens to have installed. service-tools covers both service managers
and the no-manager failure. pkg-tools covers the manager resolution order
for all five package managers. The readiness change has a test for a guest
whose unprivileged probe cannot read the run directory.

Follow-up, out of scope here

Alpine's setup-apkrepos retry loop is now copied in four recipes. The shared
thing is Alpine's guest prelude stoat_pkg_setup, which runs apk update and
is not what any recipe wants. Moving the loop there removes the exception the
Alpine scripts carry today.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SXipad3xRhe72PMkoyjBiB

Summary by CodeRabbit

  • New Features

    • Added bundled recipes for build dependencies, service inspection, and package-manager tooling across supported Linux distributions.
    • Recipes report detected tools and service managers and include health checks.
    • Expanded the bundled recipe catalog and command-line listings.
  • Bug Fixes

    • Improved stoat up cloud-init readiness handling with deadline-aware polling and permission-aware retries, preventing hangs and premature failures.
  • Documentation

    • Added catalog documentation covering bundled recipes, supported systems, outputs, health checks, and qualification criteria.

Issue #84 asks for a reviewed candidate table before implementation. This
records what is bundled, the rule that a task existing on every guest gets
one recipe with a script per family, and the OS-specific candidates that
remain unselected.

Four of the five OS-specific candidates overlap a common recipe, which is
the rule working. Only the SELinux entry stands apart.

Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Both branches added a recipe to the same enumeration maps and to
samples_test.go. The resolution keeps both entries in each map and both
test functions.

Signed-off-by: NovusEdge <novusedge0@gmail.com>
…check

Signed-off-by: NovusEdge <novusedge0@gmail.com>
service-tools reported healthy without asking the service manager
anything, and build-deps reported unhealthy on a guest that provides
pkg-config only under the name pkgconf.

Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
cloud-init 25.3 keeps /run/cloud-init at mode 0700. The seeded account
cannot read the run directory, so `cloud-init status --wait` never
reports a result and `stoat up` waits until its caller gives up. Fedora
44 ships that version.

Stoat now polls `cloud-init status` on its own deadline, and retries a
probe that returns no JSON under the guest's escalation. A running
cloud-init is a reason to wait rather than a failure.

Signed-off-by: NovusEdge <novusedge0@gmail.com>
Signed-off-by: NovusEdge <novusedge0@gmail.com>
systemctl runs its pager even when stdout is not a terminal, and the
check saw SIGPIPE as exit 141 on Fedora 44.

Signed-off-by: NovusEdge <novusedge0@gmail.com>
dnf5 on Fedora names the group development-tools. dnf4 on AlmaLinux 9
and Rocky 9 names it development and fails the transaction on the other
id.

Signed-off-by: NovusEdge <novusedge0@gmail.com>
apt-file has no --version and exits 2 on it, so the pkg-tools health
check failed on Ubuntu and Debian.

Signed-off-by: NovusEdge <novusedge0@gmail.com>
@NovusEdge NovusEdge self-assigned this Sep 6, 2026
@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Team

Run ID: a9ccc650-934c-466e-a710-519c8a883318

📥 Commits

Reviewing files that changed from the base of the PR and between ae801cd and d113453.

📒 Files selected for processing (28)
  • CHANGELOG.md
  • CONTRIBUTING.md
  • docs/recipes/catalog.md
  • docs/recipes/overview.md
  • docs/reference/cli.md
  • internal/cli/subcommands_test.go
  • internal/recipes/bundled/build-deps/install-alpine.sh
  • internal/recipes/bundled/build-deps/install-arch.sh
  • internal/recipes/bundled/build-deps/install-debian.sh
  • internal/recipes/bundled/build-deps/install-rpm.sh
  • internal/recipes/bundled/build-deps/install-zypper.sh
  • internal/recipes/bundled/build-deps/recipe.toml
  • internal/recipes/bundled/pkg-tools/install-alpine.sh
  • internal/recipes/bundled/pkg-tools/install-arch.sh
  • internal/recipes/bundled/pkg-tools/install-debian.sh
  • internal/recipes/bundled/pkg-tools/install-rpm.sh
  • internal/recipes/bundled/pkg-tools/install-zypper.sh
  • internal/recipes/bundled/pkg-tools/recipe.toml
  • internal/recipes/bundled/service-tools/install-alpine.sh
  • internal/recipes/bundled/service-tools/install-arch.sh
  • internal/recipes/bundled/service-tools/install-debian.sh
  • internal/recipes/bundled/service-tools/install-rpm.sh
  • internal/recipes/bundled/service-tools/install-zypper.sh
  • internal/recipes/bundled/service-tools/recipe.toml
  • internal/recipes/recipes_test.go
  • internal/recipes/samples_test.go
  • internal/sshx/cloudinit_readiness_test.go
  • internal/sshx/sshx.go

Walkthrough

The change adds three bundled recipes for build dependencies, service inspection, and package ownership across supported guest families. It also changes cloud-init readiness from blocking status waiting to deadline-aware polling with escalation for unreadable status data.

Changes

Bundled recipe catalog

Layer / File(s) Summary
Recipe contracts and catalog documentation
internal/recipes/bundled/*/recipe.toml, docs/recipes/*
Defines schema 3 metadata, OS-specific scripts, outputs, health checks, catalog limits, and qualification rules for the three new recipes.
Build dependency provisioning
internal/recipes/bundled/build-deps/*, internal/recipes/samples_test.go
Installs build toolchains for each OS family, validates compiler tools, supports RPM group and pkg-config fallbacks, and tests package requests and outputs.
Service inspection provisioning
internal/recipes/bundled/service-tools/*, internal/recipes/samples_test.go
Installs service inspection tools, detects systemd or OpenRC, validates binaries, records outputs, and tests supported and unsupported service-manager cases.
Package ownership tooling
internal/recipes/bundled/pkg-tools/*, internal/recipes/samples_test.go
Installs package query tools, resolves package managers in priority order, records outputs, and tests each OS family.
Bundled recipe integration and references
internal/recipes/*, internal/cli/subcommands_test.go, CONTRIBUTING.md, docs/reference/cli.md, CHANGELOG.md
Updates embedded bundle checks, CLI examples, contributor guidance, and changelog entries for the expanded catalog.

Cloud-init readiness polling

Layer / File(s) Summary
Deadline-aware cloud-init polling
internal/sshx/sshx.go
Replaces cloud-init status --wait with JSON probes, bounded polling, caller deadlines, transport-error handling, and escalated retries.
Cloud-init readiness tests
internal/sshx/cloudinit_readiness_test.go, CHANGELOG.md
Tests deadline behavior, direct status probes, and sudo escalation when the unprivileged probe cannot read cloud-init state.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant waitCloudInit
  participant SSH
  participant cloud-init
  waitCloudInit->>SSH: Run cloud-init status --format json
  SSH->>cloud-init: Execute status probe
  cloud-init-->>SSH: Return status JSON
  SSH-->>waitCloudInit: Return probe result
  waitCloudInit->>SSH: Retry with sudo when needed
  waitCloudInit-->>waitCloudInit: Poll until ready or deadline
Loading

Poem

A rabbit packs tools in a bundle so neat
Build paths and service clues hop into place
Package finders follow each trail
Cloud-init checks time without fail
Sudo lends a paw when permissions prevail

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/recipe-catalog

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@NovusEdge NovusEdge added recipes bundled or index recipes enhancement New feature go Pull requests that update go code labels Sep 6, 2026
@NovusEdge
NovusEdge merged commit fab47c0 into main Sep 6, 2026
7 of 9 checks passed
@NovusEdge
NovusEdge deleted the feat/recipe-catalog branch September 6, 2026 18:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature go Pull requests that update go code recipes bundled or index recipes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a bounded OS-specific developer recipe catalog

1 participant