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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ require (
unikraft.com/x/guesstermwidth v0.0.0-20260813113709-544c471e0bc9
unikraft.com/x/iata v0.0.0-20260713183529-fd34645687a0
unikraft.com/x/image-spec v0.0.0-20260813113709-544c471e0bc9
unikraft.com/x/io v0.0.0-20260819084004-6de0d3f1ed2c
unikraft.com/x/joinerrgroup v0.0.0-20260304162956-523940cab1de
unikraft.com/x/kingkong v0.0.0-20260824095305-c69507b68d29
unikraft.com/x/kraftfile v0.0.0-20260522114044-e2da24d09716
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,8 @@ unikraft.com/x/iata v0.0.0-20260713183529-fd34645687a0 h1:wpRYHKQbrcJM4xSb8MyumZ
unikraft.com/x/iata v0.0.0-20260713183529-fd34645687a0/go.mod h1:M9v3TZKETfctR1aqFxM6UqYM+3DJ+nHKtqrxGb8X8oc=
unikraft.com/x/image-spec v0.0.0-20260813113709-544c471e0bc9 h1:S0Er9pKNkS4SdifkcXUAQ+P+ndMIBRMheyLZP8f6g9E=
unikraft.com/x/image-spec v0.0.0-20260813113709-544c471e0bc9/go.mod h1:7HwHLKMC6Ican+0HeMFVN8qI3mSyf0M64iEZj++FoPs=
unikraft.com/x/io v0.0.0-20260819084004-6de0d3f1ed2c h1:K/HL18OZWL/FeBC3/TcuOaQePMTzmYw4CzaKHxjf+Uk=
unikraft.com/x/io v0.0.0-20260819084004-6de0d3f1ed2c/go.mod h1:k4MCTNUTf7yB9LhOpH4Vj0RecXirtVE3gZLUw5jOFQo=
unikraft.com/x/joinerrgroup v0.0.0-20260304162956-523940cab1de h1:cm4FnPvnahRIK0derbI+T4ds1LsD5CFeyyAvIqcOCek=
unikraft.com/x/joinerrgroup v0.0.0-20260304162956-523940cab1de/go.mod h1:XND1VvLxwqKFGrmdwUTWps4WEpMm7HTHPQg9HWQtrxg=
unikraft.com/x/kingkong v0.0.0-20260824095305-c69507b68d29 h1:C87AsE6sNHtSwM6b+F/8lAOotIDSu3vJ8oUva/xRC9g=
Expand Down
16 changes: 3 additions & 13 deletions internal/builder/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,19 +231,9 @@ func Build(ctx context.Context, opts BuildOpts) ([]*imagespec.Image, error) {
imgOpts = append(imgOpts, imagespec.WithInitrd(roots[i].Initrd))
roots[i].Initrd = nil
// The rootfs build may have produced a richer config (e.g. from
// a Dockerfile). Use it as the base and layer our overrides on top.
cfg = roots[i].Image.Config
if opts.Cmd != nil {
cfg.Cmd = opts.Cmd
}
if opts.Env != nil {
env := make([]string, 0, len(opts.Env))
for _, kv := range opts.Env {
env = append(env, fmt.Sprintf("%s=%s", kv.Key, kv.Value))
}
cfg.Env = append(env, cfg.Env...)
}
cfg.Labels = opts.Labels
// a Dockerfile or an OCI image). Use it as the base and layer our
// overrides on top.
cfg = applyConfigOverrides(roots[i].Image.Config, opts)
}
imgOpts = append(imgOpts, imagespec.WithImageConfig(cfg))

Expand Down
27 changes: 7 additions & 20 deletions internal/builder/kraftfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
package builder

import (
"cmp"
"fmt"
"path/filepath"

ocispec "github.com/opencontainers/image-spec/specs-go/v1"

Expand Down Expand Up @@ -58,22 +56,17 @@ func KraftfileToBuildOpts(dir string, kf *kraftfile.Kraftfile) (BuildOpts, error
if rom.Source == nil || rom.Source.Path == "" {
return BuildOpts{}, fmt.Errorf("rom entry is missing a source path")
}
romPath := filepath.Join(dir, rom.Source.Path)
romFormat := cmp.Or(rom.Format, kraftfile.FsTypeErofs)
romFormat := defaultRomFormat(rom.Format, rom.Source.Type)
romOpt := FSOpts{
Path: romPath,
Path: rom.Source.Path,
Format: romFormat,
Type: rom.Source.Type,
// Pad the file to page-size alignment. This is required by the platform
// which rejects ROM files that are not page-aligned.
Pad: 4096,
}
if romOpt.Type == "" {
typ, err := DetectSourceType(romPath)
if err != nil {
return BuildOpts{}, fmt.Errorf("detecting rom type for %q: %w", romPath, err)
}
romOpt.Type = typ
if err := resolveSource(dir, &romOpt); err != nil {
return BuildOpts{}, fmt.Errorf("resolving rom source %q: %w", rom.Source.Path, err)
}
opts.Roms = append(opts.Roms, romOpt)
}
Expand All @@ -82,18 +75,12 @@ func KraftfileToBuildOpts(dir string, kf *kraftfile.Kraftfile) (BuildOpts, error
if kf.Rootfs.Source == nil || kf.Rootfs.Source.Path == "" {
return BuildOpts{}, fmt.Errorf("rootfs entry is missing a source path")
}
opts.Rootfs.Path = filepath.Join(dir, kf.Rootfs.Source.Path)
opts.Rootfs.Path = kf.Rootfs.Source.Path
opts.Rootfs.Format = kf.Rootfs.Format
opts.Rootfs.Type = kf.Rootfs.Source.Type
opts.Rootfs.Dockerfile = kf.Rootfs.Source.Dockerfile
if opts.Rootfs.Dockerfile != "" && opts.Rootfs.Type == "" {
Comment thread
craciunoiuc marked this conversation as resolved.
opts.Rootfs.Type = kraftfile.SourceTypeDockerfile
} else if opts.Rootfs.Type == "" {
typ, err := DetectSourceType(opts.Rootfs.Path)
if err != nil {
return BuildOpts{}, fmt.Errorf("detecting rootfs type for %q: %w", opts.Rootfs.Path, err)
}
opts.Rootfs.Type = typ
if err := resolveSource(dir, &opts.Rootfs); err != nil {
return BuildOpts{}, fmt.Errorf("resolving rootfs source %q: %w", kf.Rootfs.Source.Path, err)
}
}

Expand Down
104 changes: 84 additions & 20 deletions internal/builder/kraftfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
package builder

import (
"io/fs"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -48,7 +51,7 @@ func TestKraftfileToBuildOpts(t *testing.T) {
require.Equal(t, map[string]string{"label": "value"}, opts.Labels)
require.Equal(t, "unikraft.io/unikraft.org/base", opts.Runtime)
require.Equal(t, kraftfile.FsTypeErofs, opts.Rootfs.Format)
require.Equal(t, rootfsDir+"/Dockerfile", opts.Rootfs.Path)
require.Equal(t, filepath.Join(rootfsDir, "Dockerfile"), opts.Rootfs.Path)
require.Equal(t, kraftfile.SourceTypeDockerfile, opts.Rootfs.Type)
require.Len(t, opts.Platform, 1)
require.Equal(t, "x86_64", opts.Platform[0].Architecture)
Expand All @@ -60,43 +63,50 @@ func TestKraftfileToBuildOpts(t *testing.T) {
}, opts.Platform[0].OSFeatures)
}

func TestKraftfileToBuildOptsRootfsSourceError(t *testing.T) {
// TestKraftfileToBuildOptsResolvesSources asserts that every source leaves here
// resolved against the kraftfile directory and typed.
func TestKraftfileToBuildOptsResolvesSources(t *testing.T) {
rootfsDir := t.TempDir()
rootfsPath := "rootfs.tar"
romPath := filepath.Join(rootfsDir, "romdir")
require.NoError(t, os.Mkdir(romPath, 0o755))

runtime := kraftfile.Runtime("unikraft.io/unikraft.org/base")
kf := &kraftfile.Kraftfile{
Runtime: &runtime,
Rootfs: &kraftfile.FS{
Format: kraftfile.FsTypeCpio,
Format: kraftfile.FsTypeErofs,
Source: &kraftfile.FSSource{
Path: rootfsPath,
Path: "Dockerfile",
},
},
Roms: []kraftfile.FS{
{Source: &kraftfile.FSSource{Path: "romdir"}},
},
}

_, err := KraftfileToBuildOpts(rootfsDir, kf)
require.Error(t, err)
opts, err := KraftfileToBuildOpts(rootfsDir, kf)
require.NoError(t, err)
require.Equal(t, filepath.Join(rootfsDir, "Dockerfile"), opts.Rootfs.Path)
require.Equal(t, kraftfile.SourceTypeDockerfile, opts.Rootfs.Type)
require.Len(t, opts.Roms, 1)
require.Equal(t, romPath, opts.Roms[0].Path)
require.Equal(t, kraftfile.SourceTypeDirectory, opts.Roms[0].Type)
}

func TestKraftfileToBuildOptsRootfsPathJoined(t *testing.T) {
rootfsDir := t.TempDir()

// TestKraftfileToBuildOptsMissingSource asserts the fail-fast that resolving
// here buys: a bad path is reported before anything connects to BuildKit.
func TestKraftfileToBuildOptsMissingSource(t *testing.T) {
runtime := kraftfile.Runtime("unikraft.io/unikraft.org/base")
kf := &kraftfile.Kraftfile{
Runtime: &runtime,
Rootfs: &kraftfile.FS{
Format: kraftfile.FsTypeErofs,
Source: &kraftfile.FSSource{
Path: "Dockerfile",
},
Source: &kraftfile.FSSource{Path: "rootfs.tar"},
},
}

opts, err := KraftfileToBuildOpts(rootfsDir, kf)
require.NoError(t, err)
require.Equal(t, rootfsDir+"/Dockerfile", opts.Rootfs.Path,
"rootfs path must be joined with the kraftfile directory")
_, err := KraftfileToBuildOpts(t.TempDir(), kf)
require.ErrorIs(t, err, fs.ErrNotExist)
require.ErrorContains(t, err, "resolving rootfs source")
}

func TestKraftfileToBuildOptsDockerfileWithType(t *testing.T) {
Expand All @@ -117,7 +127,7 @@ func TestKraftfileToBuildOptsDockerfileWithType(t *testing.T) {

opts, err := KraftfileToBuildOpts(rootfsDir, kf)
require.NoError(t, err)
require.Equal(t, rootfsDir+"/context", opts.Rootfs.Path)
require.Equal(t, filepath.Join(rootfsDir, "context"), opts.Rootfs.Path)
require.Equal(t, "MyDockerfile", opts.Rootfs.Dockerfile)
require.Equal(t, kraftfile.SourceTypeDockerfile, opts.Rootfs.Type)
require.Equal(t, kraftfile.FsTypeErofs, opts.Rootfs.Format)
Expand All @@ -140,7 +150,7 @@ func TestKraftfileToBuildOptsDockerfileWithoutType(t *testing.T) {

opts, err := KraftfileToBuildOpts(rootfsDir, kf)
require.NoError(t, err)
require.Equal(t, rootfsDir+"/context", opts.Rootfs.Path)
require.Equal(t, filepath.Join(rootfsDir, "context"), opts.Rootfs.Path)
require.Equal(t, "MyDockerfile", opts.Rootfs.Dockerfile)
require.Equal(t, kraftfile.SourceTypeDockerfile, opts.Rootfs.Type,
"type must be inferred as dockerfile when dockerfile field is set")
Expand Down Expand Up @@ -172,3 +182,57 @@ func TestKraftfileToBuildOptsNoRootfs(t *testing.T) {
require.Equal(t, "x86_64", opts.Platform[0].Architecture)
require.Equal(t, "fc", opts.Platform[0].OS)
}

// TestKraftfileToBuildOptsRomOCIKeepsFormat verifies that a rom keeps the erofs
// default, except for an OCI source, which dictates its own format.
func TestKraftfileToBuildOptsRomOCIKeepsFormat(t *testing.T) {
rootfsDir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(rootfsDir, "rom.bin"), []byte("rom"), 0o644))

runtime := kraftfile.Runtime("unikraft.io/unikraft.org/base")
kf := &kraftfile.Kraftfile{
Runtime: &runtime,
Roms: []kraftfile.FS{
{Source: &kraftfile.FSSource{
Path: "index.docker.io/hello-world:latest",
Type: kraftfile.SourceTypeOCI,
}},
{Source: &kraftfile.FSSource{
Path: "rom.bin",
Type: kraftfile.SourceTypeTarball,
}},
},
}

opts, err := KraftfileToBuildOpts(rootfsDir, kf)
require.NoError(t, err)
require.Len(t, opts.Roms, 2)
require.Empty(t, opts.Roms[0].Format,
"an OCI rom must keep its own format rather than defaulting to erofs")
require.Equal(t, kraftfile.FsTypeErofs, opts.Roms[1].Format)
}

func TestKraftfileToBuildOptsRootfsOCIType(t *testing.T) {
rootfsDir := t.TempDir()

runtime := kraftfile.Runtime("unikraft.io/unikraft.org/base")
kf := &kraftfile.Kraftfile{
Runtime: &runtime,
Rootfs: &kraftfile.FS{
Format: kraftfile.FsTypeErofs,
Source: &kraftfile.FSSource{
Path: "index.docker.io/hello-world:latest",
Type: kraftfile.SourceTypeOCI,
},
},
Targets: []kraftfile.Target{
{Arch: "x86_64", Plat: "fc"},
},
}

opts, err := KraftfileToBuildOpts(rootfsDir, kf)
require.NoError(t, err)
require.Equal(t, "index.docker.io/hello-world:latest", opts.Rootfs.Path,
"OCI rootfs reference must not be joined with the kraftfile directory")
require.Equal(t, kraftfile.SourceTypeOCI, opts.Rootfs.Type)
}
Loading
Loading