diff --git a/.drone.jsonnet b/.drone.jsonnet index 85b06a378..3f48ab2c6 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -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, }); diff --git a/cmd/installer/pkg/install/extensions.go b/cmd/installer/pkg/install/extensions.go index 313726b03..61469e526 100644 --- a/cmd/installer/pkg/install/extensions.go +++ b/cmd/installer/pkg/install/extensions.go @@ -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() } diff --git a/internal/pkg/extensions/compress.go b/internal/pkg/extensions/compress.go index 034554ea1..a2f5852e9 100644 --- a/internal/pkg/extensions/compress.go +++ b/internal/pkg/extensions/compress.go @@ -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) } diff --git a/internal/pkg/extensions/extensions_test.go b/internal/pkg/extensions/extensions_test.go index 2bf3a3b86..8296a8422 100644 --- a/internal/pkg/extensions/extensions_test.go +++ b/internal/pkg/extensions/extensions_test.go @@ -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) { diff --git a/internal/pkg/extensions/pull.go b/internal/pkg/extensions/pull.go index 422f61c9d..a853fc2fb 100644 --- a/internal/pkg/extensions/pull.go +++ b/internal/pkg/extensions/pull.go @@ -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 }