mirror of
https://github.com/siderolabs/talos.git
synced 2026-05-04 12:01:12 +02:00
feat: extract firmware part of system extensions into initramfs
Fixes #4816 This changes the way system extensions are packaged into the squashfs images: `/lib/firmware` is now moved out of the future squashfs images and becomes part of `initramfs` to make firmware available in the early boot. Talos will bind-mount `/lib/firmware` into rootfs as well, so it will be available in the rootfs as well. Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
This commit is contained in:
parent
8899dd3494
commit
df0e388a4f
@ -360,7 +360,7 @@ local integration_provision_tests_track_2 = Step("provision-tests-track-2", priv
|
||||
|
||||
local integration_gvisor = Step("e2e-gvisor", target="e2e-qemu", privileged=true, depends_on=[load_artifacts], environment={
|
||||
"SHORT_INTEGRATION_TEST": "yes",
|
||||
"WITH_CONFIG_PATCH": '[{"op":"add","path":"/machine/install/extensions","value":[{"image":"ghcr.io/talos-systems/gvisor:933cdb8"}]},{"op":"add","path":"/machine/sysctls","value":{"user.max_user_namespaces": "11255"}}]',
|
||||
"WITH_CONFIG_PATCH": '[{"op":"add","path":"/machine/install/extensions","value":[{"image":"ghcr.io/talos-systems/gvisor:54b831d"},{"image":"ghcr.io/talos-systems/intel-ucode:54b831d"}]},{"op":"add","path":"/machine/sysctls","value":{"user.max_user_namespaces": "11255"}}]',
|
||||
"WITH_TEST": "run_gvisor_test",
|
||||
"IMAGE_REGISTRY": local_registry,
|
||||
});
|
||||
|
||||
@ -7,6 +7,7 @@ package install
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
@ -107,7 +108,7 @@ func compressExtensions(extensions []*extensions.Extension, tempDir string) (*ex
|
||||
log.Printf("compressing system extensions")
|
||||
|
||||
for _, ext := range extensions {
|
||||
path, err := ext.Compress(tempDir)
|
||||
path, err := ext.Compress(tempDir, tempDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error compressing extension %q: %w", ext.Manifest.Metadata.Name, err)
|
||||
}
|
||||
@ -121,41 +122,80 @@ func compressExtensions(extensions []*extensions.Extension, tempDir string) (*ex
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func buildContents(path string) (io.Reader, error) {
|
||||
var listing bytes.Buffer
|
||||
|
||||
if err := buildContentsRecursive(path, "", &listing); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &listing, nil
|
||||
}
|
||||
|
||||
func buildContentsRecursive(basePath, path string, w io.Writer) error {
|
||||
if path != "" {
|
||||
fmt.Fprintf(w, "%s\n", path)
|
||||
}
|
||||
|
||||
st, err := os.Stat(filepath.Join(basePath, path))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !st.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
contents, err := os.ReadDir(filepath.Join(basePath, path))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range contents {
|
||||
if err = buildContentsRecursive(basePath, filepath.Join(path, item.Name()), w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Installer) rebuildInitramfs(tempDir string) error {
|
||||
initramfsAsset := fmt.Sprintf(constants.InitramfsAssetPath, i.options.Arch)
|
||||
|
||||
log.Printf("creating system extensions initramfs archive")
|
||||
log.Printf("creating system extensions initramfs archive and compressing it")
|
||||
|
||||
contents, err := os.ReadDir(tempDir)
|
||||
// the code below runs the equivalent of:
|
||||
// find $tempDir -print | cpio -H newc --create --reproducible | xz -v -C crc32 -0 -e -T 0 -z
|
||||
|
||||
listing, err := buildContents(tempDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var listing bytes.Buffer
|
||||
|
||||
for _, item := range contents {
|
||||
fmt.Fprintf(&listing, "%s\n", item.Name())
|
||||
pipeR, pipeW, err := os.Pipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer pipeR.Close() //nolint:errcheck
|
||||
defer pipeW.Close() //nolint:errcheck
|
||||
|
||||
// build cpio image which contains .sqsh images and extensions.yaml
|
||||
cmd := exec.Command("cpio", "-H", "newc", "--create", "--reproducible", "-F", "initramfs.sysext")
|
||||
cmd.Dir = tempDir
|
||||
cmd.Stdin = &listing
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd1 := exec.Command("cpio", "-H", "newc", "--create", "--reproducible")
|
||||
cmd1.Dir = tempDir
|
||||
cmd1.Stdin = listing
|
||||
cmd1.Stdout = pipeW
|
||||
cmd1.Stderr = os.Stderr
|
||||
|
||||
if err = cmd.Run(); err != nil {
|
||||
if err = cmd1.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("compressing system extensions initramfs archive")
|
||||
|
||||
source, err := os.OpenFile(filepath.Join(tempDir, "initramfs.sysext"), os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
if err = pipeW.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer source.Close() //nolint:errcheck
|
||||
|
||||
destination, err := os.OpenFile(initramfsAsset, os.O_APPEND|os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -164,15 +204,35 @@ func (i *Installer) rebuildInitramfs(tempDir string) error {
|
||||
defer destination.Close() //nolint:errcheck
|
||||
|
||||
// append compressed initramfs.sysext to the original initramfs.xz, kernel can read such format
|
||||
cmd = exec.Command("xz", "-v", "-C", "crc32", "-0", "-e", "-T", "0", "-z")
|
||||
cmd.Dir = tempDir
|
||||
cmd.Stdin = source
|
||||
cmd.Stdout = destination
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd2 := exec.Command("xz", "-v", "-C", "crc32", "-0", "-e", "-T", "0", "-z")
|
||||
cmd2.Dir = tempDir
|
||||
cmd2.Stdin = pipeR
|
||||
cmd2.Stdout = destination
|
||||
cmd2.Stderr = os.Stderr
|
||||
|
||||
if err = cmd.Run(); err != nil {
|
||||
if err = cmd2.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
if err = pipeR.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
errCh <- cmd1.Wait()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
errCh <- cmd2.Wait()
|
||||
}()
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
if err = <-errCh; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return destination.Sync()
|
||||
}
|
||||
|
||||
@ -6,17 +6,91 @@ package extensions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
)
|
||||
|
||||
// Compress builds the squashfs image in the specified destination folder.
|
||||
func (ext *Extension) Compress(destinationPath string) (string, error) {
|
||||
destinationPath = filepath.Join(destinationPath, fmt.Sprintf("%s.sqsh", ext.directory))
|
||||
// List of paths to be moved to the future initramfs.
|
||||
var initramfsPaths = []string{
|
||||
constants.FirmwarePath,
|
||||
}
|
||||
|
||||
cmd := exec.Command("mksquashfs", ext.rootfsPath, destinationPath, "-all-root", "-noappend", "-comp", "xz", "-Xdict-size", "100%", "-no-progress")
|
||||
// Compress builds the squashfs image in the specified destination folder.
|
||||
//
|
||||
// Components which should be placed to the initramfs are moved to the initramfsPath.
|
||||
func (ext *Extension) Compress(squashPath, initramfsPath string) (string, error) {
|
||||
for _, path := range initramfsPaths {
|
||||
if _, err := os.Stat(filepath.Join(ext.rootfsPath, path)); err == nil {
|
||||
if err = moveFiles(filepath.Join(ext.rootfsPath, path), filepath.Join(initramfsPath, path)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
squashPath = filepath.Join(squashPath, fmt.Sprintf("%s.sqsh", ext.directory))
|
||||
|
||||
cmd := exec.Command("mksquashfs", ext.rootfsPath, squashPath, "-all-root", "-noappend", "-comp", "xz", "-Xdict-size", "100%", "-no-progress")
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
return destinationPath, cmd.Run()
|
||||
return squashPath, cmd.Run()
|
||||
}
|
||||
|
||||
func moveFiles(srcPath, dstPath string) error {
|
||||
st, err := os.Stat(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if st.IsDir() {
|
||||
return moveDirectory(st, srcPath, dstPath)
|
||||
}
|
||||
|
||||
return moveFile(st, srcPath, dstPath)
|
||||
}
|
||||
|
||||
func moveFile(st fs.FileInfo, srcPath, dstPath string) error {
|
||||
src, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer src.Close() //nolint:errcheck
|
||||
|
||||
dst, err := os.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY, st.Mode().Perm())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer dst.Close() //nolint:errcheck
|
||||
|
||||
_, err = io.Copy(dst, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Remove(srcPath)
|
||||
}
|
||||
|
||||
func moveDirectory(st fs.FileInfo, srcPath, dstPath string) error {
|
||||
if err := os.MkdirAll(dstPath, st.Mode().Perm()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contents, err := os.ReadDir(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range contents {
|
||||
if err = moveFiles(filepath.Join(srcPath, item.Name()), filepath.Join(dstPath, item.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return os.Remove(srcPath)
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
package extensions_test
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
@ -15,7 +16,7 @@ import (
|
||||
"github.com/talos-systems/talos/pkg/version"
|
||||
)
|
||||
|
||||
func TestLoadValidateCompress(t *testing.T) {
|
||||
func TestLoadValidate(t *testing.T) {
|
||||
ext, err := extensions.Load("testdata/good/extension1")
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -30,11 +31,23 @@ func TestLoadValidateCompress(t *testing.T) {
|
||||
})
|
||||
|
||||
assert.NoError(t, ext.Validate())
|
||||
}
|
||||
|
||||
dest := t.TempDir()
|
||||
_, err = ext.Compress(dest)
|
||||
func TestCompress(t *testing.T) {
|
||||
// Compress is going to change contents of the extension, copy to some temporary directory
|
||||
extDir := t.TempDir()
|
||||
|
||||
require.NoError(t, exec.Command("cp", "-r", "testdata/good/extension1", extDir).Run())
|
||||
|
||||
ext, err := extensions.Load(filepath.Join(extDir, "extension1"))
|
||||
require.NoError(t, err)
|
||||
|
||||
squashDest, initramfsDest := t.TempDir(), t.TempDir()
|
||||
squashFile, err := ext.Compress(squashDest, initramfsDest)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.FileExists(t, squashFile)
|
||||
assert.FileExists(t, filepath.Join(initramfsDest, "lib", "firmware", "amd", "cpu"))
|
||||
}
|
||||
|
||||
func TestValidateFailures(t *testing.T) {
|
||||
|
||||
@ -77,7 +77,7 @@ func (puller *Puller) PullAndMount(ctx context.Context, registryConfig config.Re
|
||||
return err
|
||||
}
|
||||
|
||||
mounts, err := snapshotService.View(ctx, path, chainID.String())
|
||||
mounts, err := snapshotService.Prepare(ctx, path, chainID.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user