Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Introduce support for erofs initrds #2007

Draft
wants to merge 1 commit into
base: staging
Choose a base branch
from
Draft
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 .goreleaser-stable.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ nfpms:
recommends:
- bison
- build-essential
- erofs-utils
- flex
- git
- libncurses-dev
Expand Down
1 change: 1 addition & 0 deletions .goreleaser-staging.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ nfpms:
recommends:
- bison
- build-essential
- erofs-utils
- flex
- git
- libncurses-dev
Expand Down
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
Loading
Loading