feat: generate kernel module dependency tree
Run `depmod` during install/upgrades when extensions provide kernel modules and `modules.dep` needs to be re-generated. This also allows modules of same name from kernel to co-exist. Modules in `extras` folder takes precedence over `in-built` ones. Signed-off-by: Noel Georgi <git@frezbo.dev>
This commit is contained in:
parent
18122ae73e
commit
d4b8b35de7
@ -652,6 +652,7 @@ RUN apk add --no-cache --update --no-scripts \
|
||||
bash \
|
||||
cpio \
|
||||
efibootmgr \
|
||||
kmod \
|
||||
mtools \
|
||||
qemu-img \
|
||||
squashfs-tools \
|
||||
@ -663,6 +664,7 @@ ARG TARGETARCH
|
||||
ENV TARGETARCH ${TARGETARCH}
|
||||
COPY --from=install-artifacts / /
|
||||
COPY --from=installer-build /installer /bin/installer
|
||||
COPY --chmod=0644 hack/extra-modules.conf /etc/modules.d/10-extra-modules.conf
|
||||
RUN ln -s /bin/installer /bin/talosctl
|
||||
ARG TAG
|
||||
ENV VERSION ${TAG}
|
||||
|
@ -20,24 +20,36 @@ import (
|
||||
extinterface "github.com/siderolabs/talos/pkg/machinery/extensions"
|
||||
)
|
||||
|
||||
// nolint:gocyclo
|
||||
func (i *Installer) installExtensions() error {
|
||||
extensions, err := extensions.List(constants.SystemExtensionsPath)
|
||||
extensionsList, err := extensions.List(constants.SystemExtensionsPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing extensions: %w", err)
|
||||
}
|
||||
|
||||
if len(extensions) == 0 {
|
||||
if len(extensionsList) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = printExtensions(extensions); err != nil {
|
||||
if err = printExtensions(extensionsList); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = validateExtensions(extensions); err != nil {
|
||||
if err = validateExtensions(extensionsList); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
extensionsPathWithKernelModules := findExtensionsWithKernelModules(extensionsList)
|
||||
|
||||
if len(extensionsPathWithKernelModules) > 0 {
|
||||
kernelModuleDepExtension, genErr := extensions.GenerateKernelModuleDependencyTreeExtension(extensionsPathWithKernelModules, i.options.Arch)
|
||||
if genErr != nil {
|
||||
return genErr
|
||||
}
|
||||
|
||||
extensionsList = append(extensionsList, kernelModuleDepExtension)
|
||||
}
|
||||
|
||||
tempDir, err := os.MkdirTemp("", "ext")
|
||||
if err != nil {
|
||||
return err
|
||||
@ -47,7 +59,7 @@ func (i *Installer) installExtensions() error {
|
||||
|
||||
var cfg *extinterface.Config
|
||||
|
||||
if cfg, err = compressExtensions(extensions, tempDir); err != nil {
|
||||
if cfg, err = compressExtensions(extensionsList, tempDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -123,6 +135,18 @@ func compressExtensions(extensions []*extensions.Extension, tempDir string) (*ex
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func findExtensionsWithKernelModules(extensions []*extensions.Extension) []string {
|
||||
var modulesPath []string
|
||||
|
||||
for _, ext := range extensions {
|
||||
if ext.ProvidesKernelModules() {
|
||||
modulesPath = append(modulesPath, ext.KernelModuleDirectory())
|
||||
}
|
||||
}
|
||||
|
||||
return modulesPath
|
||||
}
|
||||
|
||||
func buildContents(path string) (io.Reader, error) {
|
||||
var listing bytes.Buffer
|
||||
|
||||
|
3
go.mod
3
go.mod
@ -112,6 +112,7 @@ require (
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/u-root/u-root v0.10.0
|
||||
github.com/ulikunitz/xz v0.5.8
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2
|
||||
github.com/vmware-tanzu/sonobuoy v0.56.14
|
||||
github.com/vmware/govmomi v0.30.0
|
||||
@ -243,6 +244,7 @@ require (
|
||||
github.com/opencontainers/selinux v1.10.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
@ -263,7 +265,6 @@ require (
|
||||
github.com/spf13/viper v1.14.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect
|
||||
github.com/ulikunitz/xz v0.5.8 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
|
||||
github.com/xlab/treeprint v1.1.0 // indirect
|
||||
|
2
go.sum
2
go.sum
@ -927,6 +927,8 @@ github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvI
|
||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
|
||||
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pin/tftp v2.1.0+incompatible h1:Yng4J7jv6lOc6IF4XoB5mnd3P7ZrF60XQq+my3FAMus=
|
||||
github.com/pin/tftp v2.1.0+incompatible/go.mod h1:xVpZOMCXTy+A5QMjEVN0Glwa1sUvaJhFXbr/aAxuxGY=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
|
1
hack/extra-modules.conf
Normal file
1
hack/extra-modules.conf
Normal file
@ -0,0 +1 @@
|
||||
search extras built-in
|
@ -6,6 +6,8 @@
|
||||
package extensions
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/siderolabs/talos/pkg/machinery/extensions"
|
||||
)
|
||||
|
||||
@ -16,3 +18,16 @@ type Extension struct {
|
||||
directory string
|
||||
rootfsPath string
|
||||
}
|
||||
|
||||
func newExtension(rootfsPath, directory string) *Extension {
|
||||
extension := &Extension{
|
||||
rootfsPath: rootfsPath,
|
||||
directory: directory,
|
||||
}
|
||||
|
||||
if extension.directory == "" {
|
||||
extension.directory = filepath.Base(rootfsPath)
|
||||
}
|
||||
|
||||
return extension
|
||||
}
|
||||
|
240
internal/pkg/extensions/kernel_modules.go
Normal file
240
internal/pkg/extensions/kernel_modules.go
Normal file
@ -0,0 +1,240 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package extensions provides function to manage system extensions.
|
||||
package extensions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/u-root/u-root/pkg/cpio"
|
||||
"github.com/ulikunitz/xz"
|
||||
"golang.org/x/sys/unix"
|
||||
"gopkg.in/freddierice/go-losetup.v1"
|
||||
|
||||
"github.com/siderolabs/talos/internal/pkg/mount"
|
||||
"github.com/siderolabs/talos/pkg/machinery/constants"
|
||||
"github.com/siderolabs/talos/pkg/machinery/extensions"
|
||||
)
|
||||
|
||||
// ProvidesKernelModules returns true if the extension provides kernel modules.
|
||||
func (ext *Extension) ProvidesKernelModules() bool {
|
||||
if _, err := os.Stat(filepath.Join(ext.rootfsPath, constants.DefaultKernelModulesPath)); os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// KernelModuleDirectory returns the path to the kernel modules directory.
|
||||
func (ext *Extension) KernelModuleDirectory() string {
|
||||
return filepath.Join(ext.rootfsPath, constants.DefaultKernelModulesPath)
|
||||
}
|
||||
|
||||
// GenerateKernelModuleDependencyTreeExtension generates a kernel module dependency tree extension.
|
||||
// nolint:gocyclo
|
||||
func GenerateKernelModuleDependencyTreeExtension(extensionsPathWithKernelModules []string, arch string) (*Extension, error) {
|
||||
log.Println("preparing to run depmod to generate kernel modules dependency tree")
|
||||
|
||||
tempDir, err := os.MkdirTemp("", "ext-modules")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer logErr(func() error {
|
||||
return os.RemoveAll(tempDir)
|
||||
})
|
||||
|
||||
initramfsxz, err := os.Open(fmt.Sprintf(constants.InitramfsAssetPath, arch))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer logErr(func() error {
|
||||
return initramfsxz.Close()
|
||||
})
|
||||
|
||||
r, err := xz.NewReader(initramfsxz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var buff bytes.Buffer
|
||||
|
||||
if _, err = io.Copy(&buff, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tempRootfsFile := filepath.Join(tempDir, constants.RootfsAsset)
|
||||
|
||||
if err = extractRootfsFromInitramfs(buff, tempRootfsFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// now we are ready to mount rootfs.sqsh
|
||||
// create a mount point under tempDir
|
||||
rootfsMountPath := filepath.Join(tempDir, "rootfs-mnt")
|
||||
|
||||
// create the loopback device from the squashfs file
|
||||
dev, err := losetup.Attach(tempRootfsFile, 0, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer logErr(func() error {
|
||||
if err = dev.Detach(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dev.Remove()
|
||||
})
|
||||
|
||||
// setup a temporary mount point for the squashfs file and mount it
|
||||
m := mount.NewMountPoint(dev.Path(), rootfsMountPath, "squashfs", unix.MS_RDONLY|unix.MS_I_VERSION, "", mount.WithFlags(mount.ReadOnly|mount.Shared))
|
||||
|
||||
if err = m.Mount(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer logErr(func() error {
|
||||
return m.Unmount()
|
||||
})
|
||||
|
||||
// create an overlayfs which contains the rootfs squashfs mount as the base
|
||||
// and the extension modules as subsequent lower directories
|
||||
overlays := mount.NewMountPoints()
|
||||
// writable overlayfs mount inside a container required a tmpfs mount
|
||||
overlays.Set("overlays-tmpfs", mount.NewMountPoint("tmpfs", constants.VarSystemOverlaysPath, "tmpfs", unix.MS_I_VERSION, ""))
|
||||
|
||||
// append the rootfs mount point
|
||||
extensionsPathWithKernelModules = append(extensionsPathWithKernelModules, filepath.Join(rootfsMountPath, constants.DefaultKernelModulesPath))
|
||||
|
||||
// create the overlayfs mount point as read write
|
||||
mp := mount.NewMountPoint("", strings.Join(extensionsPathWithKernelModules, ":"), "", unix.MS_I_VERSION, "", mount.WithFlags(mount.Overlay|mount.Shared))
|
||||
overlays.Set("overlays-mnt", mp)
|
||||
|
||||
if err = mount.Mount(overlays); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer logErr(func() error {
|
||||
return mount.Unmount(overlays)
|
||||
})
|
||||
|
||||
log.Println("running depmod to generate kernel modules dependency tree")
|
||||
|
||||
if err = depmod(mp.Target()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// we want this temp directory to be present until the extension is compressed later on, so not removing it here
|
||||
kernelModulesDependencyTreeStagingDir, err := os.MkdirTemp("", "module-deps")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kernelModulesDepenencyTreeDirectory := filepath.Join(kernelModulesDependencyTreeStagingDir, constants.DefaultKernelModulesPath)
|
||||
|
||||
if err := os.MkdirAll(kernelModulesDepenencyTreeDirectory, 0o755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := findAndMoveKernelModulesDepFiles(kernelModulesDepenencyTreeDirectory, mp.Target()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kernelModulesDepTreeExtension := newExtension(kernelModulesDependencyTreeStagingDir, "modules.dep")
|
||||
kernelModulesDepTreeExtension.Manifest = extensions.Manifest{
|
||||
Version: constants.DefaultKernelVersion,
|
||||
Metadata: extensions.Metadata{
|
||||
Name: "modules.dep",
|
||||
Version: constants.DefaultKernelVersion,
|
||||
Author: "Talos Machinery",
|
||||
Description: "Combined modules.dep for all extensions",
|
||||
},
|
||||
}
|
||||
|
||||
return kernelModulesDepTreeExtension, nil
|
||||
}
|
||||
|
||||
func logErr(f func() error) {
|
||||
// if file is already closed, ignore the error
|
||||
if err := f(); !errors.Is(err, os.ErrClosed) {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func extractRootfsFromInitramfs(input bytes.Buffer, rootfsFilePath string) error {
|
||||
recReader := cpio.Newc.Reader(bytes.NewReader(input.Bytes()))
|
||||
|
||||
if err := cpio.ForEachRecord(recReader, func(r cpio.Record) error {
|
||||
if r.Name != constants.RootfsAsset {
|
||||
return nil
|
||||
}
|
||||
|
||||
reader := io.NewSectionReader(r.ReaderAt, 0, int64(r.FileSize))
|
||||
f, err := os.Create(rootfsFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logErr(func() error {
|
||||
return f.Close()
|
||||
})
|
||||
|
||||
_, err = io.Copy(f, reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return f.Close()
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func depmod(kernelModulesPath string) error {
|
||||
baseDir := strings.TrimSuffix(kernelModulesPath, constants.DefaultKernelModulesPath)
|
||||
|
||||
cmd := exec.Command("depmod", "--all", "--basedir", baseDir, "--config", "/etc/modules.d/10-extra-modules.conf", constants.DefaultKernelVersion)
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func findAndMoveKernelModulesDepFiles(dest, kernelModulesPath string) error {
|
||||
fs, err := os.ReadDir(kernelModulesPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, f := range fs {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(f.Name(), "modules.") {
|
||||
fs, err := f.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := moveFile(fs, filepath.Join(kernelModulesPath, f.Name()), filepath.Join(dest, f.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -15,6 +15,9 @@ const (
|
||||
// DefaultKernelVersion is the default Linux kernel version.
|
||||
DefaultKernelVersion = "6.1.4-talos"
|
||||
|
||||
// DefaultKernelModulesPath is the default path to the kernel modules.
|
||||
DefaultKernelModulesPath = "/lib/modules" + "/" + DefaultKernelVersion
|
||||
|
||||
// KernelParamConfig is the kernel parameter name for specifying the URL.
|
||||
// to the config.
|
||||
KernelParamConfig = "talos.config"
|
||||
|
Loading…
Reference in New Issue
Block a user