Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions internal/cloudinit/cloudinit.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ func extraPackages(osName string) string {
func userData(v *config.VM, pubkey string, recipeBodies []string) (string, error) {
base := fmt.Sprintf(userDataTemplate, guestShell(v.OS), pubkey, consolePasswordBlock(v.ConsolePassword))

docs := []string{base, mountsDoc(v)}
docs := []string{base}
if m := mountsDoc(v); m != "" {
docs = append(docs, m)
}
if extra := extraPackages(v.OS); extra != "" {
docs = append(docs, extra)
}
Expand All @@ -116,11 +119,20 @@ func userData(v *config.VM, pubkey string, recipeBodies []string) (string, error
// QEMU command line with nothing to mount them, so the share silently did
// nothing.
//
// nofail is required: some cloud kernels ship no 9p module (Debian's does
// not), and without nofail an unmountable share holds up boot. The host
// mount is ro, matching what QEMU enforces, so a write fails immediately
// debian's cloud kernel (deb13-cloud) ships no 9p module, so the mount can
// never succeed and cloud-init's mounts module marks the whole seed as
// errored on every boot. Skip the mounts for debian; the 9p share does not
// work there.
// ponytail: keyed on the one bundled image without 9p. Add another OS here if
// the catalog gains a second 9p-less image.
//
// nofail keeps a share that drops out at runtime from holding up boot. The
// host mount is ro, matching what QEMU enforces, so a write fails immediately
// instead of after a remount that appears to succeed.
func mountsDoc(v *config.VM) string {
if v.OS == "debian" {
return ""
}
const opts = "trans=virtio,version=9p2000.L,%s,_netdev,nofail"
var b strings.Builder
b.WriteString("#cloud-config\nmounts:\n")
Expand Down
34 changes: 28 additions & 6 deletions internal/cloudinit/cloudinit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ import (
// tests can inspect individual documents instead of grepping the whole
// rendered file.

// withoutMounts returns the archive's documents minus the 9p mounts document,
// and fails if there isn't exactly one. Every VM gets a mounts document, so
// tests about the base and recipe documents filter it out instead of counting
// around it.
// withoutMounts returns the archive's documents minus the 9p mounts document.
// Every VM except debian gets one mounts document; debian's cloud kernel has
// no 9p module, so it gets none. The helper accepts zero or one and fails on
// more, so tests about the base and recipe documents filter it out instead of
// counting around it.
func withoutMounts(t *testing.T, ud string) []archiveDoc {
t.Helper()
var rest []archiveDoc
Expand All @@ -32,8 +33,8 @@ func withoutMounts(t *testing.T, ud string) []archiveDoc {
}
rest = append(rest, d)
}
if found != 1 {
t.Fatalf("want exactly one mounts document, got %d:\n%s", found, ud)
if found > 1 {
t.Fatalf("want at most one mounts document, got %d:\n%s", found, ud)
}
return rest
}
Expand Down Expand Up @@ -445,6 +446,27 @@ func TestSeedInstallsSudoWhereItIsMissing(t *testing.T) {
}
}

// debian's cloud kernel has no 9p module, so the seed omits the mounts
// document. A present-but-unmountable 9p entry made cloud-init report the
// whole seed as errored on every boot. A 9p-capable OS still gets the mounts.
func TestSeedSkipsMountsOnDebian(t *testing.T) {
deb, err := userData(&config.VM{Name: "vm", OS: "debian"}, testPubkey, nil)
if err != nil {
t.Fatal(err)
}
if strings.Contains(deb, "mounts:") {
t.Errorf("debian seed carries a 9p mounts document:\n%s", deb)
}

arch, err := userData(&config.VM{Name: "vm", OS: "arch"}, testPubkey, nil)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(arch, "mounts:") {
t.Errorf("arch seed is missing its 9p mounts document:\n%s", arch)
}
}

// Recipe fragments must still merge after the base block, whatever the OS:
// this is the existing contract and the reason the cloud-config-archive
// exists (see buildArchive).
Expand Down
7 changes: 4 additions & 3 deletions internal/recipes/bundled/docker/install-fedora.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
# Fedora VM.
set -e

# Add Docker's official repository
dnf -y install dnf-plugins-core
dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
# dnf5 (Fedora 41+) dropped `config-manager --add-repo`. Write the repo file
# directly; that works on both dnf4 and dnf5.
curl -fsSL https://download.docker.com/linux/fedora/docker-ce.repo \
-o /etc/yum.repos.d/docker-ce.repo

dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

Expand Down
Loading