diff --git a/CHANGELOG.md b/CHANGELOG.md index ae2be9477..7385aa9e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -136,6 +136,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 has a container these binaries are extracted from the container image. - overlays from different profiles for one node are now merged, overlays can be excluded with ~ prefix and in listings are listed as !{excluded_profile} #885 +- modules are now correctly included ## [4.4.0] 2023-01-18 diff --git a/internal/app/wwctl/kernel/imprt/main.go b/internal/app/wwctl/kernel/imprt/main.go index 47a2e636d..4a586366c 100644 --- a/internal/app/wwctl/kernel/imprt/main.go +++ b/internal/app/wwctl/kernel/imprt/main.go @@ -49,12 +49,12 @@ func CobraRunE(cmd *cobra.Command, args []string) error { } else if OptDetect && (OptContainer != "") { kernelName = OptContainer } - output, err := kernel.Build(kernelVersion, kernelName, OptRoot) + err = kernel.Build(kernelVersion, kernelName, OptRoot) if err != nil { wwlog.Error("Failed building kernel: %s", err) os.Exit(1) } else { - fmt.Printf("%s: %s\n", kernelName, output) + fmt.Printf("%s: %s\n", kernelName, "Finished kernel build") } if SetDefault { diff --git a/internal/pkg/kernel/kernel.go b/internal/pkg/kernel/kernel.go index 47395d5c9..69dac6df4 100644 --- a/internal/pkg/kernel/kernel.go +++ b/internal/pkg/kernel/kernel.go @@ -111,9 +111,17 @@ func ListKernels() ([]string, error) { return ret, nil } -func Build(kernelVersion, kernelName, root string) (string, error) { - kernelDriversRelative := path.Join("/lib/modules/", kernelVersion) - kernelDrivers := path.Join(root, kernelDriversRelative) +/* +Triggers the kernel extraction and build of the modules for the given +kernel version. A name for this kernel and were to find has also to be +supplied +*/ +func Build(kernelVersion, kernelName, root string) error { + kernelDrivers := []string{path.Join("lib/modules/", + kernelVersion, "*"), + "lib/firmware/*", + "lib/modprobe.d", + "lib/modules-load.d"} kernelDestination := KernelImage(kernelName) driversDestination := KmodsImage(kernelName) versionDestination := KernelVersionFile(kernelName) @@ -122,17 +130,17 @@ func Build(kernelVersion, kernelName, root string) (string, error) { // Create the destination paths just in case it doesn't exist err := os.MkdirAll(path.Dir(kernelDestination), 0755) if err != nil { - return "", errors.Wrap(err, "failed to create kernel dest") + return errors.Wrap(err, "failed to create kernel dest") } err = os.MkdirAll(path.Dir(driversDestination), 0755) if err != nil { - return "", errors.Wrap(err, "failed to create driver dest") + return errors.Wrap(err, "failed to create driver dest") } err = os.MkdirAll(path.Dir(versionDestination), 0755) if err != nil { - return "", fmt.Errorf("failed to create version dest: %s", err) + return fmt.Errorf("failed to create version dest: %s", err) } for _, searchPath := range kernelSearchPaths { @@ -146,20 +154,16 @@ func Build(kernelVersion, kernelName, root string) (string, error) { if kernelSource == "" { wwlog.Error("Could not locate kernel image") - return "", errors.New("could not locate kernel image") + return errors.New("could not locate kernel image") } else { wwlog.Info("Found kernel at: %s", kernelSource) } - if !util.IsDir(kernelDrivers) { - return "", errors.New("Could not locate kernel drivers") - } - wwlog.Verbose("Setting up Kernel") if _, err := os.Stat(kernelSource); err == nil { kernel, err := os.Open(kernelSource) if err != nil { - return "", errors.Wrap(err, "could not open kernel") + return errors.Wrap(err, "could not open kernel") } defer kernel.Close() @@ -169,63 +173,59 @@ func Build(kernelVersion, kernelName, root string) (string, error) { writer, err := os.Create(kernelDestination) if err != nil { - return "", errors.Wrap(err, "could not decompress kernel") + return errors.Wrap(err, "could not decompress kernel") } defer writer.Close() _, err = io.Copy(writer, gzipreader) if err != nil { - return "", errors.Wrap(err, "could not write decompressed kernel") + return errors.Wrap(err, "could not write decompressed kernel") } } else { err := util.CopyFile(kernelSource, kernelDestination) if err != nil { - return "", errors.Wrap(err, "could not copy kernel") + return errors.Wrap(err, "could not copy kernel") } } } - if _, err := os.Stat(kernelDrivers); err == nil { - name := kernelName + " drivers" - wwlog.Verbose("Creating image for %s: %s", name, root) - - err = util.BuildFsImage( - name, - root, - driversDestination, - []string{ - "." + kernelDriversRelative, - "./lib/firmware"}, - []string{}, - // ignore cross-device files - true, - "newc", - // dereference symbolic links - "-L") + name := kernelName + " drivers" + wwlog.Verbose("Creating image for %s: %s", name, root) + + err = util.BuildFsImage( + name, + root, + driversDestination, + kernelDrivers, + []string{}, + // ignore cross-device files + true, + "newc", + // dereference symbolic links + "-L") - if err != nil { - return "", err - } + if err != nil { + return err } wwlog.Verbose("Creating version file") file, err := os.Create(versionDestination) if err != nil { - return "", errors.Wrap(err, "Failed to create version file") + return errors.Wrap(err, "Failed to create version file") } defer file.Close() _, err = io.WriteString(file, kernelVersion) if err != nil { - return "", errors.Wrap(err, "Could not write kernel version") + return errors.Wrap(err, "Could not write kernel version") } err = file.Sync() if err != nil { - return "", errors.Wrap(err, "Could not sync kernel version") + return errors.Wrap(err, "Could not sync kernel version") } - return "Done", nil + return nil } func DeleteKernel(name string) error { diff --git a/internal/pkg/kernel/kernel_test.go b/internal/pkg/kernel/kernel_test.go new file mode 100644 index 000000000..163a1744c --- /dev/null +++ b/internal/pkg/kernel/kernel_test.go @@ -0,0 +1,72 @@ +package kernel + +import ( + "os" + "path" + "testing" + + warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" + "github.com/hpcng/warewulf/internal/pkg/util" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/stretchr/testify/assert" +) + +var kernelBuildTests = []struct { + kernelVersion string + kernelName string + kernelFileName string + succeed bool +}{ + {"4.3.2.1", "kernel1", "vmlinuz-1.2.3.4.gz", false}, + {"1.2.3.4", "kernel1", "vmlinuz-1.2.3.4.gz", true}, +} + +func Test_BuildKernel(t *testing.T) { + wwlog.SetLogLevel(wwlog.DEBUG) + srvDir, err := os.MkdirTemp(os.TempDir(), "ww-test-srv-*") + assert.NoError(t, err) + defer os.RemoveAll(srvDir) + conf := warewulfconf.Get() + conf.Paths.WWProvisiondir = srvDir + kernelDir, err := os.MkdirTemp(os.TempDir(), "ww-test-kernel-*") + assert.NoError(t, err) + defer os.RemoveAll(kernelDir) + { + err = os.MkdirAll(path.Join(kernelDir, "boot"), 0755) + assert.NoError(t, err) + err = os.MkdirAll(path.Join(kernelDir, "lib/modules/old-kernel"), 0755) + assert.NoError(t, err) + _, err = os.Create(path.Join(kernelDir, "lib/modules/old-kernel/old-module")) + assert.NoError(t, err) + err = os.MkdirAll(path.Join(kernelDir, "lib/firmware"), 0755) + assert.NoError(t, err) + _, err = os.Create(path.Join(kernelDir, "lib/firmware/test-firmware")) + assert.NoError(t, err) + for _, tt := range kernelBuildTests { + _, err = os.Create(path.Join(kernelDir, "boot", tt.kernelFileName)) + assert.NoError(t, err) + err = os.MkdirAll(path.Join(kernelDir, "lib/modules", tt.kernelVersion, "/nested"), 0755) + assert.NoError(t, err) + _, err = os.Create(path.Join(kernelDir, "lib/modules", tt.kernelVersion, "test-module")) + assert.NoError(t, err) + err = os.Symlink(path.Join(kernelDir, "lib/modules/old-kernel/old-module"), path.Join(kernelDir, "lib/modules", tt.kernelVersion, "symlink-module")) + assert.NoError(t, err) + } + } + for _, tt := range kernelBuildTests { + t.Run(tt.kernelName, func(t *testing.T) { + err = Build(tt.kernelVersion, tt.kernelName, kernelDir) + if tt.succeed { + assert.NoError(t, err) + assert.FileExists(t, path.Join(srvDir, "kernel", tt.kernelName, "vmlinuz")) + assert.FileExists(t, path.Join(srvDir, "kernel", tt.kernelName, "kmods.img.gz")) + assert.FileExists(t, path.Join(srvDir, "kernel", tt.kernelName, "kmods.img")) + files, err := util.CpioFiles(path.Join(srvDir, "kernel", tt.kernelName, "kmods.img")) + assert.NoError(t, err) + assert.ElementsMatch(t, files, []string{"lib/firmware/test-firmware", "lib/modules/1.2.3.4/symlink-module", "lib/modules/1.2.3.4/test-module", "lib/modules/1.2.3.4/nested"}) + } else { + assert.Error(t, err) + } + }) + } +} diff --git a/internal/pkg/overlay/overlay_test.go b/internal/pkg/overlay/overlay_test.go index 11e762950..194ea49e1 100644 --- a/internal/pkg/overlay/overlay_test.go +++ b/internal/pkg/overlay/overlay_test.go @@ -1,15 +1,17 @@ package overlay import ( - warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" - "github.com/hpcng/warewulf/internal/pkg/node" - "github.com/sassoftware/go-rpmutils/cpio" - "github.com/stretchr/testify/assert" "io" "os" "path" "sort" "testing" + + warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/stretchr/testify/assert" + + "github.com/hpcng/warewulf/internal/pkg/util" ) var buildOverlayTests = []struct { @@ -161,9 +163,9 @@ func Test_BuildOverlay(t *testing.T) { if tt.image != "" { image := path.Join(provisionDir, "overlays", tt.image) assert.FileExists(t, image) - sort.Strings(tt.contents) - files := cpioFiles(t, image) + files, err := util.CpioFiles(image) + assert.NoError(t, err) sort.Strings(files) assert.Equal(t, tt.contents, files) } else { @@ -401,20 +403,3 @@ func dirIsEmpty(t *testing.T, name string) bool { t.Log(dirnames) return false } - -func cpioFiles(t *testing.T, name string) (files []string) { - f, openErr := os.Open(name) - if openErr != nil { - return - } - defer f.Close() - - reader := cpio.NewReader(f) - for { - header, err := reader.Next() - if err != nil { - return - } - files = append(files, header.Filename()) - } -} diff --git a/internal/pkg/util/cpio.go b/internal/pkg/util/cpio.go new file mode 100644 index 000000000..f8709c3d0 --- /dev/null +++ b/internal/pkg/util/cpio.go @@ -0,0 +1,31 @@ +package util + +import ( + "io" + "os" + + "github.com/sassoftware/go-rpmutils/cpio" +) + +/* +Opens cpio archive and returns the file list +*/ +func CpioFiles(name string) (files []string, err error) { + f, err := os.Open(name) + if err != nil { + return files, err + } + defer f.Close() + + reader := cpio.NewReader(f) + for { + header, err := reader.Next() + if err == io.EOF { + return files, nil + } + if err != nil { + return files, err + } + files = append(files, header.Filename()) + } +} diff --git a/internal/pkg/util/util.go b/internal/pkg/util/util.go index 2381e77b0..00242fb03 100644 --- a/internal/pkg/util/util.go +++ b/internal/pkg/util/util.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "fmt" "io" + "io/fs" "math/rand" "net" "os" @@ -248,15 +249,17 @@ func FindFiles(path string) []string { return ret } -// ****************************************************************************** +/* +Finds all files under a given directory with tar like include and ignore patterns. +/foo/* +will match /foo/baar/ and /foo/baar/sibling +*/ func FindFilterFiles( path string, - include []string, - ignore []string, + includePattern []string, + ignorePattern []string, ignore_xdev bool) (ofiles []string, err error) { - - wwlog.Debug("Finding files: %s", path) - + wwlog.Debug("Finding files: %s include: %s ignore: %s", path, includePattern, ignorePattern) cwd, err := os.Getwd() if err != nil { return ofiles, err @@ -268,13 +271,16 @@ func FindFilterFiles( if err != nil { return ofiles, errors.Wrapf(err, "Failed to change path: %s", path) } - - for i := range ignore { - ignore[i] = strings.TrimLeft(ignore[i], "/") - ignore[i] = strings.TrimPrefix(ignore[i], "./") - wwlog.Debug("Ignore pattern (%d): %s", i, ignore[i]) + // expand our include list as fspath.Match with /foo/* would catch /foo/baar but + // not /foo/baar/sibling + var globedInclude []string + for _, include := range includePattern { + globed, err := filepath.Glob(include) + if err != nil { + return ofiles, err + } + globedInclude = append(globedInclude, globed...) } - if ignore_xdev { wwlog.Debug("Ignoring cross-device (xdev) files") } @@ -285,72 +291,47 @@ func FindFilterFiles( } dev := path_stat.Sys().(*syscall.Stat_t).Dev - - includeDirs := []string{} - ignoreDirs := []string{} - err = filepath.Walk(".", func(location string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - if location == "." { - return nil - } - - var file string - if info.IsDir() { - file = location + "/" - } else { - file = location - } - - if ignore_xdev && info.Sys().(*syscall.Stat_t).Dev != dev { - wwlog.Debug("Ignored (cross-device): %s", file) - return nil - } - - for _, ignoreDir := range ignoreDirs { - if strings.HasPrefix(location, ignoreDir) { - wwlog.Debug("Ignored (dir): %s", file) - return nil - } + for _, inc := range globedInclude { + wwlog.Debug("inc %s", inc) + stat, err := os.Stat(inc) + if os.IsNotExist(err) { + // there may be broken softlinks + continue + } else if err != nil { + return ofiles, err } - for i, pattern := range ignore { - m, err := filepath.Match(pattern, location) - if err != nil { - return err - } else if m { - wwlog.Debug("Ignored (%d): %s", i, file) - if info.IsDir() { - ignoreDirs = append(ignoreDirs, file) + if stat.IsDir() { + // get the rest of dir + err = filepath.WalkDir(inc, func(location string, info fs.DirEntry, err error) error { + if err != nil { + return err + } + if location == "." { + return nil + } + fsInfo, err := info.Info() + if err != nil { + return err + } + if ignore_xdev && fsInfo.Sys().(*syscall.Stat_t).Dev != dev { + wwlog.Debug("Ignored (cross-device): %s", location) + return nil + } + for _, ignored_pat := range ignorePattern { + if ignored, _ := filepath.Match(ignored_pat, location); ignored { + return filepath.SkipDir + } } - return nil - } - } - - for _, includeDir := range includeDirs { - if strings.HasPrefix(location, includeDir) { - wwlog.Debug("Included (dir): %s", file) ofiles = append(ofiles, location) return nil - } - } - for i, pattern := range include { - m, err := filepath.Match(pattern, location) + }) if err != nil { - return err - } else if m { - wwlog.Debug("Included (%d): %s", i, file) - ofiles = append(ofiles, location) - if info.IsDir() { - includeDirs = append(includeDirs, file) - } - return nil + return ofiles, err } + } else { + ofiles = append(ofiles, inc) } - - return nil - }) + } return ofiles, err } @@ -421,7 +402,7 @@ func SliceAddUniqueElement(array []string, add string) []string { } /* -Appends a string slice to another slice. Guarantess that the elements are uniq. +Appends a string slice to another slice. Guarantees that the elements are uniq. */ func SliceAppendUniq(array []string, add []string) []string { ret := array @@ -690,17 +671,7 @@ func BuildFsImage( if err != nil { return errors.Wrapf(err, "Failed to create image directory for %s: %s", name, imagePath) } - wwlog.Debug("Created image directory for %s: %s", name, imagePath) - - // TODO: why is this done if the container must already exist? - err = os.MkdirAll(path.Dir(rootfsPath), 0755) - if err != nil { - return errors.Wrapf(err, "Failed to create fs directory for %s: %s", name, rootfsPath) - } - - wwlog.Debug("Created fs directory for %s: %s", name, rootfsPath) - cwd, err := os.Getwd() if err != nil { return err @@ -713,7 +684,7 @@ func BuildFsImage( if err != nil { return errors.Wrapf(err, "Failed chdir to fs directory for %s: %s", name, rootfsPath) } - + wwlog.Verbose("changed to: %s", rootfsPath) files, err := FindFilterFiles( ".", include,