Skip to content
Open
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
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ jobs:
- Bookworm
- Bullseye
- Bionic
- Flatcar
- Focal
- Jammy
- Noble
Expand All @@ -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
Expand Down
35 changes: 35 additions & 0 deletions docs/examples/flatcar-sysext.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# syntax=ghcr.io/project-dalec/dalec/frontend@sha256:60be1bad349ae49aeea971c0e46edee0c9f7926e4d62066e4e981c45289f6e10

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
17 changes: 17 additions & 0 deletions targets/linux/flatcar/flatcar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ 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"
)

func TestSysextEnvDefaults(t *testing.T) {
Expand All @@ -19,3 +21,18 @@ func TestSysextEnvDefaults(t *testing.T) {
t.Fatalf("unexpected env\n got: %#v\nwant: %#v", got, want)
}
}

func TestDefaultConfigUsesNobleWorker(t *testing.T) {
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")
}

if _, ok := DefaultConfig.Base.(workerHandler); !ok {
t.Fatalf("expected default Flatcar base to provide a worker target")
}
}
155 changes: 155 additions & 0 deletions test/target_flatcar_test.go
Original file line number Diff line number Diff line change
@@ -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
}
38 changes: 38 additions & 0 deletions website/docs/system-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,44 @@ 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
```

Example output:

```text
flatcar-hello
Comment on lines +64 to +73
```

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
Expand Down
1 change: 1 addition & 0 deletions website/docs/targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down