From daa12447df9ca8f6ccabc90c3d33ac541ec92545 Mon Sep 17 00:00:00 2001 From: Kartik Joshi Date: Wed, 3 Jun 2026 10:48:40 +0000 Subject: [PATCH 1/2] flatcar: document sysext usage Add a Flatcar sysext example and document the build, install, and version-pinning workflow for flatcar/testing/sysext. Add coverage that the default Flatcar target continues to use the Noble-backed worker path. Signed-off-by: Kartik Joshi --- docs/examples/flatcar-sysext.yml | 35 +++++++++++++++++++++++++++ targets/linux/flatcar/flatcar_test.go | 11 +++++++++ website/docs/system-extensions.md | 33 +++++++++++++++++++++++++ website/docs/targets.md | 1 + 4 files changed, 80 insertions(+) create mode 100644 docs/examples/flatcar-sysext.yml diff --git a/docs/examples/flatcar-sysext.yml b/docs/examples/flatcar-sysext.yml new file mode 100644 index 000000000..f5a171dd3 --- /dev/null +++ b/docs/examples/flatcar-sysext.yml @@ -0,0 +1,35 @@ +# syntax=ghcr.io/project-dalec/dalec/frontend:latest + +name: flatcar-hello +version: 0.1.0 +revision: 1 +packager: Dalec Example +vendor: Dalec Example +license: MIT +description: A small Flatcar-compatible system extension example. + +sources: + hello: + inline: + file: + permissions: 0o755 + contents: | + #!/bin/sh + echo "hello from a Flatcar system extension" + +build: + steps: + - command: | + install -D -m 0755 hello out/flatcar-hello + +artifacts: + binaries: + out/flatcar-hello: + subpath: "" + permissions: 0o755 + +tests: + - name: Check binary + files: + /usr/bin/flatcar-hello: + permissions: 0o755 diff --git a/targets/linux/flatcar/flatcar_test.go b/targets/linux/flatcar/flatcar_test.go index 9ae896ed2..a7b9a5a68 100644 --- a/targets/linux/flatcar/flatcar_test.go +++ b/targets/linux/flatcar/flatcar_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/project-dalec/dalec" + "github.com/project-dalec/dalec/targets/linux/deb/ubuntu" ) func TestSysextEnvDefaults(t *testing.T) { @@ -19,3 +20,13 @@ func TestSysextEnvDefaults(t *testing.T) { t.Fatalf("unexpected env\n got: %#v\nwant: %#v", got, want) } } + +func TestDefaultConfigUsesNobleWorker(t *testing.T) { + if DefaultConfig.Base != ubuntu.NobleConfig { + t.Fatalf("expected default Flatcar base to be Noble") + } + + if _, ok := DefaultConfig.Base.(workerHandler); !ok { + t.Fatalf("expected default Flatcar base to provide a worker target") + } +} diff --git a/website/docs/system-extensions.md b/website/docs/system-extensions.md index 8821aa0eb..2d0c63d7f 100644 --- a/website/docs/system-extensions.md +++ b/website/docs/system-extensions.md @@ -48,6 +48,39 @@ This creates `my-sysext-v0.1.0-1-azlinux3-x86-64.raw` in the current directory. The `--target=azlinux3` flag tells Dalec to source the packages from Azure Linux 3 repositories, but you can merge the extension against other distributions. +## Flatcar system extensions + +Dalec also includes a Flatcar target for system extensions. The Flatcar target +uses Flatcar-compatible metadata and emits an image named after the spec, so a +spec named `flatcar-hello` produces `flatcar-hello.raw`. + +```shell +docker buildx build -f docs/examples/flatcar-sysext.yml --target=flatcar/testing/sysext --output=type=local,dest=_out . +``` + +Copy the generated image to a Flatcar host under `/etc/extensions`: + +```shell +sudo mkdir -p /etc/extensions +sudo cp _out/flatcar-hello.raw /etc/extensions/flatcar-hello.raw +sudo systemd-sysext status +sudo systemd-sysext merge +flatcar-hello +``` + +By default, Dalec writes `ID=flatcar` and `SYSEXT_LEVEL=1.0` into the +`extension-release` metadata. To pin a sysext to a specific Flatcar release, +pass `DALEC_SYSEXT_OS_VERSION_ID`: + +```shell +docker buildx build \ + -f docs/examples/flatcar-sysext.yml \ + --target=flatcar/testing/sysext \ + --build-arg DALEC_SYSEXT_OS_VERSION_ID=4593.0.0 \ + --output=type=local,dest=_out \ + . +``` + ## Tips ### glibc compatibility diff --git a/website/docs/targets.md b/website/docs/targets.md index b09ef1ad0..5fdcab6fd 100644 --- a/website/docs/targets.md +++ b/website/docs/targets.md @@ -14,6 +14,7 @@ DALEC includes a number of built-in targets that you can either use in your spec - `azlinux3` - Azure Linux 3 - `azlinux4` - Azure Linux 4 (beta status) +- `flatcar` - Flatcar system extension target - `bullseye` - Debian 11 (Bullseye) (v0.11) - `bookworm` - Debian 12 (Bookworm) (v0.11) - `trixie` - Debian 13 (Trixie) (v0.next) From df497bee9e4ad11ef1fcdaa7dbb2ef028400ffa5 Mon Sep 17 00:00:00 2001 From: Kartik Joshi Date: Wed, 3 Jun 2026 18:19:44 +0000 Subject: [PATCH 2/2] flatcar: validate sysext output Signed-off-by: Kartik Joshi --- .github/workflows/ci.yml | 3 +- docs/examples/flatcar-sysext.yml | 2 +- targets/linux/flatcar/flatcar_test.go | 8 +- test/target_flatcar_test.go | 155 ++++++++++++++++++++++++++ website/docs/system-extensions.md | 5 + 5 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 test/target_flatcar_test.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61cac2005..a4b4ea298 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,6 +104,7 @@ jobs: - Bookworm - Bullseye - Bionic + - Flatcar - Focal - Jammy - Noble @@ -116,7 +117,7 @@ jobs: - other include: - suite: other - skip: Azlinux3|Azlinux4|Bookworm|Bullseye|Bionic|Focal|Jammy|Noble|Windows|Almalinux8|Almalinux9|Rockylinux8|Rockylinux9|Trixie + skip: Azlinux3|Azlinux4|Bookworm|Bullseye|Bionic|Flatcar|Focal|Jammy|Noble|Windows|Almalinux8|Almalinux9|Rockylinux8|Rockylinux9|Trixie steps: - name: Harden Runner diff --git a/docs/examples/flatcar-sysext.yml b/docs/examples/flatcar-sysext.yml index f5a171dd3..882237f5b 100644 --- a/docs/examples/flatcar-sysext.yml +++ b/docs/examples/flatcar-sysext.yml @@ -1,4 +1,4 @@ -# syntax=ghcr.io/project-dalec/dalec/frontend:latest +# syntax=ghcr.io/project-dalec/dalec/frontend@sha256:60be1bad349ae49aeea971c0e46edee0c9f7926e4d62066e4e981c45289f6e10 name: flatcar-hello version: 0.1.0 diff --git a/targets/linux/flatcar/flatcar_test.go b/targets/linux/flatcar/flatcar_test.go index a7b9a5a68..016f71c5c 100644 --- a/targets/linux/flatcar/flatcar_test.go +++ b/targets/linux/flatcar/flatcar_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/project-dalec/dalec" + "github.com/project-dalec/dalec/targets/linux/deb/distro" "github.com/project-dalec/dalec/targets/linux/deb/ubuntu" ) @@ -22,7 +23,12 @@ func TestSysextEnvDefaults(t *testing.T) { } func TestDefaultConfigUsesNobleWorker(t *testing.T) { - if DefaultConfig.Base != ubuntu.NobleConfig { + base, ok := DefaultConfig.Base.(*distro.Config) + if !ok { + t.Fatalf("expected default Flatcar base to use a deb distro config, got %T", DefaultConfig.Base) + } + + if base.ContextRef != ubuntu.NobleWorkerContextName || base.AptCachePrefix != ubuntu.NobleAptCachePrefix { t.Fatalf("expected default Flatcar base to be Noble") } diff --git a/test/target_flatcar_test.go b/test/target_flatcar_test.go new file mode 100644 index 000000000..6e4976c21 --- /dev/null +++ b/test/target_flatcar_test.go @@ -0,0 +1,155 @@ +package test + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/moby/buildkit/client/llb" + gwclient "github.com/moby/buildkit/frontend/gateway/client" + "github.com/moby/buildkit/util/stack" + ocispecs "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/project-dalec/dalec" + "github.com/project-dalec/dalec/frontend" + "github.com/project-dalec/dalec/targets/linux/flatcar" + "gotest.tools/v3/assert" + "gotest.tools/v3/assert/cmp" +) + +func TestFlatcar(t *testing.T) { + t.Parallel() + + ctx := startTestSpan(baseCtx, t) + + t.Run("sysext", func(t *testing.T) { + t.Parallel() + + ctx := startTestSpan(ctx, t) + testFlatcarSysext(ctx, t, nil, + []string{ + "ID=flatcar\n", + "ARCHITECTURE=x86-64\n", + "EXTENSION_RELOAD_MANAGER=1\n", + "SYSEXT_LEVEL=1.0\n", + }, + []string{ + "VERSION_ID=", + }, + ) + }) + + t.Run("sysext version id", func(t *testing.T) { + t.Parallel() + + ctx := startTestSpan(ctx, t) + testFlatcarSysext(ctx, t, + map[string]string{ + "DALEC_SYSEXT_OS_VERSION_ID": "4593.0.0", + }, + []string{ + "ID=flatcar\n", + "ARCHITECTURE=x86-64\n", + "EXTENSION_RELOAD_MANAGER=1\n", + "VERSION_ID=4593.0.0\n", + }, + []string{ + "SYSEXT_LEVEL=", + }, + ) + }) +} + +func testFlatcarSysext(ctx context.Context, t *testing.T, buildArgs map[string]string, want, notWant []string) { + t.Helper() + + platform := ocispecs.Platform{OS: "linux", Architecture: "amd64"} + spec := newSimpleSpec() + spec.Name = "flatcar-hello" + + testEnv.RunTest(ctx, t, func(ctx context.Context, gwc gwclient.Client) { + opts := []srOpt{ + withBuildTarget(flatcar.TargetKey + "/testing/sysext"), + withPlatform(platform), + withSpec(ctx, t, spec), + } + + for k, v := range buildArgs { + opts = append(opts, withBuildArg(k, v)) + } + + res := solveT(ctx, t, gwc, newSolveRequest(opts...)) + + ref, err := res.SingleRef() + if err != nil { + t.Fatal(err) + } + + expectedPath := "/" + spec.Name + ".raw" + if _, err := ref.StatFile(ctx, gwclient.StatRequest{Path: expectedPath}); err != nil { + t.Fatalf("expected Flatcar sysext image not found: %v", err) + } + + extracted := extractFlatcarSysext(ctx, t, gwc, ref, expectedPath, platform) + extractedRef, err := extracted.SingleRef() + if err != nil { + t.Fatal(err) + } + + if _, err := extractedRef.StatFile(ctx, gwclient.StatRequest{Path: "/usr/bin/foo"}); err != nil { + t.Fatalf("expected binary in Flatcar sysext not found: %v", err) + } + + metadataPath := fmt.Sprintf("/usr/lib/extension-release.d/extension-release.%s", spec.Name) + metadata := string(readFile(ctx, t, metadataPath, extracted)) + + for _, expected := range want { + assert.Check(t, cmp.Contains(metadata, expected), "expected metadata to contain %q:\n%s", expected, metadata) + } + for _, unexpected := range notWant { + assert.Check(t, !strings.Contains(metadata, unexpected), "expected metadata not to contain %q:\n%s", unexpected, metadata) + } + }) +} + +func extractFlatcarSysext(ctx context.Context, t *testing.T, gwc gwclient.Client, ref gwclient.Reference, expectedPath string, platform ocispecs.Platform) *gwclient.Result { + t.Helper() + + sOpt, err := frontend.SourceOptFromClient(ctx, gwc, &platform) + assert.NilError(t, err) + + pc := dalec.Platform(&platform) + state, err := ref.ToState() + if err != nil { + t.Fatal(err) + } + + output := flatcar.DefaultConfig.SysextWorker(sOpt, pc).Run( + llb.Args([]string{"fsck.erofs", "--extract=/output", "/input" + expectedPath}), + llb.AddMount("/input", state, llb.Readonly), + dalec.WithConstraints(pc), + ).AddMount("/output", llb.Scratch()) + + def, err := output.Marshal(ctx, pc) + if err != nil { + t.Fatalf("error marshalling Flatcar sysext extraction: %v", err) + } + + res, err := gwc.Solve(ctx, gwclient.SolveRequest{ + Definition: def.ToPB(), + Evaluate: true, + }) + if err != nil { + t.Fatalf("error extracting Flatcar sysext: %+v", stack.Formatter(err)) + } + + extractedRef, err := res.SingleRef() + if err != nil { + t.Fatal(err) + } + if err := extractedRef.Evaluate(ctx); err != nil { + t.Fatalf("error evaluating extracted Flatcar sysext: %+v", stack.Formatter(err)) + } + + return res +} diff --git a/website/docs/system-extensions.md b/website/docs/system-extensions.md index 2d0c63d7f..128b84949 100644 --- a/website/docs/system-extensions.md +++ b/website/docs/system-extensions.md @@ -65,6 +65,11 @@ sudo mkdir -p /etc/extensions sudo cp _out/flatcar-hello.raw /etc/extensions/flatcar-hello.raw sudo systemd-sysext status sudo systemd-sysext merge +``` + +Example output: + +```text flatcar-hello ```