Skip to content

Commit

Permalink
feat: Introduce support for erofs initrds
Browse files Browse the repository at this point in the history
Signed-off-by: Cezar Craciunoiu <[email protected]>
  • Loading branch information
craciunoiuc committed Dec 10, 2024
1 parent 8d321c3 commit 083dff7
Show file tree
Hide file tree
Showing 21 changed files with 273 additions and 51 deletions.
4 changes: 4 additions & 0 deletions initrd/directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ func (initrd *directory) Build(ctx context.Context) (string, error) {
return "", fmt.Errorf("could not create output directory: %w", err)
}

if initrd.opts.fsType == FsTypeErofs {
return initrd.opts.output, convertToErofs(ctx, initrd.opts.output, initrd.path, false, false)
}

f, err := os.OpenFile(initrd.opts.output, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
return "", fmt.Errorf("could not open initramfs file: %w", err)
Expand Down
14 changes: 8 additions & 6 deletions initrd/dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,12 +225,6 @@ func (initrd *dockerfile) Build(ctx context.Context) (string, error) {
initrd.opts.output = fi.Name()
}

outputDir, err := os.MkdirTemp("", "")
if err != nil {
return "", fmt.Errorf("could not make temporary directory: %w", err)
}
defer os.RemoveAll(outputDir)

tarOutput, err := os.CreateTemp("", "")
if err != nil {
return "", fmt.Errorf("could not make temporary file: %w", err)
Expand Down Expand Up @@ -516,6 +510,14 @@ func (initrd *dockerfile) Build(ctx context.Context) (string, error) {
return "", fmt.Errorf("could not create output directory: %w", err)
}

if initrd.opts.fsType == FsTypeErofs {
err := tarOutput.Close()
if err != nil && !strings.Contains(err.Error(), "file already closed") {
return "", fmt.Errorf("could not close tarball: %w", err)
}
return initrd.opts.output, convertToErofs(ctx, initrd.opts.output, tarOutput.Name(), true, false)
}

cpioFile, err := os.OpenFile(initrd.opts.output, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
return "", fmt.Errorf("could not open initramfs file: %w", err)
Expand Down
20 changes: 19 additions & 1 deletion initrd/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,25 @@ func (initrd *file) Name() string {
}

// Build implements Initrd.
func (initrd *file) Build(_ context.Context) (string, error) {
func (initrd *file) Build(ctx context.Context) (string, error) {
if initrd.opts.fsType == FsTypeErofs {
isCpio, err := isCpioFile(initrd.path)
if err != nil {
return "", fmt.Errorf("could not determine if file is a CPIO archive: %w", err)
}

if isCpio {
return "", fmt.Errorf("CPIO-to-EROFS conversion currently not supported. Use 'bsdcpio' or 'cpio' to unpack it first to a directory")
}

isTar, isGz, err := isTarGzFile(initrd.path)
if err != nil {
return "", fmt.Errorf("could not determine if file is a tar/gz archive: %w", err)
}

return initrd.opts.output, convertToErofs(ctx, initrd.opts.output, initrd.path, isTar, isGz)
}

return initrd.path, nil
}

Expand Down
4 changes: 4 additions & 0 deletions initrd/ociimage.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ func (initrd *ociimage) Build(ctx context.Context) (string, error) {
return "", fmt.Errorf("could not create output directory: %w", err)
}

if initrd.opts.fsType == FsTypeErofs {
return initrd.opts.output, convertToErofs(ctx, initrd.opts.output, ociTarballFile, true, false)
}

f, err := os.OpenFile(initrd.opts.output, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
return "", fmt.Errorf("could not open initramfs file: %w", err)
Expand Down
75 changes: 60 additions & 15 deletions initrd/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,68 @@
// You may not use this file except in compliance with the License.
package initrd

import "fmt"

type InitrdOptions struct {
arch string
cacheDir string
compress bool
output string
cacheDir string
arch string
fsType FsType
workdir string
}

type InitrdOption func(*InitrdOptions) error

// WithCompression sets the compression of the resulting CPIO archive file.
func WithCompression(compress bool) InitrdOption {
type FsType string

const (
FsTypeCpio = FsType("cpio")
FsTypeErofs = FsType("erofs")
FsTypeUnknown = FsType("unknown")
)

var _ fmt.Stringer = (*FsType)(nil)

// String implements fmt.Stringer
func (fsType FsType) String() string {
return string(fsType)
}

// FsTypes returns the list of possible fsTypes.
func FsTypes() []FsType {
return []FsType{
FsTypeCpio,
FsTypeErofs,
}
}

// FsTypeNames returns the string representation of all possible
// fsType implementations.
func FsTypeNames() []string {
types := []string{}
for _, name := range FsTypes() {
types = append(types, name.String())
}

return types
}

// WithArchitecture sets the architecture of the file contents of binaries in
// the initramfs. Files may not always be architecture specific, this option
// simply indicates the target architecture if any binaries are compiled by the
// implementing initrd builder.
func WithArchitecture(arch string) InitrdOption {
return func(opts *InitrdOptions) error {
opts.compress = compress
opts.arch = arch
return nil
}
}

// WithOutput sets the location of the output location of the resulting CPIO
// archive file.
func WithOutput(output string) InitrdOption {
// WithCompression sets the compression of the resulting CPIO archive file.
func WithCompression(compress bool) InitrdOption {
return func(opts *InitrdOptions) error {
opts.output = output
opts.compress = compress
return nil
}
}
Expand All @@ -41,13 +80,19 @@ func WithCacheDir(dir string) InitrdOption {
}
}

// WithArchitecture sets the architecture of the file contents of binaries in
// the initramfs. Files may not always be architecture specific, this option
// simply indicates the target architecture if any binaries are compiled by the
// implementing initrd builder.
func WithArchitecture(arch string) InitrdOption {
// WithOutput sets the location of the output location of the resulting CPIO
// archive file.
func WithOutput(output string) InitrdOption {
return func(opts *InitrdOptions) error {
opts.arch = arch
opts.output = output
return nil
}
}

// WithType sets the filesystem type of the resulting CPIO archive file.
func WithType(fsType FsType) InitrdOption {
return func(opts *InitrdOptions) error {
opts.fsType = fsType
return nil
}
}
Expand Down
88 changes: 88 additions & 0 deletions initrd/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@
package initrd

import (
"archive/tar"
"compress/gzip"
"context"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"

"github.com/sirupsen/logrus"
"kraftkit.sh/cpio"
"kraftkit.sh/log"
)

func compressFiles(output string, writer *cpio.Writer, reader *os.File) error {
Expand Down Expand Up @@ -55,3 +61,85 @@ func compressFiles(output string, writer *cpio.Writer, reader *os.File) error {

return nil
}

func convertToErofs(ctx context.Context, output string, source string, isTar, isGz bool) error {
if output == "" {
return fmt.Errorf("output path is required")
}

if err := os.MkdirAll(filepath.Dir(output), 0o755); err != nil {
return fmt.Errorf("could not create output directory: %w", err)
}

_, err := exec.LookPath("mkfs.erofs")
if err != nil {
return fmt.Errorf("could not find 'mkfs.erofs', try installing 'erofs-utils': %w", err)
}

args := []string{
"--all-root",
"-d2",
"-E", "noinline_data",
}

if isTar {
args = append(args, "--tar="+source)
}

if isGz {
args = append(args, "--gzip")
}

args = append(args, output, source)

// Output is a target file
// Source is an input directory, tar archive, or erofs file
cmd := exec.Command("mkfs.erofs", args...)
cmd.Env = os.Environ()
cmd.Stderr = log.G(ctx).WriterLevel(logrus.WarnLevel)
cmd.Stdout = log.G(ctx).WriterLevel(logrus.DebugLevel)
return cmd.Run()
}

func isCpioFile(initrd string) (bool, error) {
fi, err := os.Open(initrd)
if err != nil {
return false, fmt.Errorf("could not open file: %w", err)
}
defer fi.Close()

reader := cpio.NewReader(fi)

_, _, err = reader.Next()
if err != nil {
return false, nil
}

return true, nil
}

func isTarGzFile(initrd string) (isTar bool, isGz bool, err error) {
file, err := os.Open(initrd)
if err != nil {
return false, false, fmt.Errorf("could not open file: %w", err)
}
defer file.Close()

gzReader, err := gzip.NewReader(file)
if err == nil {
defer gzReader.Close()
isGz = true
}

var tarReader *tar.Reader
if isGz {
tarReader = tar.NewReader(gzReader)
} else {
tarReader = tar.NewReader(file)
}
_, err = tarReader.Next()
if err == nil || err == tar.ErrHeader {
isTar = true
}
return
}
13 changes: 12 additions & 1 deletion internal/cli/kraft/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/spf13/cobra"

"kraftkit.sh/cmdfactory"
"kraftkit.sh/initrd"
"kraftkit.sh/internal/cli/kraft/utils"
"kraftkit.sh/internal/fancymap"
"kraftkit.sh/iostreams"
Expand Down Expand Up @@ -50,6 +51,7 @@ type BuildOptions struct {
PrintStats bool `long:"print-stats" usage:"Print build statistics"`
Project app.Application `noattribute:"true"`
Rootfs string `long:"rootfs" usage:"Specify a path to use as root file system (can be volume or initramfs)"`
RootfsType initrd.FsType `noattribute:"true"`
SaveBuildLog string `long:"build-log" usage:"Use the specified file to save the output from the build"`
Target *target.Target `noattribute:"true"`
TargetName string `long:"target" short:"t" usage:"Build a particular known target"`
Expand Down Expand Up @@ -107,7 +109,7 @@ func Build(ctx context.Context, opts *BuildOptions, args ...string) error {
return fmt.Errorf("could not complete build: %w", err)
}

if opts.Rootfs, _, _, err = utils.BuildRootfs(ctx, opts.Workdir, opts.Rootfs, false, (*opts.Target).Architecture().String()); err != nil {
if opts.Rootfs, _, _, err = utils.BuildRootfs(ctx, opts.Workdir, opts.Rootfs, false, (*opts.Target).Architecture().String(), opts.RootfsType); err != nil {
return err
}

Expand Down Expand Up @@ -169,6 +171,15 @@ func NewCmd() *cobra.Command {
panic(err)
}

cmd.Flags().Var(
cmdfactory.NewEnumFlag[initrd.FsType](
initrd.FsTypes(),
initrd.FsTypeCpio,
),
"rootfs-type",
"Set the type of the format of the rootfs (cpio/erofs)",
)

return cmd
}

Expand Down
15 changes: 15 additions & 0 deletions internal/cli/kraft/cloud/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"kraftkit.sh/cmdfactory"
"kraftkit.sh/config"
"kraftkit.sh/initrd"
"kraftkit.sh/internal/cli/kraft/cloud/instance/create"
"kraftkit.sh/internal/cli/kraft/cloud/instance/logs"
"kraftkit.sh/internal/cli/kraft/cloud/utils"
Expand Down Expand Up @@ -68,6 +69,7 @@ type DeployOptions struct {
RolloutQualifier create.RolloutQualifier `noattribute:"true"`
RolloutWait time.Duration `local:"true" long:"rollout-wait" usage:"Time to wait before performing rolling out action (ms/s/m/h)" default:"10s"`
Rootfs string `local:"true" long:"rootfs" usage:"Specify a path to use as root filesystem"`
RootfsType initrd.FsType `noattribute:"true"`
Runtime string `local:"true" long:"runtime" usage:"Set an alternative project runtime"`
SaveBuildLog string `long:"build-log" usage:"Use the specified file to save the output from the build"`
ScaleToZero *kcinstances.ScaleToZeroPolicy `noattribute:"true"`
Expand Down Expand Up @@ -162,6 +164,15 @@ func NewCmd() *cobra.Command {
"When a package of the same name exists, use this strategy when applying targets.",
)

cmd.Flags().Var(
cmdfactory.NewEnumFlag[initrd.FsType](
initrd.FsTypes(),
initrd.FsTypeCpio,
),
"rootfs-type",
"Set the type of the format of the rootfs (cpio/erofs)",
)

return cmd
}

Expand All @@ -176,6 +187,10 @@ func (opts *DeployOptions) Pre(cmd *cobra.Command, _ []string) error {
opts.RolloutQualifier = create.RolloutQualifier(cmd.Flag("rollout-qualifier").Value.String())
opts.Strategy = packmanager.MergeStrategy(cmd.Flag("strategy").Value.String())

if cmd.Flag("rootfs-type").Changed {
opts.RootfsType = initrd.FsType(cmd.Flag("rootfs-type").Value.String())
}

if cmd.Flag("scale-to-zero").Changed {
s20v := kcinstances.ScaleToZeroPolicy(cmd.Flag("scale-to-zero").Value.String())
opts.ScaleToZero = &s20v
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ func (deployer *deployerKraftfileRuntime) Deploy(ctx context.Context, opts *Depl
Project: opts.Project,
Push: true,
Rootfs: opts.Rootfs,
RootfsType: opts.RootfsType,
Runtime: opts.Runtime,
Strategy: opts.Strategy,
Workdir: opts.Workdir,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func (deployer *deployerKraftfileUnikraft) Deploy(ctx context.Context, opts *Dep
NoUpdate: opts.NoUpdate,
Platform: "kraftcloud",
Rootfs: opts.Rootfs,
RootfsType: opts.RootfsType,
SaveBuildLog: opts.SaveBuildLog,
Workdir: opts.Workdir,
}); err != nil {
Expand Down
1 change: 1 addition & 0 deletions internal/cli/kraft/cloud/volume/import/cpio.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ func buildCPIO(ctx context.Context, workdir, source string) (path string, size i

cpio, err := initrd.New(ctx, source,
initrd.WithWorkdir(workdir),
initrd.WithType(initrd.FsTypeCpio),
)
if err != nil {
return "", -1, fmt.Errorf("initializing temp CPIO archive: %w", err)
Expand Down
Loading

0 comments on commit 083dff7

Please sign in to comment.