Skip to content

feat: support Cluster API bootcmd and runcmd - #13

Draft
navaneeth-dev wants to merge 2 commits into
mainfrom
feat/runcmd-cluster-api
Draft

navaneeth-dev wants to merge 2 commits into
mainfrom
feat/runcmd-cluster-api

Conversation

@navaneeth-dev

@navaneeth-dev navaneeth-dev commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Adds bootcmd and runcmd support, so a Cluster API worker cloud-config can run its pre-kubeadm commands, kubeadm join, and post-kubeadm commands on Flatcar.

  • Only the shape Cluster API emits is accepted: a list of strings. Argument lists, non-strings, and a null stanza are rejected with path-aware errors.
  • Each stanza becomes a verbatim #!/bin/sh script at /opt/butane-init/<stanza>.sh (root, mode 0700) without set -e, matching cloud-init. A failing command does not stop later ones.
  • Scripts live in /opt, not /etc, because Flatcar 4757 mounts /etc as a noexec confext overlay.
  • bootcmd runs every boot through butane-init-bootcmd.service, after network.target and before network-online.target and runcmd. If it fails, runcmd still runs.
  • runcmd runs once per instance through butane-init-runcmd.service, after network-online.target. An instance is the Flatcar machine ID. The marker /var/lib/butane-init/sem/<machine-id>/runcmd is written before the commands run, so failed or interrupted runs are not retried, and flatcar-reset causes a rerun.
  • The units, and nothing else, get PATH with /opt/butane-init/bin:/opt/bin first, HOME=/root, and INSTANCE_ID=%m.
  • The two units and the cloud-init-per replacement are plain files under commands/, embedded with //go:embed; a test keeps every path they use reserved against write_files.
  • A generated cloud-init-per replacement (once|instance|always) is provided because Flatcar has no cloud-init. Commands are never rewritten, and no other cloud-init tools are provided.
  • Caveat: write_files content already exists when bootcmd runs, unlike cloud-init's first boot.
  • Caveat: output goes to the journal only; /var/log/cloud-init-output.log is not emulated.
  • Caveat: units have no start timeout, so a hanging command delays multi-user.target indefinitely.
  • /opt/butane-init, /var/lib/butane-init, the unit files and drop-in directories, their multi-user.target.wants symlinks, and Ignition's preset file are reserved, and write_files entries that target them are rejected. An oracle review found that the preset file and symlinks could otherwise disable the units.
  • Verified with Go tests, butane --strict, and a Flatcar 4757.2.1 QEMU boot plus reboot. The checks covered: unit ordering, the environment, no set -e, the Cluster API sentinel, runcmd skipped after a failed first run, bootcmd rerunning on every boot, and the cloud-init-per markers.
  • flatcar-reset --keep-paths /var/lib/butane-init was also tested: with a new machine ID, runcmd and cloud-init-per instance ran again, while cloud-init-per once did not. A reset without kept paths wipes the markers, so everything runs again.
  • cloud-init-per passes shellcheck -s sh.

@navaneeth-dev navaneeth-dev self-assigned this Oct 22, 2026
@navaneeth-dev navaneeth-dev added the kind/feature A feature request. label Oct 22, 2026
@navaneeth-dev
navaneeth-dev force-pushed the feat/runcmd-cluster-api branch from f8130d0 to cd6a0fe Compare October 22, 2026 14:22
@navaneeth-dev
navaneeth-dev added this pull request to stack #16 October 23, 2026 13:24
Base automatically changed from feat/ntp-cluster-api to main October 26, 2026 13:48
Generate verbatim /bin/sh scripts under /opt/butane-init and oneshot
units: bootcmd runs every boot before network-online.target, runcmd
runs once per machine ID after it. Provide a cloud-init-per replacement
and reserve the generated paths against write_files.

Signed-off-by: Navaneeth <me@rizexor.com>
Write the generated systemd units and the cloud-init-per replacement as
files that read exactly as they land on disk, loaded with go:embed,
instead of Go string constants assembled by concatenation. The generated
Butane output is unchanged. A test keeps the hard-coded paths reserved
against write_files.

Signed-off-by: Navaneeth <me@rizexor.com>
@navaneeth-dev
navaneeth-dev force-pushed the feat/runcmd-cluster-api branch from cd6a0fe to 262af91 Compare October 27, 2026 10:00
Copilot AI balanced review requested due to automatic review settings October 27, 2026 10:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/feature A feature request.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants