diff --git a/CreateMt-Targets.patch b/CreateMt-Targets.patch deleted file mode 100644 index 10ac360..0000000 --- a/CreateMt-Targets.patch +++ /dev/null @@ -1,1004 +0,0 @@ -diff --git a/CHANGELOG.md b/CHANGELOG.md -index 04a40983..a36b77a3 100644 ---- a/CHANGELOG.md -+++ b/CHANGELOG.md -@@ -56,7 +56,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - The environment variable `WW_CONTAINER_SHELL` is defined in a `wwctl - container shell` environment to indicate the container in use. #579 - - Network interface configuration (`ifcfg`) files now include the -- interface name and type. #574 -+ interface name and type. #457 - - ### Fixed - -diff --git a/Makefile b/Makefile -index b494dd69..6363b653 100644 ---- a/Makefile -+++ b/Makefile -@@ -96,7 +96,7 @@ export GOPROXY - WW_GO_BUILD_TAGS := containers_image_openpgp containers_image_ostree - - # Default target --all: config vendor wwctl wwclient bash_completion.d man_pages config_defaults print_defaults wwapid wwapic wwapird -+all: config vendor wwctl wwclient bash_completion.d man_pages config_defaults print_defaults wwapid wwapic wwapird print_mnts - - # Validate source and build all packages - build: lint test-it vet all -@@ -247,6 +247,7 @@ config_defaults: vendor cmd/config_defaults/config_defaults.go - print_defaults: vendor cmd/print_defaults/print_defaults.go - cd cmd/print_defaults && go build -ldflags="-X 'github.com/hpcng/warewulf/internal/pkg/warewulfconf.ConfigFile=./etc/warewulf.conf'" -o ../../print_defaults - -+ - update_configuration: vendor cmd/update_configuration/update_configuration.go - cd cmd/update_configuration && go build -ldflags="-X 'github.com/hpcng/warewulf/internal/pkg/warewulfconf.ConfigFile=./etc/warewulf.conf'\ - -X 'github.com/hpcng/warewulf/internal/pkg/node.ConfigFile=./etc/nodes.conf'"\ -diff --git a/etc/warewulf.conf b/etc/warewulf.conf -index d0a0c4db..a5b9a346 100644 ---- a/etc/warewulf.conf -+++ b/etc/warewulf.conf -@@ -29,3 +29,7 @@ nfs: - mount options: defaults - mount: false - systemd name: nfs-server -+container mounts: -+ - source: /etc/resolv.conf -+ dest: /etc/resolv.conf -+ readonly: true -diff --git a/internal/app/wwctl/container/exec/child/main.go b/internal/app/wwctl/container/exec/child/main.go -index dda8b757..bf32ad17 100644 ---- a/internal/app/wwctl/container/exec/child/main.go -+++ b/internal/app/wwctl/container/exec/child/main.go -@@ -7,11 +7,15 @@ import ( - "fmt" - "os" - "path" -+ "path/filepath" -+ "strings" - "syscall" -- "time" - - "github.com/hpcng/warewulf/internal/pkg/container" -+ "github.com/hpcng/warewulf/internal/pkg/node" -+ "github.com/hpcng/warewulf/internal/pkg/overlay" - "github.com/hpcng/warewulf/internal/pkg/util" -+ "github.com/hpcng/warewulf/internal/pkg/warewulfconf" - "github.com/hpcng/warewulf/internal/pkg/wwlog" - "github.com/pkg/errors" - "github.com/spf13/cobra" -@@ -29,43 +33,130 @@ func CobraRunE(cmd *cobra.Command, args []string) error { - wwlog.Error("Unknown Warewulf container: %s", containerName) - os.Exit(1) - } -+ conf, err := warewulfconf.New() -+ if err != nil { -+ wwlog.Verbose("Couldn't get warewulf ocnfiguration: %s", err) -+ } -+ mountPts := conf.MountsContainer -+ mountPts = append(container.InitMountPnts(binds), mountPts...) -+ // check for valid mount points -+ lowerObjects := checkMountPoints(containerName, mountPts) -+ if len(lowerObjects) != 0 { -+ if tempDir == "" { -+ tempDir, err = os.MkdirTemp(os.TempDir(), "overlay") -+ if err != nil { -+ wwlog.Warn("couldn't create temp dir for overlay", err) -+ lowerObjects = []string{} -+ tempDir = "" -+ } -+ } -+ // need to create a overlay, where the lower layer contains -+ // the missing mount points -+ if tempDir != "" { -+ wwlog.Verbose("for ephermal mount use tempdir %s", tempDir) -+ // ignore errors as we are doomed if a tmp dir couldn't be written -+ _ = os.Mkdir(path.Join(tempDir, "work"), os.ModePerm) -+ _ = os.Mkdir(path.Join(tempDir, "lower"), os.ModePerm) -+ _ = os.Mkdir(path.Join(tempDir, "nodeoverlay"), os.ModePerm) -+ for _, obj := range lowerObjects { -+ newFile := "" -+ if !strings.HasSuffix(obj, "/") { -+ newFile = filepath.Base(obj) -+ obj = filepath.Dir(obj) -+ } -+ err = os.MkdirAll(filepath.Join(tempDir, "lower", obj), os.ModePerm) -+ if err != nil { -+ wwlog.Warn("couldn't create directory for mounts: %s", err) -+ } -+ if newFile != "" { -+ desc, err := os.Create(filepath.Join(tempDir, "lower", obj, newFile)) -+ if err != nil { -+ wwlog.Warn("couldn't create directory for mounts: %s", err) -+ } -+ defer desc.Close() -+ } -+ } -+ } -+ } - containerPath := container.RootFsDir(containerName) -- fileStat, _ := os.Stat(path.Join(containerPath, "/etc/passwd")) -- unixStat := fileStat.Sys().(*syscall.Stat_t) -- passwdTime := time.Unix(int64(unixStat.Ctim.Sec), int64(unixStat.Ctim.Nsec)) -- fileStat, _ = os.Stat(path.Join(containerPath, "/etc/group")) -- unixStat = fileStat.Sys().(*syscall.Stat_t) -- groupTime := time.Unix(int64(unixStat.Ctim.Sec), int64(unixStat.Ctim.Nsec)) -- wwlog.Debug("passwd: %v", passwdTime) -- wwlog.Debug("group: %v", groupTime) -- -- err := syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, "") -+ err = syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, "") - if err != nil { - return errors.Wrap(err, "failed to mount") - } -+ ps1Str := fmt.Sprintf("[%s] Warewulf> ", containerName) -+ if len(lowerObjects) != 0 && nodename == "" { -+ options := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", -+ path.Join(tempDir, "lower"), containerPath, path.Join(tempDir, "work")) -+ wwlog.Debug("overlay options: %s", options) -+ err = syscall.Mount("overlay", containerPath, "overlay", 0, options) -+ if err != nil { -+ wwlog.Warn(fmt.Sprintf("Couldn't create overlay for ephermal mount points: %s", err)) -+ } -+ } else if nodename != "" { -+ nodeDB, err := node.New() -+ if err != nil { -+ wwlog.Error("Could not open node configuration: %s", err) -+ os.Exit(1) -+ } -+ -+ nodes, err := nodeDB.FindAllNodes() -+ if err != nil { -+ wwlog.Error("Could not get node list: %s", err) -+ os.Exit(1) -+ } -+ nodes = node.FilterByName(nodes, []string{nodename}) -+ if len(nodes) != 1 { -+ wwlog.Error("No single node idendified with %s", nodename) -+ os.Exit(1) -+ } -+ overlays := nodes[0].SystemOverlay.GetSlice() -+ overlays = append(overlays, nodes[0].RuntimeOverlay.GetSlice()...) -+ err = overlay.BuildOverlayIndir(nodes[0], overlays, path.Join(tempDir, "nodeoverlay")) -+ if err != nil { -+ wwlog.Error("Could not build overlay: %s", err) -+ os.Exit(1) -+ } -+ options := fmt.Sprintf("lowerdir=%s:%s:%s", -+ path.Join(tempDir, "lower"), containerPath, path.Join(tempDir, "nodeoverlay")) -+ wwlog.Debug("overlay options: %s", options) -+ err = syscall.Mount("overlay", containerPath, "overlay", 0, options) -+ if err != nil { -+ wwlog.Warn(fmt.Sprintf("Couldn't create overlay for node render overlay: %s", err)) -+ os.Exit(1) -+ } -+ ps1Str = fmt.Sprintf("[%s|ro|%s] Warewulf> ", containerName, nodename) -+ } -+ if !util.IsWriteAble(containerPath) && nodename == "" { -+ wwlog.Verbose("mounting %s ro", containerPath) -+ ps1Str = fmt.Sprintf("[%s|ro] Warewulf> ", containerName) -+ err = syscall.Mount(containerPath, containerPath, "", syscall.MS_BIND, "") -+ if err != nil { -+ return errors.Wrap(err, "failed to prepare bind mount") -+ } -+ err = syscall.Mount(containerPath, containerPath, "", syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_BIND, "") -+ if err != nil { -+ return errors.Wrap(err, "failed to remount ro") -+ } -+ } - - err = syscall.Mount("/dev", path.Join(containerPath, "/dev"), "", syscall.MS_BIND, "") - if err != nil { - return errors.Wrap(err, "failed to mount /dev") - } - -- for _, b := range binds { -- var source string -- var dest string -- -- bind := util.SplitValidPaths(b, ":") -- source = bind[0] -- -- if len(bind) == 1 { -- dest = source -- } else { -- dest = bind[1] -- } -- -- err := syscall.Mount(source, path.Join(containerPath, dest), "", syscall.MS_BIND, "") -+ for _, mntPnt := range mountPts { -+ err = syscall.Mount(mntPnt.Source, path.Join(containerPath, mntPnt.Dest), "", syscall.MS_BIND, "") - if err != nil { -- fmt.Printf("BIND ERROR: %s\n", err) -- os.Exit(1) -+ wwlog.Warn("Couldn't mount %s to %s: %s", mntPnt.Source, mntPnt.Dest, err) -+ } else if mntPnt.ReadOnly { -+ err = syscall.Mount(mntPnt.Source, path.Join(containerPath, mntPnt.Dest), "", syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_BIND, "") -+ if err != nil { -+ wwlog.Warn("failed to following mount readonly: %s", mntPnt.Source) -+ } else { -+ wwlog.Verbose("mounted readonly from host to container: %s:%s", mntPnt.Source, mntPnt.Dest) -+ } -+ } else { -+ wwlog.Verbose("mounted from host to container: %s:%s", mntPnt.Source, mntPnt.Dest) - } - } - -@@ -84,27 +175,39 @@ func CobraRunE(cmd *cobra.Command, args []string) error { - return errors.Wrap(err, "failed to mount proc") - } - -- os.Setenv("PS1", fmt.Sprintf("[%s] Warewulf> ", containerName)) -+ os.Setenv("PS1", ps1Str) - os.Setenv("PATH", "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin") - os.Setenv("HISTFILE", "/dev/null") - -- err = syscall.Exec(args[1], args[1:], os.Environ()) -- if err != nil { -- wwlog.Error("%s", err) -- os.Exit(1) -- } -- fileStat, _ = os.Stat(path.Join(containerPath, "/etc/passwd")) -- unixStat = fileStat.Sys().(*syscall.Stat_t) -- if passwdTime.Before(time.Unix(int64(unixStat.Ctim.Sec), int64(unixStat.Ctim.Nsec))) { -- wwlog.Warn("/etc/passwd has been modified, maybe you want to run syncuser") -- } -- wwlog.Debug("passwd: %v", time.Unix(int64(unixStat.Ctim.Sec), int64(unixStat.Ctim.Nsec))) -- fileStat, _ = os.Stat(path.Join(containerPath, "/etc/group")) -- unixStat = fileStat.Sys().(*syscall.Stat_t) -- if groupTime.Before(time.Unix(int64(unixStat.Ctim.Sec), int64(unixStat.Ctim.Nsec))) { -- wwlog.Warn("/etc/group has been modified, maybe you want to run syncuser") -- } -- wwlog.Debug("group: %v", time.Unix(int64(unixStat.Ctim.Sec), int64(unixStat.Ctim.Nsec))) -- -+ _ = syscall.Exec(args[1], args[1:], os.Environ()) -+ /* -+ Exec replaces the actual program, so nothing to do here afterwards -+ */ - return nil - } -+ -+/* -+Check if the bind mount points exists in the given container. Returns -+the invalid mount points. Directories always have '/' as suffix -+*/ -+func checkMountPoints(containerName string, binds []*warewulfconf.MountEntry) (overlayObjects []string) { -+ overlayObjects = []string{} -+ for _, b := range binds { -+ _, err := os.Stat(b.Source) -+ if err != nil { -+ wwlog.Debug("Couldn't stat %s create no mount point in container", b.Source) -+ continue -+ } -+ wwlog.Debug("Checking in container for %s", path.Join(container.RootFsDir(containerName), b.Dest)) -+ if _, err = os.Stat(path.Join(container.RootFsDir(containerName), b.Dest)); err != nil { -+ if os.IsNotExist(err) { -+ if util.IsDir(b.Dest) && !strings.HasSuffix(b.Dest, "/") { -+ b.Dest += "/" -+ } -+ overlayObjects = append(overlayObjects, b.Dest) -+ wwlog.Debug("Container %s, needs following path: %s", containerName, b.Dest) -+ } -+ } -+ } -+ return overlayObjects -+} -diff --git a/internal/app/wwctl/container/exec/child/root.go b/internal/app/wwctl/container/exec/child/root.go -index 0b7dde7c..d7060995 100644 ---- a/internal/app/wwctl/container/exec/child/root.go -+++ b/internal/app/wwctl/container/exec/child/root.go -@@ -5,17 +5,21 @@ import "github.com/spf13/cobra" - var ( - baseCmd = &cobra.Command{ - DisableFlagsInUseLine: true, -- Use: "__child", -- Hidden: true, -- RunE: CobraRunE, -- Args: cobra.MinimumNArgs(1), -- FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true}, -+ Use: "__child", -+ Hidden: true, -+ RunE: CobraRunE, -+ Args: cobra.MinimumNArgs(1), -+ FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true}, - } -- binds []string -+ binds []string -+ tempDir string -+ nodename string - ) - - func init() { -- baseCmd.PersistentFlags().StringArrayVarP(&binds, "bind", "b", []string{}, "bind points") -+ baseCmd.Flags().StringVarP(&nodename, "node", "n", "", "create ro overlay for given node") -+ baseCmd.Flags().StringArrayVarP(&binds, "bind", "b", []string{}, "bind points") -+ baseCmd.Flags().StringVar(&tempDir, "tempdir", "", "tempdir") - } - - // GetRootCommand returns the root cobra.Command for the application. -diff --git a/internal/app/wwctl/container/exec/main.go b/internal/app/wwctl/container/exec/main.go -index 722ba479..bc622de2 100644 ---- a/internal/app/wwctl/container/exec/main.go -+++ b/internal/app/wwctl/container/exec/main.go -@@ -17,9 +17,26 @@ import ( - "github.com/spf13/cobra" - ) - -+/* -+fork off a process with a new PID space -+*/ - func runContainedCmd(args []string) error { -+ var err error -+ if tempDir == "" { -+ tempDir, err = os.MkdirTemp(os.TempDir(), "overlay") -+ if err != nil { -+ wwlog.Warn("couldn't create temp dir for overlay", err) -+ } -+ defer func() { -+ err = os.RemoveAll(tempDir) -+ if err != nil { -+ wwlog.Warn("Couldn't remove temp dir for ephermal mounts:", err) -+ } -+ }() -+ } -+ logStr := fmt.Sprint(wwlog.GetLogLevel()) - wwlog.Verbose("Running contained command: %s", args[1:]) -- c := exec.Command("/proc/self/exe", append([]string{"container", "exec", "__child"}, args...)...) -+ c := exec.Command("/proc/self/exe", append([]string{"--loglevel", logStr, "--tempdir", tempDir, "container", "exec", "__child"}, args...)...) - - c.SysProcAttr = &syscall.SysProcAttr{ - Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS, -@@ -30,6 +47,11 @@ func runContainedCmd(args []string) error { - - if err := c.Run(); err != nil { - fmt.Printf("Command exited non-zero, not rebuilding/updating VNFS image\n") -+ // defer is not called before os.Exit(0) -+ err = os.RemoveAll(tempDir) -+ if err != nil { -+ wwlog.Warn("Couldn't remove temp dir for ephermal mounts:", err) -+ } - os.Exit(0) - } - return nil -@@ -38,6 +60,8 @@ func runContainedCmd(args []string) error { - func CobraRunE(cmd *cobra.Command, args []string) error { - - containerName := args[0] -+ os.Setenv("WW_CONTAINER_SHELL", containerName) -+ - var allargs []string - - if !container.ValidSource(containerName) { -@@ -48,6 +72,9 @@ func CobraRunE(cmd *cobra.Command, args []string) error { - for _, b := range binds { - allargs = append(allargs, "--bind", b) - } -+ if nodeName != "" { -+ allargs = append(allargs, "--node", nodeName) -+ } - allargs = append(allargs, args...) - containerPath := container.RootFsDir(containerName) - -@@ -109,3 +136,10 @@ func CobraRunE(cmd *cobra.Command, args []string) error { - - return nil - } -+func SetBinds(myBinds []string) { -+ binds = append(binds, myBinds...) -+} -+ -+func SetNode(myNode string) { -+ nodeName = myNode -+} -diff --git a/internal/app/wwctl/container/exec/root.go b/internal/app/wwctl/container/exec/root.go -index db8bea41..e5fc577b 100644 ---- a/internal/app/wwctl/container/exec/root.go -+++ b/internal/app/wwctl/container/exec/root.go -@@ -27,12 +27,16 @@ var ( - } - SyncUser bool - binds []string -+ tempDir string -+ nodeName string - ) - - func init() { - baseCmd.AddCommand(child.GetCommand()) - baseCmd.PersistentFlags().StringArrayVarP(&binds, "bind", "b", []string{}, "Bind a local path into the container (must exist)") - baseCmd.PersistentFlags().BoolVar(&SyncUser, "syncuser", false, "Synchronize UIDs/GIDs from host to container") -+ baseCmd.PersistentFlags().StringVar(&tempDir, "tempdir", "", "Use tempdir for constructing the overlay fs (only used if mount points don't exist in container)") -+ baseCmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "Create a read only view of the container for the given node") - } - - // GetRootCommand returns the root cobra.Command for the application. -diff --git a/internal/app/wwctl/container/shell/main.go b/internal/app/wwctl/container/shell/main.go -index 04a095ec..b7c3fbde 100644 ---- a/internal/app/wwctl/container/shell/main.go -+++ b/internal/app/wwctl/container/shell/main.go -@@ -4,11 +4,10 @@ - package shell - - import ( -- "fmt" - "os" -- "os/exec" -- "syscall" -+ "path" - -+ cntexec "github.com/hpcng/warewulf/internal/app/wwctl/container/exec" - "github.com/hpcng/warewulf/internal/pkg/container" - "github.com/hpcng/warewulf/internal/pkg/wwlog" - "github.com/spf13/cobra" -@@ -23,28 +22,32 @@ func CobraRunE(cmd *cobra.Command, args []string) error { - wwlog.Error("Unknown Warewulf container: %s", containerName) - os.Exit(1) - } -- -- for _, b := range binds { -- allargs = append(allargs, "--bind", b) -+ /* -+ for _, b := range binds { -+ allargs = append(allargs, "--bind", b) -+ } -+ */ -+ shellName := os.Getenv("SHELL") -+ if !container.ValidSource(containerName) { -+ wwlog.Error("Unknown Warewulf container: %s", containerName) -+ os.Exit(1) - } -- allargs = append(allargs, args...) -- allargs = append(allargs, "/usr/bin/bash") -- -- c := exec.Command("/proc/self/exe", append([]string{"container", "exec"}, allargs...)...) -- -- //c := exec.Command("/bin/sh") -- c.SysProcAttr = &syscall.SysProcAttr{ -- Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS, -+ var shells []string -+ if shellName == "" { -+ shells = append(shells, "/bin/bash") -+ } else { -+ shells = append(shells, shellName, "/bin/bash") - } -- c.Stdin = os.Stdin -- c.Stdout = os.Stdout -- c.Stderr = os.Stderr -- -- os.Setenv("WW_CONTAINER_SHELL", containerName) -- -- if err := c.Run(); err != nil { -- fmt.Println(err) -- os.Exit(1) -+ for _, s := range shells { -+ if _, err := os.Stat(path.Join(container.RootFsDir(containerName), s)); err == nil { -+ shellName = s -+ break -+ } - } -- return nil -+ args = append(args, shellName) -+ allargs = append(allargs, args...) -+ wwlog.Debug("Calling exec with args: %s", allargs) -+ cntexec.SetBinds(binds) -+ cntexec.SetNode(nodeName) -+ return cntexec.CobraRunE(cmd, allargs) - } -diff --git a/internal/app/wwctl/container/shell/root.go b/internal/app/wwctl/container/shell/root.go -index 59f6c56b..dcec3f06 100644 ---- a/internal/app/wwctl/container/shell/root.go -+++ b/internal/app/wwctl/container/shell/root.go -@@ -22,11 +22,13 @@ var ( - }, - FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true}, - } -- binds []string -+ binds []string -+ nodeName string - ) - - func init() { - baseCmd.PersistentFlags().StringArrayVarP(&binds, "bind", "b", []string{}, "Bind a local path into the container (must exist)") -+ baseCmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "Create a read only view of the container for the given node") - } - - // GetRootCommand returns the root cobra.Command for the application. -diff --git a/internal/app/wwctl/root.go b/internal/app/wwctl/root.go -index 81fe2a26..cc8927d6 100644 ---- a/internal/app/wwctl/root.go -+++ b/internal/app/wwctl/root.go -@@ -31,12 +31,14 @@ var ( - } - verboseArg bool - DebugFlag bool -+ LogLevel int - ) - - func init() { - rootCmd.PersistentFlags().BoolVarP(&verboseArg, "verbose", "v", false, "Run with increased verbosity.") - rootCmd.PersistentFlags().BoolVarP(&DebugFlag, "debug", "d", false, "Run with debugging messages enabled.") -- -+ rootCmd.PersistentFlags().IntVar(&LogLevel, "loglevel", wwlog.INFO, "Set log level to given string") -+ _ = rootCmd.PersistentFlags().MarkHidden("loglevel") - rootCmd.SetUsageTemplate(help.UsageTemplate) - rootCmd.SetHelpTemplate(help.HelpTemplate) - -@@ -65,6 +67,9 @@ func rootPersistentPreRunE(cmd *cobra.Command, args []string) error { - } else { - wwlog.SetLogLevel(wwlog.INFO) - } -+ if LogLevel != wwlog.INFO { -+ wwlog.SetLogLevel(LogLevel) -+ } - return nil - } - -diff --git a/internal/pkg/api/apiconfig/container/container.go b/internal/pkg/api/apiconfig/container/container.go -index 8bc2ba23..beb29544 100644 ---- a/internal/pkg/api/apiconfig/container/container.go -+++ b/internal/pkg/api/apiconfig/container/container.go -@@ -192,12 +192,6 @@ func ContainerImport(cip *wwapiv1.ContainerImportParameter) (containerName strin - return - } - -- fmt.Printf("Updating the container's /etc/resolv.conf\n") -- err = util.CopyFile("/etc/resolv.conf", path.Join(container.RootFsDir(cip.Name), "/etc/resolv.conf")) -- if err != nil { -- wwlog.Warn("Could not copy /etc/resolv.conf into container: %s", err) -- } -- - fmt.Printf("Building container: %s\n", cip.Name) - err = container.Build(cip.Name, true) - if err != nil { -diff --git a/internal/pkg/api/container/container.go b/internal/pkg/api/container/container.go -index 757ea873..6683c75c 100644 ---- a/internal/pkg/api/container/container.go -+++ b/internal/pkg/api/container/container.go -@@ -197,12 +197,6 @@ func ContainerImport(cip *wwapiv1.ContainerImportParameter) (containerName strin - return - } - -- wwlog.Info("Updating the container's /etc/resolv.conf") -- err = util.CopyFile("/etc/resolv.conf", path.Join(container.RootFsDir(cip.Name), "/etc/resolv.conf")) -- if err != nil { -- wwlog.Warn("Could not copy /etc/resolv.conf into container: %s", err) -- } -- - err = container.SyncUids(cip.Name, !cip.SyncUser) - if err != nil && !cip.SyncUser { - err = fmt.Errorf("error in user sync, fix error and run 'syncuser' manually: %s", err) -diff --git a/internal/pkg/buildconfig/defaults.go b/internal/pkg/buildconfig/defaults.go -index 24cb2d40..332a2f6d 100644 ---- a/internal/pkg/buildconfig/defaults.go -+++ b/internal/pkg/buildconfig/defaults.go -@@ -19,6 +19,7 @@ var ( - release string = "UNDEF" - wwclientdir string = "UNDEF" - datadir string = "UNDEF" -+ tmpdir string = "UNDEF" - ) - - func BINDIR() string { -@@ -90,3 +91,8 @@ func WWCLIENTDIR() string { - wwlog.Debug("WWCLIENTDIR = '%s'", wwclientdir) - return wwclientdir - } -+ -+func TMPDIR() string { -+ wwlog.Debug("TMPDIR = '%s'", tmpdir) -+ return tmpdir -+} -diff --git a/internal/pkg/buildconfig/setconfigs.go.in b/internal/pkg/buildconfig/setconfigs.go.in -index e10303e3..f049ffeb 100644 ---- a/internal/pkg/buildconfig/setconfigs.go.in -+++ b/internal/pkg/buildconfig/setconfigs.go.in -@@ -15,4 +15,5 @@ func init() { - version = "@VERSION@" - release = "@RELEASE@" - wwclientdir = "@WWCLIENTDIR@" -+ tmpdir = "@TMPDIR@" - } -diff --git a/internal/pkg/container/mountpoints.go b/internal/pkg/container/mountpoints.go -new file mode 100644 -index 00000000..727ff35c ---- /dev/null -+++ b/internal/pkg/container/mountpoints.go -@@ -0,0 +1,35 @@ -+package container -+ -+import ( -+ "strings" -+ -+ "github.com/hpcng/warewulf/internal/pkg/warewulfconf" -+ "github.com/hpcng/warewulf/internal/pkg/wwlog" -+) -+ -+/* -+Create a slice iof MntDetails from a string slice with following -+format "source:[:destination][:readonly]" if destination is not -+given, the source is used as destination -+*/ -+func InitMountPnts(binds []string) (mounts []*warewulfconf.MountEntry) { -+ wwlog.Debug("Trying to mount following mount points: %s", mounts) -+ for _, b := range binds { -+ bind := strings.Split(b, ":") -+ dest := bind[0] -+ if len(bind) >= 2 { -+ dest = bind[1] -+ } -+ readonly := false -+ if len(bind) >= 3 && bind[2] == "ro" { -+ readonly = true -+ } -+ mntPnt := warewulfconf.MountEntry{ -+ Source: bind[0], -+ Dest: dest, -+ ReadOnly: readonly, -+ } -+ mounts = append(mounts, &mntPnt) -+ } -+ return mounts -+} -diff --git a/internal/pkg/util/util.go b/internal/pkg/util/util.go -index 3f97fbb5..6ad445cb 100644 ---- a/internal/pkg/util/util.go -+++ b/internal/pkg/util/util.go -@@ -24,12 +24,16 @@ import ( - - // reserve some number of cpus for system/warwulfd usage - var processLimitedReserve int = 4 -+ - // maximum number of concurrent spawned processes --var processLimitedMax = MaxInt(1, runtime.NumCPU() - processLimitedReserve) -+var processLimitedMax = MaxInt(1, runtime.NumCPU()-processLimitedReserve) -+ - // Channel used as semaphore to specififed processLimitedMax - var processLimitedChan = make(chan int, processLimitedMax) -+ - // Current number of processes started + queued - var processLimitedNum int32 = 0 -+ - // Counter over total history of started processes - var processLimitedCounter uint32 = 0 - -@@ -53,7 +57,7 @@ func ProcessLimitedStatus() (running int32, queued int32) { - return - } - --func MaxInt( a int, b int ) int { -+func MaxInt(a int, b int) int { - if a > b { - return a - } -@@ -65,7 +69,7 @@ func FirstError(errs ...error) (err error) { - for _, e := range errs { - if err == nil { - err = e -- }else if e != nil { -+ } else if e != nil { - wwlog.ErrorExc(e, "Unhandled error") - } - } -@@ -207,7 +211,7 @@ func ValidateOrDie(message string, pattern string, expr string) { - } - } - --//****************************************************************************** -+// ****************************************************************************** - func FindFiles(path string) []string { - var ret []string - -@@ -244,7 +248,7 @@ func FindFiles(path string) []string { - return ret - } - --//****************************************************************************** -+// ****************************************************************************** - func FindFilterFiles( - path string, - include []string, -@@ -265,7 +269,7 @@ func FindFilterFiles( - return ofiles, errors.Wrapf(err, "Failed to change path: %s", path) - } - -- for i := range(ignore) { -+ 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]) -@@ -282,7 +286,6 @@ 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 { -@@ -306,13 +309,13 @@ func FindFilterFiles( - return nil - } - -- for _, ignoreDir := range(ignoreDirs) { -+ for _, ignoreDir := range ignoreDirs { - if strings.HasPrefix(location, ignoreDir) { - wwlog.Debug("Ignored (dir): %s", file) - return nil - } - } -- for i, pattern := range(ignore) { -+ for i, pattern := range ignore { - m, err := filepath.Match(pattern, location) - if err != nil { - return err -@@ -325,14 +328,14 @@ func FindFilterFiles( - } - } - -- for _, includeDir := range(includeDirs) { -+ 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) { -+ for i, pattern := range include { - m, err := filepath.Match(pattern, location) - if err != nil { - return err -@@ -352,7 +355,7 @@ func FindFilterFiles( - return ofiles, err - } - --//****************************************************************************** -+// ****************************************************************************** - func ExecInteractive(command string, a ...string) error { - wwlog.Debug("ExecInteractive(%s, %s)", command, a) - c := exec.Command(command, a...) -@@ -538,20 +541,22 @@ func AppendLines(fileName string, lines []string) error { - return nil - } - --/******************************************************************************* -+/* -+****************************************************************************** -+ - Create an archive using cpio - */ - func CpioCreate( - ifiles []string, - ofile string, - format string, -- cpio_args ...string ) (err error) { -+ cpio_args ...string) (err error) { - - args := []string{ - "--quiet", - "--create", - "-H", format, -- "--file=" + ofile } -+ "--file=" + ofile} - - args = append(args, cpio_args...) - -@@ -574,14 +579,16 @@ func CpioCreate( - wwlog.Debug(string(out)) - } - -- return FirstError(err, <- err_in) -+ return FirstError(err, <-err_in) - } - --/******************************************************************************* -+/* -+****************************************************************************** -+ - Compress a file using gzip or pigz - */ - func FileGz( -- file string ) (err error) { -+ file string) (err error) { - - file_gz := file + ".gz" - -@@ -608,41 +615,41 @@ func FileGz( - proc := exec.Command( - compressor, - "--keep", -- file ) -+ file) - - out, err := proc.CombinedOutput() - if len(out) > 0 { - outStr := string(out[:]) - if err != nil && strings.HasSuffix(compressor, "gzip") && strings.Contains(outStr, "unrecognized option") { -- var gzippedFile *os.File -- var gzipStderr io.ReadCloser -- -+ var gzippedFile *os.File -+ var gzipStderr io.ReadCloser -+ - /* Older version of gzip, try it another way: */ - wwlog.Verbose("%s does not recognize the --keep flag, trying redirected stdout", compressor) -- -+ - /* Open the output file for writing: */ - gzippedFile, err = os.Create(file_gz) - if err != nil { - return errors.Wrapf(err, "Unable to open compressed image file for writing: %s", file_gz) - } -- -+ - /* We'll execute gzip with output to stdout and attach stdout to the compressed file we just - created: -- */ -+ */ - proc = exec.Command( - compressor, - "--stdout", -- file ) -+ file) - proc.Stdout = gzippedFile - gzipStderr, err = proc.StderrPipe() - if err != nil { - return errors.Wrapf(err, "Unable to open stderr pipe for compression program: %s", compressor) - } -- -+ - /* Execute the command: */ - err = proc.Start() - if err != nil { -- _ = proc.Wait() -+ _ = proc.Wait() - gzippedFile.Close() - os.Remove(file_gz) - err = errors.Wrapf(err, "Unable to successfully execute compression program: %s", compressor) -@@ -664,7 +671,9 @@ func FileGz( - return err - } - --/******************************************************************************* -+/* -+****************************************************************************** -+ - Create an archive using cpio - */ - func BuildFsImage( -@@ -675,7 +684,7 @@ func BuildFsImage( - ignore []string, - ignore_xdev bool, - format string, -- cpio_args ...string ) (err error) { -+ cpio_args ...string) (err error) { - - err = os.MkdirAll(path.Dir(imagePath), 0755) - if err != nil { -@@ -709,16 +718,16 @@ func BuildFsImage( - ".", - include, - ignore, -- ignore_xdev ) -+ ignore_xdev) - if err != nil { - return errors.Wrapf(err, "Failed discovering files for %s: %s", name, rootfsPath) - } - - err = CpioCreate( - files, -- imagePath, -+ imagePath, - format, -- cpio_args...) -+ cpio_args...) - if err != nil { - return errors.Wrapf(err, "Failed creating image for %s: %s", name, imagePath) - } -@@ -727,15 +736,17 @@ func BuildFsImage( - - err = FileGz(imagePath) - if err != nil { -- return errors.Wrapf(err, "Failed to compress image for %s: %s", name, imagePath + ".gz") -+ return errors.Wrapf(err, "Failed to compress image for %s: %s", name, imagePath+".gz") - } - -- wwlog.Info("Compressed image for %s: %s", name, imagePath + ".gz") -+ wwlog.Info("Compressed image for %s: %s", name, imagePath+".gz") - - return nil - } - --/******************************************************************************* -+/* -+****************************************************************************** -+ - Runs wwctl command - */ - func RunWWCTL(args ...string) (out []byte, err error) { -@@ -744,7 +755,7 @@ func RunWWCTL(args ...string) (out []byte, err error) { - running, queued := ProcessLimitedStatus() - - wwlog.Verbose("Starting wwctl process %d (%d running, %d queued): %v", -- index, running, queued, args ) -+ index, running, queued, args) - - proc := exec.Command("wwctl", args...) - -@@ -787,3 +798,21 @@ func ByteToString(b int64) string { - } - return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp]) - } -+ -+/* -+Check if the w-bit of a file/dir. unix.Access(file,unix.W_OK) will -+not show this. -+*/ -+func IsWriteAble(path string) bool { -+ info, err := os.Stat(path) -+ if err != nil { -+ return false -+ } -+ -+ // Check if the user bit is enabled in file permission -+ if info.Mode().Perm()&(1<<(uint(7))) == 0 { -+ wwlog.Debug("Write permission bit is not set for: %s", path) -+ return false -+ } -+ return true -+} -diff --git a/internal/pkg/warewulfconf/datastructure.go b/internal/pkg/warewulfconf/datastructure.go -index c3eebf8c..170c94cd 100644 ---- a/internal/pkg/warewulfconf/datastructure.go -+++ b/internal/pkg/warewulfconf/datastructure.go -@@ -7,19 +7,20 @@ import ( - ) - - type ControllerConf struct { -- WWInternal int `yaml:"WW_INTERNAL"` -- Comment string `yaml:"comment,omitempty"` -- Ipaddr string `yaml:"ipaddr"` -- Ipaddr6 string `yaml:"ipaddr6,omitempty"` -- Netmask string `yaml:"netmask"` -- Network string `yaml:"network,omitempty"` -- Ipv6net string `yaml:"ipv6net,omitempty"` -- Fqdn string `yaml:"fqdn,omitempty"` -- Warewulf *WarewulfConf `yaml:"warewulf"` -- Dhcp *DhcpConf `yaml:"dhcp"` -- Tftp *TftpConf `yaml:"tftp"` -- Nfs *NfsConf `yaml:"nfs"` -- current bool -+ WWInternal int `yaml:"WW_INTERNAL"` -+ Comment string `yaml:"comment,omitempty"` -+ Ipaddr string `yaml:"ipaddr"` -+ Ipaddr6 string `yaml:"ipaddr6,omitempty"` -+ Netmask string `yaml:"netmask"` -+ Network string `yaml:"network,omitempty"` -+ Ipv6net string `yaml:"ipv6net,omitempty"` -+ Fqdn string `yaml:"fqdn,omitempty"` -+ Warewulf *WarewulfConf `yaml:"warewulf"` -+ Dhcp *DhcpConf `yaml:"dhcp"` -+ Tftp *TftpConf `yaml:"tftp"` -+ Nfs *NfsConf `yaml:"nfs"` -+ MountsContainer []*MountEntry `yaml:"container mounts"` -+ current bool - } - - type WarewulfConf struct { -@@ -59,6 +60,16 @@ type NfsExportConf struct { - Mount bool `default:"true" yaml:"mount"` - } - -+/* -+Describe a mount point for a container exec -+*/ -+type MountEntry struct { -+ Source string `yaml:"source" default:"/etc/resolv.conf"` -+ Dest string `yaml:"dest,omitempty" default:"/etc/resolv.conf"` -+ ReadOnly bool `yaml:"readonly,omitempty" default:"false"` -+ Options string `yaml:"options,omitempty"` // ignored at the moment -+} -+ - func (s *NfsConf) Unmarshal(unmarshal func(interface{}) error) error { - if err := defaults.Set(s); err != nil { - return err -diff --git a/print_mnts b/print_mnts -new file mode 100755 -index 00000000..28986c71 -Binary files /dev/null and b/print_mnts differ diff --git a/Fix-for-CVE-2022-41723.patch b/Fix-for-CVE-2022-41723.patch deleted file mode 100644 index 487b244..0000000 --- a/Fix-for-CVE-2022-41723.patch +++ /dev/null @@ -1,370 +0,0 @@ -From bc2ccefd481066a25257a5f654f20d601491150f Mon Sep 17 00:00:00 2001 -From: Christian Goll -Date: Thu, 16 Feb 2023 11:44:09 +0100 -Subject: [PATCH] Fix for CVE-2022-41723 - ---- - go.mod | 74 +++++++++++++++++++++++++++++++++++++++++++++++++-- - go.sum | 83 ++++++---------------------------------------------------- - 2 files changed, 80 insertions(+), 77 deletions(-) - -diff --git a/go.mod b/go.mod -index 6159de0..32debb2 100644 ---- a/go.mod -+++ b/go.mod -@@ -1,6 +1,6 @@ - module github.com/hpcng/warewulf - --go 1.16 -+go 1.19 - - require ( - github.com/brotherpowers/ipsubnet v0.0.0-20170914094241-30bc98f0a5b1 -@@ -20,10 +20,80 @@ require ( - github.com/spf13/cobra v1.1.1 - github.com/stretchr/testify v1.7.0 - github.com/talos-systems/go-smbios v0.1.1 -- golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 -+ golang.org/x/term v0.3.0 - google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e - google.golang.org/grpc v1.45.0 - google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0 - google.golang.org/protobuf v1.27.1 - gopkg.in/yaml.v2 v2.4.0 - ) -+ -+require ( -+ github.com/BurntSushi/toml v0.3.1 // indirect -+ github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3 // indirect -+ github.com/VividCortex/ewma v1.1.1 // indirect -+ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect -+ github.com/apex/log v1.4.0 // indirect -+ github.com/beorn7/perks v1.0.1 // indirect -+ github.com/cespare/xxhash/v2 v2.1.1 // indirect -+ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect -+ github.com/containerd/containerd v1.5.0-beta.4 // indirect -+ github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b // indirect -+ github.com/containers/ocicrypt v1.1.0 // indirect -+ github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect -+ github.com/cyphar/filepath-securejoin v0.2.2 // indirect -+ github.com/davecgh/go-spew v1.1.1 // indirect -+ github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect -+ github.com/docker/distribution v2.7.1+incompatible // indirect -+ github.com/docker/docker v1.4.2-0.20191219165747-a9416c67da9f // indirect -+ github.com/docker/docker-credential-helpers v0.6.3 // indirect -+ github.com/docker/go-connections v0.4.0 // indirect -+ github.com/docker/go-metrics v0.0.1 // indirect -+ github.com/docker/go-units v0.4.0 // indirect -+ github.com/ghodss/yaml v1.0.0 // indirect -+ github.com/gogo/protobuf v1.3.2 // indirect -+ github.com/golang/snappy v0.0.3 // indirect -+ github.com/gorilla/mux v1.7.4 // indirect -+ github.com/inconshreveable/mousetrap v1.0.0 // indirect -+ github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a // indirect -+ github.com/klauspost/compress v1.12.1 // indirect -+ github.com/klauspost/pgzip v1.2.5 // indirect -+ github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a // indirect -+ github.com/mattn/go-colorable v0.1.9 // indirect -+ github.com/mattn/go-isatty v0.0.14 // indirect -+ github.com/mattn/go-runewidth v0.0.9 // indirect -+ github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect -+ github.com/miekg/pkcs11 v1.0.3 // indirect -+ github.com/moby/sys/mountinfo v0.4.1 // indirect -+ github.com/mtrmac/gpgme v0.1.2 // indirect -+ github.com/opencontainers/go-digest v1.0.0 // indirect -+ github.com/opencontainers/runc v1.0.0-rc93 // indirect -+ github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d // indirect -+ github.com/pmezard/go-difflib v1.0.0 // indirect -+ github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9 // indirect -+ github.com/prometheus/client_golang v1.7.1 // indirect -+ github.com/prometheus/client_model v0.2.0 // indirect -+ github.com/prometheus/common v0.10.0 // indirect -+ github.com/prometheus/procfs v0.6.0 // indirect -+ github.com/rootless-containers/proto v0.1.0 // indirect -+ github.com/russross/blackfriday/v2 v2.0.1 // indirect -+ github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect -+ github.com/sirupsen/logrus v1.8.1 // indirect -+ github.com/spf13/pflag v1.0.5 // indirect -+ github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980 // indirect -+ github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect -+ github.com/ulikunitz/xz v0.5.10 // indirect -+ github.com/urfave/cli v1.22.4 // indirect -+ github.com/vbatts/go-mtree v0.5.0 // indirect -+ github.com/vbauerster/mpb/v5 v5.3.0 // indirect -+ go.etcd.io/bbolt v1.3.5 // indirect -+ go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 // indirect -+ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect -+ golang.org/x/net v0.4.1-0.20230214201333-88ed8ca3307d //indirect -+ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a // indirect -+ golang.org/x/sys v0.3.0 // indirect -+ golang.org/x/text v0.5.0 // indirect -+ gopkg.in/square/go-jose.v2 v2.5.1 // indirect -+ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect -+ sigs.k8s.io/yaml v1.3.0 // indirect -+) -diff --git a/go.sum b/go.sum -index a58cfc2..49bc9a1 100644 ---- a/go.sum -+++ b/go.sum -@@ -10,28 +10,18 @@ cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6T - cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= - cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= - cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= --cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= --cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= --cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= --cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= - cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= - cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= - cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= --cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= --cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= --cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= - cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= - cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= - cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= - cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= - cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= - cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= --cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= - cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= - cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= - cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= --cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= --cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= - dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= - github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774/go.mod h1:6/0dYRLLXyJjbkIPeeGyoJ/eKOSI0eU6eTlCBYibgd0= - github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -@@ -112,7 +102,6 @@ github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8n - github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= - github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= - github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= --github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= - github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= - github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= - github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -@@ -337,8 +326,6 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb - github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= - github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= - github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= --github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= --github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= - github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= - github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= - github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -@@ -364,26 +351,21 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a - github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= - github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= - github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= --github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= - github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= - github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= - github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= - github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= - github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= - github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= --github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= - github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y= - github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= - github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= - github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= --github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= - github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= - github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= - github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= - github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= - github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= --github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= --github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= - github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= - github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= - github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -@@ -405,7 +387,6 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= - github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= - github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= --github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= - github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= - github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.0 h1:ESEyqQqXXFIcImj/BE8oKEX37Zsuceb2cZI+EL/zNCY= - github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.0/go.mod h1:XnLCLFp3tjoZJszVKjfpyAK6J8sYIcQXWQxmqLWF21I= -@@ -755,9 +736,7 @@ github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf - github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= - github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= - github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= --github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= - github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= --github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= - github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= - github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= - github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= -@@ -773,7 +752,6 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= - go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= - go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= - go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= --go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= - go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= - go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= - go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -@@ -845,7 +823,6 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR - golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= - golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= - golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= --golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= - golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= - golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= - golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -@@ -857,26 +834,20 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL - golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= - golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= - golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= --golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= --golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= --golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= --golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= - golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= --golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= - golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= - golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= - golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= - golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= - golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= - golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= --golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= --golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -+golang.org/x/net v0.4.1-0.20230214201333-88ed8ca3307d h1:KHU/KRz6+/yWyRHEC24m7T5gou5VSh62duch955ktBY= -+golang.org/x/net v0.4.1-0.20230214201333-88ed8ca3307d/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= - golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= - golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= - golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= - golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= - golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= --golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= - golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= - golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= - golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -@@ -936,15 +907,9 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w - golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= --golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= --golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= --golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= --golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= --golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= --golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -@@ -959,13 +924,12 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w - golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= --golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= - golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= --golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= --golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -+golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -+golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= - golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= --golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= --golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -+golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= -+golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= - golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= - golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= - golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -@@ -973,8 +937,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= - golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= - golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= - golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= --golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= --golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -+golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -+golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= - golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= - golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= - golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -@@ -1016,18 +980,8 @@ golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapK - golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= - golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= - golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= --golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= - golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= --golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= --golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= --golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= --golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= --golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= --golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= - golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= --golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= --golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= --golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= - golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= - golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= - golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -@@ -1044,19 +998,12 @@ google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb - google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= - google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= - google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= --google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= - google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= --google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= --google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= --google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= --google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= --google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= - google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= - google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= - google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= - google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= - google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= --google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= - google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= - google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= - google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -@@ -1077,19 +1024,9 @@ google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvx - google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= - google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= - google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= --google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= - google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= --google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= --google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= --google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= --google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= - google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= --google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= - google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= --google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= --google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= --google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= --google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= - google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= - google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e h1:fNKDNuUyC4WH+inqDMpfXDdfvwfYILbsX+oskGZ8hxg= - google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -@@ -1105,10 +1042,7 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ - google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= - google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= - google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= --google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= --google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= - google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= --google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= - google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= - google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= - google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -@@ -1175,7 +1109,6 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh - honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= - honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= - honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= --honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= - k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= - k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= - k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= --- -2.39.1 - diff --git a/config-ww4.sh b/config-ww4.sh index 1a33d30..ddb5351 100644 --- a/config-ww4.sh +++ b/config-ww4.sh @@ -61,7 +61,7 @@ network: $IP4NET range start: $DYNSTART range end: $DYNEND EOF - exit 1 + exit 0 fi DYNSIZE=20 @@ -78,7 +78,7 @@ DYNEND="${DYNPRE}.${DYNEND}" if is_ip_in_range $IP4 ; then echo "ERROR: ip address is in range for dynamic address, please set this manually" - exit 1 + exit 0 fi diff --git a/grub-boot.patch b/grub-boot.patch new file mode 100644 index 0000000..9f5e8da --- /dev/null +++ b/grub-boot.patch @@ -0,0 +1,1647 @@ +diff --git a/CHANGELOG.md b/CHANGELOG.md +index 70280717..93304050 100644 +--- a/CHANGELOG.md ++++ b/CHANGELOG.md +@@ -124,6 +124,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 + - Support importing containers with symlinked `/bin/sh` #797 + - Don't panic on malformed passwd #527 + ++- first container imported container is added to the default profile ++- grub in combination can now be set as boot method with `warewulf.grubboot: true` in ++ `warewulf.conf`. For unknown nodes `grub.efi` and `shim.efi` will be extracted from ++ the host running warewulf. If node has container it will get these binaries from the ++ container image. ++ ++- Added support for booting nodes with grub. Enable this behavior using ++ warewulf.grubboot: true in warewulf.conf. For unknown nodes, grub.efi ++ and shim.efi are extracted from the Warewulf host. If the booted node ++ has a container these binaries are extracted from the container image. + ## [4.4.0] 2023-01-18 + + ### Added +diff --git a/Makefile b/Makefile +index 4ef5a5bf..e85ac05b 100644 +--- a/Makefile ++++ b/Makefile +@@ -94,8 +94,10 @@ install: build docs + install -d -m 0755 $(DESTDIR)$(WWCHROOTDIR) + install -d -m 0755 $(DESTDIR)$(WWPROVISIONDIR) + install -d -m 0755 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/$(WWCLIENTDIR) ++ install -d -m 0755 $(DESTDIR)$(WWOVERLAYDIR)/host/$(TFTPDIR)/warewulf/ + install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/examples + install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/ipxe ++ install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/grub + install -d -m 0755 $(DESTDIR)$(BASHCOMPDIR) + install -d -m 0755 $(DESTDIR)$(MANDIR)/man1 + install -d -m 0755 $(DESTDIR)$(MANDIR)/man5 +@@ -112,6 +114,8 @@ install: build docs + test -f $(DESTDIR)$(DATADIR)/warewulf/defaults.conf || install -m 0644 etc/defaults.conf $(DESTDIR)$(DATADIR)/warewulf/defaults.conf + for f in etc/examples/*.ww; do install -m 0644 $$f $(DESTDIR)$(WWCONFIGDIR)/examples/; done + for f in etc/ipxe/*.ipxe; do install -m 0644 $$f $(DESTDIR)$(WWCONFIGDIR)/ipxe/; done ++ install -m 0644 etc/grub/grub.cfg.ww $(DESTDIR)$(WWCONFIGDIR)/grub/grub.cfg.ww ++ install -m 0644 etc/grub/chainload.ww $(DESTDIR)$(WWOVERLAYDIR)/host$(TFTPDIR)/warewulf/grub.cfg.ww + (cd overlays && find * -type f -exec install -D -m 0644 {} $(DESTDIR)$(WWOVERLAYDIR)/{} \;) + (cd overlays && find * -type d -exec mkdir -pv $(DESTDIR)$(WWOVERLAYDIR)/{} \;) + (cd overlays && find * -type l -exec cp -av {} $(DESTDIR)$(WWOVERLAYDIR)/{} \;) +diff --git a/etc/grub/chainload.ww b/etc/grub/chainload.ww +new file mode 100644 +index 00000000..916aff84 +--- /dev/null ++++ b/etc/grub/chainload.ww +@@ -0,0 +1,18 @@ ++# This file is autogenerated by warewulf ++# Host: {{ .BuildHost }} ++# Time: {{ .BuildTime }} ++# Source: {{ .BuildSource }} ++echo "================================================================================" ++echo "Warewulf v4 now booting with grub" ++echo ++echo "Warewulf Controller: {{.Ipaddr}}" ++echo "Chain loading specific grub.cfg" ++uri="(http,{{.Ipaddr}}:{{.Warewulf.Port}})/efiboot/grub.cfg" ++echo $uri ++configfile $uri ++echo "MESSAGE: This node is unconfigured. Please have your system administrator add a" ++echo " configuration for this node with HW address: ${net_default_mac}" ++echo "" ++echo "Rebooting in 1 minute..." ++sleep 60 ++reboot +diff --git a/etc/grub/grub.cfg.ww b/etc/grub/grub.cfg.ww +new file mode 100644 +index 00000000..79357fae +--- /dev/null ++++ b/etc/grub/grub.cfg.ww +@@ -0,0 +1,29 @@ ++echo "================================================================================" ++echo "Warewulf v4 now booting with grub: {{.Fqdn}} ({{.Hwaddr}})" ++echo "================================================================================" ++uri="(http,{{.Ipaddr}}:9873)/provision/${net_default_mac}?assetkey=" ++kernel="${uri}&stage=kernel" ++container="${uri}&stage=container&compress=gz" ++system="${uri}&stage=system&compress=gz" ++runtime="${uri}&stage=runtime&compress=gz" ++echo "Warewulf Controller: {{.Ipaddr}}" ++{{if .KernelOverride }} ++echo "Kernel: {{.KernelOverride}}" ++{{else}} ++echo "Kernel: {{.ContainerName}} (container default)" ++{{end}} ++echo "KernelArgs: {{.KernelArgs}}" ++linux $kernel wwid=${net_default_mac} {{.KernelArgs}} ++if [ x$? = x0 ] ; then ++echo "Loading Container: {{.ContainerName}}" ++initrd $container $system $runtime ++echo ++boot ++else ++echo "MESSAGE: This node is unconfigured. Please have your system administrator add a" ++echo " configuration for this node with HW address: ${net_default_mac}" ++echo "" ++echo "Rebooting in 1 minute..." ++sleep 60 ++reboot ++fi +diff --git a/go.mod b/go.mod +index c9e07d29..4d304414 100644 +--- a/go.mod ++++ b/go.mod +@@ -11,6 +11,7 @@ require ( + github.com/creasty/defaults v1.7.0 + github.com/fatih/color v1.15.0 + github.com/golang/glog v1.0.0 ++ github.com/golang/protobuf v1.5.2 + github.com/google/uuid v1.3.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 + github.com/manifoldco/promptui v0.9.0 +@@ -56,7 +57,6 @@ require ( + github.com/docker/go-units v0.4.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect +- github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.3 // indirect + github.com/gorilla/mux v1.7.4 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect +diff --git a/internal/app/wwctl/container/delete/main.go b/internal/app/wwctl/container/delete/main.go +index 576ccef7..71e5a3d7 100644 +--- a/internal/app/wwctl/container/delete/main.go ++++ b/internal/app/wwctl/container/delete/main.go +@@ -5,7 +5,8 @@ import ( + + "github.com/hpcng/warewulf/internal/pkg/api/container" + "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" +- "github.com/hpcng/warewulf/internal/pkg/api/util" ++ apiutil "github.com/hpcng/warewulf/internal/pkg/api/util" ++ + "github.com/spf13/cobra" + ) + +@@ -13,8 +14,9 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) { + cdp := &wwapiv1.ContainerDeleteParameter{ + ContainerNames: args, + } ++ + if !SetYes { +- yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to delete container %s", args)) ++ yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to delete container %s", args)) + if !yes { + return + } +diff --git a/internal/app/wwctl/container/exec/main.go b/internal/app/wwctl/container/exec/main.go +index bc622de2..f34ada9f 100644 +--- a/internal/app/wwctl/container/exec/main.go ++++ b/internal/app/wwctl/container/exec/main.go +@@ -133,7 +133,6 @@ func CobraRunE(cmd *cobra.Command, args []string) error { + wwlog.Error("Could not build container %s: %s", containerName, err) + os.Exit(1) + } +- + return nil + } + func SetBinds(myBinds []string) { +diff --git a/internal/app/wwctl/container/imprt/main.go b/internal/app/wwctl/container/imprt/main.go +index 1ec28322..4114b45c 100644 +--- a/internal/app/wwctl/container/imprt/main.go ++++ b/internal/app/wwctl/container/imprt/main.go +@@ -1,7 +1,9 @@ + package imprt + + import ( +- "github.com/hpcng/warewulf/internal/pkg/api/container" ++ "github.com/hpcng/warewulf/internal/pkg/container" ++ ++ apicontainer "github.com/hpcng/warewulf/internal/pkg/api/container" + "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" + "github.com/spf13/cobra" + ) +@@ -13,6 +15,9 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) { + if len(args) == 2 { + name = args[1] + } ++ if list, _ := container.ListSources(); len(list) == 0 { ++ SetDefault = true ++ } + + cip := &wwapiv1.ContainerImportParameter{ + Source: args[0], +@@ -23,6 +28,7 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) { + Default: SetDefault, + SyncUser: SyncUser, + } +- _, err = container.ContainerImport(cip) ++ ++ _, err = apicontainer.ContainerImport(cip) + return + } +diff --git a/internal/app/wwctl/profile/delete/main.go b/internal/app/wwctl/profile/delete/main.go +index 71d3dabf..137ff4ae 100644 +--- a/internal/app/wwctl/profile/delete/main.go ++++ b/internal/app/wwctl/profile/delete/main.go +@@ -5,6 +5,7 @@ import ( + "os" + + "github.com/hpcng/warewulf/internal/pkg/node" ++ "github.com/hpcng/warewulf/internal/pkg/util" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/manifoldco/promptui" + "github.com/pkg/errors" +@@ -13,6 +14,9 @@ import ( + + func CobraRunE(cmd *cobra.Command, args []string) error { + var count int ++ if util.InSlice(args, "default") { ++ return fmt.Errorf("can't delete the `default` profile ") ++ } + + nodeDB, err := node.New() + if err != nil { +diff --git a/internal/pkg/api/profile/profile.go b/internal/pkg/api/profile/profile.go +index e6ca6c68..50e60afc 100644 +--- a/internal/pkg/api/profile/profile.go ++++ b/internal/pkg/api/profile/profile.go +@@ -24,7 +24,8 @@ func ProfileSet(set *wwapiv1.ProfileSetParameter) (err error) { + if err != nil { + return errors.Wrap(err, "Could not open database") + } +- return apinode.DbSave(&nodeDB) ++ dbError := apinode.DbSave(&nodeDB) ++ return dbError + } + + // ProfileSetParameterCheck does error checking on ProfileSetParameter. +diff --git a/internal/pkg/config/warewulf.go b/internal/pkg/config/warewulf.go +index e8f08ac5..9a0dc3f4 100644 +--- a/internal/pkg/config/warewulf.go ++++ b/internal/pkg/config/warewulf.go +@@ -10,4 +10,5 @@ type WarewulfConf struct { + EnableHostOverlay bool `yaml:"host overlay" default:"true"` + Syslog bool `yaml:"syslog" default:"false"` + DataStore string `yaml:"datastore" default:"/var/lib/warewulf"` ++ GrubBoot bool `yaml:"grubboot" default:"false"` + } +diff --git a/internal/pkg/configure/tftp.go b/internal/pkg/configure/tftp.go +index 82e023f3..058ca059 100644 +--- a/internal/pkg/configure/tftp.go ++++ b/internal/pkg/configure/tftp.go +@@ -1,12 +1,12 @@ + package configure + + import ( +- "fmt" + "os" + "path" + + warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" + "github.com/hpcng/warewulf/internal/pkg/util" ++ "github.com/hpcng/warewulf/internal/pkg/warewulfd" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + ) + +@@ -20,28 +20,34 @@ func TFTP() error { + return err + } + +- fmt.Printf("Writing PXE files to: %s\n", tftpdir) +- copyCheck := make(map[string]bool) +- for _, f := range controller.TFTP.IpxeBinaries { +- if !path.IsAbs(f) { +- f = path.Join(controller.Paths.Ipxesource, f) +- } +- if copyCheck[f] { +- continue +- } +- copyCheck[f] = true +- err = util.SafeCopyFile(f, path.Join(tftpdir, path.Base(f))) ++ if controller.Warewulf.GrubBoot { ++ err := warewulfd.CopyShimGrub() + if err != nil { +- wwlog.Warn("ipxe binary could not be copied, booting may not work: %s", err) ++ wwlog.Warn("error when copying shim/grub binaries: %s", err) ++ } ++ } else { ++ wwlog.Info("Writing PXE files to: %s", tftpdir) ++ copyCheck := make(map[string]bool) ++ for _, f := range controller.TFTP.IpxeBinaries { ++ if !path.IsAbs(f) { ++ f = path.Join(controller.Paths.Ipxesource, f) ++ } ++ if copyCheck[f] { ++ continue ++ } ++ copyCheck[f] = true ++ err = util.SafeCopyFile(f, path.Join(tftpdir, path.Base(f))) ++ if err != nil { ++ wwlog.Warn("ipxe binary could not be copied, booting may not work: %s", err) ++ } + } + } +- + if !controller.TFTP.Enabled { + wwlog.Info("Warewulf does not auto start TFTP services due to disable by warewulf.conf") + os.Exit(0) + } + +- fmt.Printf("Enabling and restarting the TFTP services\n") ++ wwlog.Info("Enabling and restarting the TFTP services") + err = util.SystemdStart(controller.TFTP.SystemdName) + if err != nil { + wwlog.Error("%s", err) +diff --git a/internal/pkg/container/shimgrub.go b/internal/pkg/container/shimgrub.go +new file mode 100644 +index 00000000..a590690f +--- /dev/null ++++ b/internal/pkg/container/shimgrub.go +@@ -0,0 +1,94 @@ ++package container ++ ++import ( ++ "os" ++ "path" ++ "path/filepath" ++ ++ "github.com/hpcng/warewulf/internal/pkg/wwlog" ++) ++ ++func shimDirs() []string { ++ return []string{ ++ `/usr/share/efi/x86_64/`, ++ `/usr/lib64/efi/`, ++ `/boot/efi/EFI/*/`, ++ } ++} ++func shimNames() []string { ++ return []string{ ++ `shim.efi`, ++ `shim-sles.efi`, ++ `shimx64.efi`, ++ `shim-susesigned.efi`, ++ } ++} ++ ++func grubDirs() []string { ++ return []string{ ++ `/usr/lib64/efi/`, ++ `/usr/share/grub2/*-efi/`, ++ `/usr/share/efi/*/`, ++ `/boot/efi/EFI/*/`, ++ } ++} ++func grubNames() []string { ++ return []string{ ++ `grub-tpm.efi`, ++ `grub.efi`, ++ `grubx64.efi`, ++ `grubia32.efi`, ++ `grubaa64.efi`, ++ `grubarm.efi`, ++ } ++} ++ ++/* ++find the path of the shim binary in container ++*/ ++func ShimFind(container string) string { ++ var container_path string ++ if container != "" { ++ container_path = RootFsDir(container) ++ } else { ++ container_path = "/" ++ } ++ wwlog.Debug("Finding grub under paths: %s", container_path) ++ return BootLoaderFindPath(container_path, shimNames, shimDirs) ++} ++ ++/* ++find a grub.efi in the used container ++*/ ++func GrubFind(container string) string { ++ var container_path string ++ if container != "" { ++ container_path = RootFsDir(container) ++ } else { ++ container_path = "/" ++ } ++ wwlog.Debug("Finding grub under paths: %s", container_path) ++ return BootLoaderFindPath(container_path, grubNames, grubDirs) ++} ++ ++/* ++find the path of the shim binary in container ++*/ ++func BootLoaderFindPath(cpath string, names func() []string, paths func() []string) string { ++ for _, bdir := range paths() { ++ wwlog.Debug("Checking shim directory: %s", bdir) ++ for _, bname := range names() { ++ wwlog.Debug("Checking for bootloader name: %s", bname) ++ shimPaths, _ := filepath.Glob(path.Join(cpath, bdir, bname)) ++ for _, shimPath := range shimPaths { ++ wwlog.Debug("Checking for bootloader path: %s", shimPath) ++ // Only succeeds if shimPath exists and, if a ++ // symlink, links to a path that also exists ++ if _, err := os.Stat(shimPath); err == nil { ++ return shimPath ++ } ++ } ++ } ++ } ++ return "" ++} +diff --git a/internal/pkg/node/constructors.go b/internal/pkg/node/constructors.go +index 08c27b19..053d3a63 100644 +--- a/internal/pkg/node/constructors.go ++++ b/internal/pkg/node/constructors.go +@@ -31,6 +31,7 @@ defaultnode: + init: /sbin/init + root: initramfs + ipxe template: default ++ boot method: ipxe + profiles: + - default + network devices: +@@ -306,6 +307,36 @@ func (config *NodeYaml) ListAllProfiles() []string { + return ret + } + ++/* ++return a map where the key is the profile id ++*/ ++func (config *NodeYaml) MapAllProfiles() (retMap map[string]*NodeInfo, err error) { ++ retMap = make(map[string]*NodeInfo) ++ profileList, err := config.FindAllProfiles() ++ if err != nil { ++ return ++ } ++ for _, pr := range profileList { ++ retMap[pr.Id.Get()] = &pr ++ } ++ return ++} ++ ++/* ++return a map where the key is the node id ++*/ ++func (config *NodeYaml) MapAllNodes() (retMap map[string]*NodeInfo, err error) { ++ retMap = make(map[string]*NodeInfo) ++ nodeList, err := config.FindAllNodes() ++ if err != nil { ++ return ++ } ++ for _, nd := range nodeList { ++ retMap[nd.Id.Get()] = &nd ++ } ++ return ++} ++ + /* + FindDiscoverableNode returns the first discoverable node and an + interface to associate with the discovered interface. If the node has +diff --git a/internal/pkg/node/datastructure.go b/internal/pkg/node/datastructure.go +index 989c007a..34432454 100644 +--- a/internal/pkg/node/datastructure.go ++++ b/internal/pkg/node/datastructure.go +@@ -154,6 +154,7 @@ type NodeInfo struct { + ClusterName Entry + ContainerName Entry + Ipxe Entry ++ Grub Entry + RuntimeOverlay Entry + SystemOverlay Entry + Root Entry +diff --git a/internal/pkg/node/util.go b/internal/pkg/node/util.go +index e90efee6..8e1273c3 100644 +--- a/internal/pkg/node/util.go ++++ b/internal/pkg/node/util.go +@@ -6,6 +6,9 @@ import ( + "strings" + ) + ++/* ++get node by its hardware/MAC address, return error otherwise ++*/ + func (config *NodeYaml) FindByHwaddr(hwa string) (NodeInfo, error) { + if _, err := net.ParseMAC(hwa); err != nil { + return NodeInfo{}, errors.New("invalid hardware address: " + hwa) +@@ -26,6 +29,9 @@ func (config *NodeYaml) FindByHwaddr(hwa string) (NodeInfo, error) { + return ret, errors.New("No nodes found with HW Addr: " + hwa) + } + ++/* ++get node by its ip address, return error otherwise ++*/ + func (config *NodeYaml) FindByIpaddr(ipaddr string) (NodeInfo, error) { + if addr := net.ParseIP(ipaddr); addr == nil { + return NodeInfo{}, errors.New("invalid IP:" + ipaddr) +diff --git a/internal/pkg/overlay/datastructure.go b/internal/pkg/overlay/datastructure.go +index 15c9d3d5..0adc2f39 100644 +--- a/internal/pkg/overlay/datastructure.go ++++ b/internal/pkg/overlay/datastructure.go +@@ -49,19 +49,15 @@ func InitStruct(nodeInfo *node.NodeInfo) TemplateStruct { + controller := warewulfconf.Get() + nodeDB, err := node.New() + if err != nil { +- wwlog.Error("%s", err) +- os.Exit(1) ++ wwlog.Warn("Problems opening nodes.conf: %s", err) + } +- allNodes, err := nodeDB.FindAllNodes() ++ tstruct.AllNodes, err = nodeDB.FindAllNodes() + if err != nil { +- wwlog.Error("%s", err) +- os.Exit(1) ++ wwlog.Warn("couldn't get all nodes: %s", err) + } + // init some convenience vars + tstruct.Id = nodeInfo.Id.Get() + tstruct.Hostname = nodeInfo.Id.Get() +- // Backwards compatibility for templates using "Keys" +- tstruct.AllNodes = allNodes + tstruct.Nfs = *controller.NFS + tstruct.Dhcp = *controller.DHCP + tstruct.Tftp = *controller.TFTP +diff --git a/internal/pkg/warewulfd/copyshim.go b/internal/pkg/warewulfd/copyshim.go +new file mode 100644 +index 00000000..44ea45f4 +--- /dev/null ++++ b/internal/pkg/warewulfd/copyshim.go +@@ -0,0 +1,45 @@ ++package warewulfd ++ ++import ( ++ "fmt" ++ "os" ++ "path" ++ ++ warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" ++ "github.com/hpcng/warewulf/internal/pkg/wwlog" ++ ++ "github.com/hpcng/warewulf/internal/pkg/container" ++ "github.com/hpcng/warewulf/internal/pkg/util" ++) ++ ++/* ++Copies the default shim, which is the shim located on host ++to the tftp directory ++*/ ++ ++func CopyShimGrub() (err error) { ++ conf := warewulfconf.Get() ++ wwlog.Debug("copy shim and grub binaries from host") ++ shimPath := container.ShimFind("") ++ if shimPath == "" { ++ return fmt.Errorf("no shim found on the host os") ++ } ++ err = util.CopyFile(shimPath, path.Join(conf.Paths.Tftpdir, "warewulf", "shim.efi")) ++ if err != nil { ++ return err ++ } ++ _ = os.Chmod(path.Join(conf.Paths.Tftpdir, "warewulf", "shim.efi"), 0o755) ++ grubPath := container.GrubFind("") ++ if grubPath == "" { ++ return fmt.Errorf("no grub found on host os") ++ } ++ err = util.CopyFile(grubPath, path.Join(conf.Paths.Tftpdir, "warewulf", "grub.efi")) ++ if err != nil { ++ return err ++ } ++ _ = os.Chmod(path.Join(conf.Paths.Tftpdir, "warewulf", "grub.efi"), 0o755) ++ err = util.CopyFile(grubPath, path.Join(conf.Paths.Tftpdir, "warewulf", "grubx64.efi")) ++ _ = os.Chmod(path.Join(conf.Paths.Tftpdir, "warewulf", "grubx64.efi"), 0o755) ++ ++ return ++} +diff --git a/internal/pkg/warewulfd/parser.go b/internal/pkg/warewulfd/parser.go +index 1e62dd0f..063d10b4 100644 +--- a/internal/pkg/warewulfd/parser.go ++++ b/internal/pkg/warewulfd/parser.go +@@ -5,6 +5,7 @@ import ( + "strconv" + "strings" + ++ "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/pkg/errors" + ) + +@@ -16,6 +17,7 @@ type parserInfo struct { + uuid string + stage string + overlay string ++ efifile string + compress string + } + +@@ -25,16 +27,22 @@ func parseReq(req *http.Request) (parserInfo, error) { + url := strings.Split(req.URL.Path, "?")[0] + path_parts := strings.Split(url, "/") + +- if len(path_parts) != 3 { ++ if len(path_parts) < 3 { + return ret, errors.New("unknown path components in GET") + } + + // handle when stage was passed in the url path /[stage]/hwaddr + stage := path_parts[1] +- hwaddr := path_parts[2] +- hwaddr = strings.ReplaceAll(hwaddr, "-", ":") +- hwaddr = strings.ToLower(hwaddr) +- ++ hwaddr := "" ++ if stage != "efiboot" { ++ hwaddr = path_parts[2] ++ hwaddr = strings.ReplaceAll(hwaddr, "-", ":") ++ hwaddr = strings.ToLower(hwaddr) ++ } else if len(path_parts) > 3 { ++ ret.efifile = strings.Join(path_parts[2:], "/") ++ } else { ++ ret.efifile = path_parts[2] ++ } + ret.hwaddr = hwaddr + ret.ipaddr = strings.Split(req.RemoteAddr, ":")[0] + ret.remoteport, _ = strconv.Atoi(strings.Split(req.RemoteAddr, ":")[1]) +@@ -63,6 +71,8 @@ func parseReq(req *http.Request) (parserInfo, error) { + ret.stage = "system" + } else if stage == "overlay-runtime" { + ret.stage = "runtime" ++ } else if stage == "efiboot" { ++ ret.stage = "efiboot" + } + } + +@@ -76,7 +86,11 @@ func parseReq(req *http.Request) (parserInfo, error) { + return ret, errors.New("no stage encoded in GET") + } + if ret.hwaddr == "" { +- return ret, errors.New("no hwaddr encoded in GET") ++ ret.hwaddr = ArpFind(ret.ipaddr) ++ wwlog.Verbose("node mac not encoded, arp cache got %s for %s", ret.hwaddr, ret.ipaddr) ++ if ret.hwaddr == "" { ++ return ret, errors.New("no hwaddr encoded in GET") ++ } + } + if ret.ipaddr == "" { + return ret, errors.New("could not obtain ipaddr from HTTP request") +diff --git a/internal/pkg/warewulfd/provision.go b/internal/pkg/warewulfd/provision.go +index f2851a26..b04a8a91 100644 +--- a/internal/pkg/warewulfd/provision.go ++++ b/internal/pkg/warewulfd/provision.go +@@ -3,7 +3,9 @@ package warewulfd + import ( + "bytes" + "errors" ++ "fmt" + "net/http" ++ "os" + "path" + "strconv" + "strings" +@@ -17,7 +19,7 @@ import ( + "github.com/hpcng/warewulf/internal/pkg/wwlog" + ) + +-type iPxeTemplate struct { ++type templateVars struct { + Message string + WaitTime string + Hostname string +@@ -32,13 +34,10 @@ type iPxeTemplate struct { + KernelOverride string + } + +-var status_stages = map[string]string{ +- "ipxe": "IPXE", +- "kernel": "KERNEL", +- "kmods": "KMODS_OVERLAY", +- "system": "SYSTEM_OVERLAY", +- "runtime": "RUNTIME_OVERLAY"} +- ++/* ++Handles all the http request for warewulfd, different stages are encoded ++in the GET request. ++*/ + func ProvisionSend(w http.ResponseWriter, req *http.Request) { + conf := warewulfconf.Get() + +@@ -59,8 +58,17 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { + } + } + ++ status_stages := map[string]string{ ++ "efiboot": "EFI", ++ "ipxe": "IPXE", ++ "kernel": "KERNEL", ++ "kmods": "KMODS_OVERLAY", ++ "system": "SYSTEM_OVERLAY", ++ "runtime": "RUNTIME_OVERLAY"} ++ + status_stage := status_stages[rinfo.stage] +- var stage_file string ++ var stage_file string = "" ++ updateSentDB := true + + // TODO: when module version is upgraded to go1.18, should be 'any' type + var tmpl_data interface{} +@@ -83,13 +91,13 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { + wwlog.Error("%s (unknown/unconfigured node)", rinfo.hwaddr) + if rinfo.stage == "ipxe" { + stage_file = path.Join(conf.Paths.Sysconfdir, "/warewulf/ipxe/unconfigured.ipxe") +- tmpl_data = iPxeTemplate{ ++ tmpl_data = templateVars{ + Hwaddr: rinfo.hwaddr} + } + + } else if rinfo.stage == "ipxe" { + stage_file = path.Join(conf.Paths.Sysconfdir, "warewulf/ipxe/"+node.Ipxe.Get()+".ipxe") +- tmpl_data = iPxeTemplate{ ++ tmpl_data = templateVars{ + Id: node.Id.Get(), + Cluster: node.ClusterName.Get(), + Fqdn: node.Id.Get(), +@@ -100,8 +108,20 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { + ContainerName: node.ContainerName.Get(), + KernelArgs: node.Kernel.Args.Get(), + KernelOverride: node.Kernel.Override.Get()} +- + } else if rinfo.stage == "kernel" { ++ if DBGetWWinit(node.Id.Get()) { ++ DBReset(node.Id.Get()) ++ } ++ if DBSize(node.Id.Get()) == 0 { ++ fd, err := os.Open(path.Join(path.Join(conf.Paths.Tftpdir, "warewulf", "grub.cfg"))) ++ if err != nil { ++ wwlog.Warn("no grub.cfg detected for potential tftp boot node: %s", node) ++ } else { ++ defer fd.Close() ++ DBAddImage(node.Id.Get(), "grub.cfg", fd) ++ } ++ ++ } + if node.Kernel.Override.Defined() { + stage_file = kernel.KernelImage(node.Kernel.Override.Get()) + } else if node.ContainerName.Defined() { +@@ -137,7 +157,6 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { + } else { + context = rinfo.stage + } +- + stage_file, err = getOverlayFile( + node, + context, +@@ -154,6 +173,64 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { + wwlog.ErrorExc(err, "") + return + } ++ } else if rinfo.stage == "efiboot" { ++ wwlog.Debug("requested method: %s", req.Method) ++ containerName := node.ContainerName.Get() ++ switch rinfo.efifile { ++ case "shim.efi": ++ stage_file = container.ShimFind(containerName) ++ if stage_file == "" { ++ wwlog.ErrorExc(fmt.Errorf("could't find shim.efi"), containerName) ++ w.WriteHeader(http.StatusNotFound) ++ return ++ } ++ case "grub.efi", "grub-tpm.efi", "grubx64.efi", "grubia32.efi", "grubaa64.efi", "grubarm.efi": ++ stage_file = container.GrubFind(containerName) ++ if stage_file == "" { ++ wwlog.ErrorExc(fmt.Errorf("could't find grub.efi"), containerName) ++ w.WriteHeader(http.StatusNotFound) ++ return ++ } ++ case "grub.cfg": ++ stage_file = path.Join(conf.Paths.Sysconfdir, "warewulf/grub/grub.cfg.ww") ++ tmpl_data = templateVars{ ++ Id: node.Id.Get(), ++ Cluster: node.ClusterName.Get(), ++ Fqdn: node.Id.Get(), ++ Ipaddr: conf.Ipaddr, ++ Port: strconv.Itoa(conf.Warewulf.Port), ++ Hostname: node.Id.Get(), ++ Hwaddr: rinfo.hwaddr, ++ ContainerName: node.ContainerName.Get(), ++ KernelArgs: node.Kernel.Args.Get(), ++ KernelOverride: node.Kernel.Override.Get()} ++ if stage_file == "" { ++ wwlog.ErrorExc(fmt.Errorf("could't find grub.cfg template"), containerName) ++ w.WriteHeader(http.StatusNotFound) ++ return ++ } ++ default: ++ wwlog.ErrorExc(fmt.Errorf("could't find efiboot file: %s", rinfo.efifile), "") ++ } ++ } else if rinfo.stage == "shim" { ++ if node.ContainerName.Defined() { ++ stage_file = container.ShimFind(node.ContainerName.Get()) ++ ++ if stage_file == "" { ++ wwlog.Error("No kernel found for container %s", node.ContainerName.Get()) ++ } ++ } else { ++ wwlog.Warn("No container set for this %s", node.Id.Get()) ++ } ++ } else if rinfo.stage == "grub" { ++ if node.ContainerName.Defined() { ++ stage_file = container.GrubFind(node.ContainerName.Get()) ++ if stage_file == "" { ++ wwlog.Error("No grub found for container %s", node.ContainerName.Get()) ++ } ++ } else { ++ wwlog.Warn("No conainer set for node %s", node.Id.Get()) ++ } + } + + wwlog.Serv("stage_file '%s'", stage_file) +@@ -187,11 +264,12 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { + + w.Header().Set("Content-Type", "text") + w.Header().Set("Content-Length", strconv.Itoa(buf.Len())) ++ reader := bytes.NewReader(buf.Bytes()) ++ DBAddImage(node.Id.Get(), stage_file, reader) + _, err = buf.WriteTo(w) + if err != nil { + wwlog.ErrorExc(err, "") + } +- + wwlog.Send("%15s: %s", node.Id.Get(), stage_file) + + } else { +@@ -210,7 +288,7 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(http.StatusNotFound) + } + +- err = sendFile(w, req, stage_file, node.Id.Get()) ++ err = sendFile(w, req, stage_file, node.Id.Get(), updateSentDB) + if err != nil { + wwlog.ErrorExc(err, "") + return +diff --git a/internal/pkg/warewulfd/provision_test.go b/internal/pkg/warewulfd/provision_test.go +index 870e1de0..5ec8d72e 100644 +--- a/internal/pkg/warewulfd/provision_test.go ++++ b/internal/pkg/warewulfd/provision_test.go +@@ -1,16 +1,18 @@ + package warewulfd + + import ( +- "github.com/stretchr/testify/assert" +- "io/ioutil" ++ "io" + "net/http" + "net/http/httptest" + "os" + "path" + "testing" + ++ "github.com/stretchr/testify/assert" ++ + warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" + "github.com/hpcng/warewulf/internal/pkg/node" ++ "github.com/hpcng/warewulf/internal/pkg/wwlog" + ) + + var provisionSendTests = []struct { +@@ -18,37 +20,80 @@ var provisionSendTests = []struct { + url string + body string + status int ++ ip string + }{ +- {"system overlay", "/overlay-system/00:00:00:ff:ff:ff", "system overlay", 200}, +- {"runtime overlay", "/overlay-runtime/00:00:00:ff:ff:ff", "runtime overlay", 200}, +- {"fake overlay", "/overlay-system/00:00:00:ff:ff:ff?overlay=fake", "", 404}, +- {"specific overlay", "/overlay-system/00:00:00:ff:ff:ff?overlay=o1", "specific overlay", 200}, ++ {"system overlay", "/overlay-system/00:00:00:ff:ff:ff", "system overlay", 200, "10.10.10.10:9873"}, ++ {"runtime overlay", "/overlay-runtime/00:00:00:ff:ff:ff", "runtime overlay", 200, "10.10.10.10:9873"}, ++ {"fake overlay", "/overlay-system/00:00:00:ff:ff:ff?overlay=fake", "", 404, "10.10.10.10:9873:9873"}, ++ {"specific overlay", "/overlay-system/00:00:00:ff:ff:ff?overlay=o1", "specific overlay", 200, "10.10.10.10:9873"}, ++ {"find shim", "/efiboot/shim.efi", "", 200, "10.10.10.10:9873"}, ++ {"find shim", "/efiboot/shim.efi", "", 404, "10.10.10.11:9873"}, ++ {"find grub", "/efiboot/grub.efi", "", 200, "10.10.10.10:9873"}, ++ {"find grub", "/efiboot/grub.efi", "", 404, "10.10.10.11:9873"}, + } + + func Test_ProvisionSend(t *testing.T) { +- file, err := os.CreateTemp(os.TempDir(), "ww-test-nodes.conf-*") ++ conf_file, err := os.CreateTemp(os.TempDir(), "ww-test-nodes.conf-*") + assert.NoError(t, err) +- defer file.Close() ++ defer conf_file.Close() + { +- _, err := file.WriteString(`WW_INTERNAL: 43 ++ _, err := conf_file.WriteString(`WW_INTERNAL: 43 ++nodeprofiles: ++ default: ++ container name: suse + nodes: + n1: + network devices: + default: +- hwaddr: 00:00:00:ff:ff:ff`) ++ hwaddr: 00:00:00:ff:ff:ff ++ n2: ++ network devices: ++ default: ++ hwaddr: 00:00:00:00:ff:ff ++ container name: none`) ++ assert.NoError(t, err) ++ } ++ assert.NoError(t, conf_file.Sync()) ++ node.ConfigFile = conf_file.Name() ++ ++ // create a arp file as for grub we look up the ip address through the arp cache ++ arp_file, err := os.CreateTemp(os.TempDir(), "ww-arp") ++ assert.NoError(t, err) ++ defer arp_file.Close() ++ { ++ _, err := arp_file.WriteString(`IP address HW type Flags HW address Mask Device ++10.10.10.10 0x1 0x2 00:00:00:ff:ff:ff * dummy ++10.10.10.11 0x1 0x2 00:00:00:00:ff:ff * dummy`) + assert.NoError(t, err) + } +- assert.NoError(t, file.Sync()) +- node.ConfigFile = file.Name() ++ assert.NoError(t, arp_file.Sync()) ++ SetArpFile(arp_file.Name()) ++ ++ conf := warewulfconf.Get() ++ containerDir, imageDirErr := os.MkdirTemp(os.TempDir(), "ww-test-container-*") ++ assert.NoError(t, imageDirErr) ++ defer os.RemoveAll(containerDir) ++ conf.Paths.WWChrootdir = containerDir ++ assert.NoError(t, os.MkdirAll(path.Join(containerDir, "suse/rootfs/usr/lib64/efi"), 0700)) ++ { ++ _, err := os.Create(path.Join(containerDir, "suse/rootfs/usr/lib64/efi", "shim.efi")) ++ assert.NoError(t, err) ++ } ++ assert.NoError(t, os.MkdirAll(path.Join(containerDir, "suse/rootfs/usr/share/efi/x86_64/"), 0700)) ++ { ++ _, err := os.Create(path.Join(containerDir, "suse/rootfs/usr/share/efi/x86_64/", "grub.efi")) ++ assert.NoError(t, err) ++ } ++ + dbErr := LoadNodeDB() + assert.NoError(t, dbErr) + + provisionDir, provisionDirErr := os.MkdirTemp(os.TempDir(), "ww-test-provision-*") + assert.NoError(t, provisionDirErr) + defer os.RemoveAll(provisionDir) +- conf := warewulfconf.Get() + conf.Paths.WWProvisiondir = provisionDir + conf.Warewulf.Secure = false ++ wwlog.SetLogLevel(wwlog.DEBUG) + assert.NoError(t, os.MkdirAll(path.Join(provisionDir, "overlays", "n1"), 0700)) + assert.NoError(t, os.WriteFile(path.Join(provisionDir, "overlays", "n1", "__SYSTEM__.img"), []byte("system overlay"), 0600)) + assert.NoError(t, os.WriteFile(path.Join(provisionDir, "overlays", "n1", "__RUNTIME__.img"), []byte("runtime overlay"), 0600)) +@@ -57,12 +102,13 @@ nodes: + for _, tt := range provisionSendTests { + t.Run(tt.description, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, tt.url, nil) ++ req.RemoteAddr = tt.ip + w := httptest.NewRecorder() + ProvisionSend(w, req) + res := w.Result() + defer res.Body.Close() + +- data, readErr := ioutil.ReadAll(res.Body) ++ data, readErr := io.ReadAll(res.Body) + assert.NoError(t, readErr) + assert.Equal(t, tt.body, string(data)) + assert.Equal(t, tt.status, res.StatusCode) +diff --git a/internal/pkg/warewulfd/sentDB.go b/internal/pkg/warewulfd/sentDB.go +new file mode 100644 +index 00000000..ee64e4c5 +--- /dev/null ++++ b/internal/pkg/warewulfd/sentDB.go +@@ -0,0 +1,144 @@ ++package warewulfd ++ ++import ( ++ "crypto/sha256" ++ "fmt" ++ "io" ++ "path" ++ "sync" ++ ++ "github.com/hpcng/warewulf/internal/pkg/wwlog" ++) ++ ++/* ++store the sent files name and its checksum ++*/ ++type SentFiles struct { ++ Files []File `json:"files:"` ++ sha256sum [32]byte ++ Sha256hex string `json:"sha256"` ++ wwinit bool ++} ++ ++type File struct { ++ FileName string `json:"file name"` ++ sha256sum [32]byte ++ Sha256hex string `json:"sha256"` ++} ++ ++/* ++Database for the checksum of sent files, this ++values can be used for measured boot in combination with ++TPM devicces ++*/ ++var sentDB map[string]*SentFiles ++ ++// mutex for locking the map ++var mu sync.Mutex ++ ++func init() { ++ sentDB = map[string]*SentFiles{} ++} ++ ++/* ++Adds the image with the name to the database ++*/ ++func DBAddImage(node string, fileName string, content io.ReadSeeker) { ++ wwlog.Debug("adding file %s for node %s to sentDB", node, fileName) ++ hasher := sha256.New() ++ sent := File{ ++ FileName: path.Base(fileName), ++ } ++ if _, err := io.Copy(hasher, content); err != nil { ++ wwlog.SecWarn("couldn't create hash of %s for %s", fileName, node) ++ return ++ } ++ copy(sent.sha256sum[:], hasher.Sum(nil)) ++ sent.Sha256hex = fmt.Sprintf("%x", sent.sha256sum) ++ mu.Lock() ++ if _, ok := sentDB[node]; !ok { ++ sentDB[node] = new(SentFiles) ++ } ++ sentDB[node].Files = append(sentDB[node].Files, sent) ++ for i := 0; i < sha256.Size; i++ { ++ sentDB[node].sha256sum[i] = 1 ++ } ++ for _, sntFile := range sentDB[node].Files { ++ sentDB[node].sha256sum = sha256.Sum256(append(sentDB[node].sha256sum[:], sntFile.sha256sum[:]...)) ++ } ++ sentDB[node].Sha256hex = fmt.Sprintf("%x", sentDB[node].sha256sum) ++ mu.Unlock() ++ hasher.Reset() ++} ++ ++/* ++Get the final sum of all the hashed files ++*/ ++func DBGetSum(node string) (ret [sha256.Size]byte) { ++ mu.Lock() ++ defer mu.Unlock() ++ if sentNode, ok := sentDB[node]; ok { ++ ret = sentNode.sha256sum ++ return ++ } ++ ret = [sha256.Size]byte{0} ++ return ++} ++ ++/* ++Reset the database for a single node ++*/ ++func DBReset(node string) { ++ mu.Lock() ++ if _, ok := sentDB[node]; !ok { ++ sentDB[node] = new(SentFiles) ++ } ++ sentDB[node] = new(SentFiles) ++ mu.Unlock() ++} ++ ++/* ++Reset the database ++*/ ++func DBResetAll() { ++ sentDB = make(map[string]*SentFiles) ++} ++ ++/* ++Get the size of the DB ++*/ ++func DBSize(node string) int { ++ mu.Lock() ++ if _, ok := sentDB[node]; !ok { ++ sentDB[node] = new(SentFiles) ++ } ++ size := len(sentDB[node].Files) ++ mu.Unlock() ++ return size ++} ++ ++/* ++Check if wwinit was sent ++*/ ++func DBGetWWinit(node string) bool { ++ mu.Lock() ++ if _, ok := sentDB[node]; !ok { ++ sentDB[node] = new(SentFiles) ++ } ++ ret := sentDB[node].wwinit ++ mu.Unlock() ++ return ret ++} ++ ++/* ++Mark that wwinit was sent ++*/ ++ ++func DBWWinitSent(node string) { ++ mu.Lock() ++ if _, ok := sentDB[node]; !ok { ++ sentDB[node] = new(SentFiles) ++ } ++ sentDB[node].wwinit = true ++ mu.Unlock() ++} +diff --git a/internal/pkg/warewulfd/sentstatus.go b/internal/pkg/warewulfd/sentstatus.go +new file mode 100644 +index 00000000..36350367 +--- /dev/null ++++ b/internal/pkg/warewulfd/sentstatus.go +@@ -0,0 +1,34 @@ ++package warewulfd ++ ++import ( ++ "encoding/json" ++ "net/http" ++ ++ "github.com/hpcng/warewulf/internal/pkg/wwlog" ++ "github.com/pkg/errors" ++) ++ ++func sentStatusJSON() ([]byte, error) { ++ wwlog.Debug("Request for node sent status data...") ++ ++ ret, err := json.MarshalIndent(sentDB, "", " ") ++ if err != nil { ++ return ret, errors.Wrap(err, "could not marshal JSON data from status structure") ++ } ++ ++ return ret, nil ++ ++} ++ ++func SentStatus(w http.ResponseWriter, req *http.Request) { ++ status, err := sentStatusJSON() ++ if err != nil { ++ w.WriteHeader(http.StatusInternalServerError) ++ return ++ } ++ ++ _, err = w.Write(status) ++ if err != nil { ++ wwlog.Warn("Could not send sent status JSON: %s", err) ++ } ++} +diff --git a/internal/pkg/warewulfd/sentsums_test.go b/internal/pkg/warewulfd/sentsums_test.go +new file mode 100644 +index 00000000..41fcfe00 +--- /dev/null ++++ b/internal/pkg/warewulfd/sentsums_test.go +@@ -0,0 +1,23 @@ ++package warewulfd ++ ++import ( ++ "bytes" ++ "testing" ++) ++ ++func Test_SumOne(t *testing.T) { ++ firstText := `Scalable. Flexible. Today, Warewulf unites the ecosystem with the ability to provision containers directly to the bare metal hardware at massive scale, simplistically while retaining massive flexibility.` ++ secondText := `Being open source for over two-decades, and pioneering the concept of stateless node management, Warewulf is among the most successful HPC cluster platforms in the industry with support from OpenHPC, contributors around the world, and usage from every industry.` ++ DBAddImage("n01", "firstText", bytes.NewReader([]byte(firstText))) ++ DBAddImage("n01", "secondText", bytes.NewReader([]byte(secondText))) ++ if sum_n02 := DBGetSum("n02"); sum_n02 != [32]byte{} { ++ t.Errorf("Sum of second entry must be zero") ++ } ++ if sum_n01 := DBGetSum("n01"); sum_n01 == [32]byte{} { ++ t.Errorf("Sum of entry must not be zero") ++ } ++ DBResetAll() ++ if sum_n01 := DBGetSum("n01"); sum_n01 != [32]byte{} { ++ t.Errorf("Sum after reset must be zero") ++ } ++} +diff --git a/internal/pkg/warewulfd/util.go b/internal/pkg/warewulfd/util.go +index 3290a678..cefa1cab 100644 +--- a/internal/pkg/warewulfd/util.go ++++ b/internal/pkg/warewulfd/util.go +@@ -1,8 +1,11 @@ + package warewulfd + + import ( ++ "bufio" ++ "io" + "net/http" + "os" ++ "strings" + + "github.com/hpcng/warewulf/internal/pkg/node" + nodepkg "github.com/hpcng/warewulf/internal/pkg/node" +@@ -15,7 +18,7 @@ func sendFile( + w http.ResponseWriter, + req *http.Request, + filename string, +- sendto string) error { ++ sendto string, updateSentDB bool) error { + + fd, err := os.Open(filename) + if err != nil { +@@ -36,7 +39,14 @@ func sendFile( + filename, + stat.ModTime(), + fd) +- ++ // seek back ++ _, err = fd.Seek(0, io.SeekStart) ++ if err != nil { ++ wwlog.Warn("couldn't seek in file: %s", filename) ++ } ++ if updateSentDB { ++ DBAddImage(sendto, filename, fd) ++ } + wwlog.Send("%15s: %s", sendto, filename) + + return nil +@@ -70,3 +80,34 @@ func getOverlayFile( + + return + } ++ ++var arpFile string ++ ++func init() { ++ arpFile = "/proc/net/arp" ++} ++ ++func SetArpFile(newName string) { ++ arpFile = newName ++} ++ ++/* ++returns the mac address if it has an entry in the arp cache ++*/ ++func ArpFind(ip string) (mac string) { ++ arpCache, err := os.Open(arpFile) ++ if err != nil { ++ return ++ } ++ defer arpCache.Close() ++ ++ scanner := bufio.NewScanner(arpCache) ++ scanner.Scan() ++ for scanner.Scan() { ++ fields := strings.Fields(scanner.Text()) ++ if strings.EqualFold(fields[0], ip) { ++ return fields[3] ++ } ++ } ++ return ++} +diff --git a/internal/pkg/warewulfd/warewulfd.go b/internal/pkg/warewulfd/warewulfd.go +index 796054b0..4c480ebe 100644 +--- a/internal/pkg/warewulfd/warewulfd.go ++++ b/internal/pkg/warewulfd/warewulfd.go +@@ -5,6 +5,7 @@ import ( + "os" + "os/signal" + "strconv" ++ "strings" + "syscall" + + warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" +@@ -14,6 +15,23 @@ import ( + + // TODO: https://github.com/danderson/netboot/blob/master/pixiecore/dhcp.go + // TODO: https://github.com/pin/tftp ++/* ++wrapper type for the server mux as shim requests http://efiboot//grub.efi ++which is filtered out by http to `301 Moved Permanently` what ++shim.efi can't handle. So filter out `//` before they hit go/http. ++Makes go/http more to behave like apache ++*/ ++type slashFix struct { ++ mux http.Handler ++} ++ ++/* ++Filter out the '//' ++*/ ++func (h *slashFix) ServeHTTP(w http.ResponseWriter, r *http.Request) { ++ r.URL.Path = strings.Replace(r.URL.Path, "//", "/", -1) ++ h.mux.ServeHTTP(w, r) ++} + + func RunServer() error { + err := DaemonInitLogging() +@@ -49,21 +67,27 @@ func RunServer() error { + wwlog.Error("Could not prepopulate node status DB: %s", err) + } + +- http.HandleFunc("/provision/", ProvisionSend) +- http.HandleFunc("/ipxe/", ProvisionSend) +- http.HandleFunc("/kernel/", ProvisionSend) +- http.HandleFunc("/kmods/", ProvisionSend) +- http.HandleFunc("/container/", ProvisionSend) +- http.HandleFunc("/overlay-system/", ProvisionSend) +- http.HandleFunc("/overlay-runtime/", ProvisionSend) +- http.HandleFunc("/status", StatusSend) ++ if err != nil { ++ wwlog.Warn("couldn't copy default shim: %s", err) ++ } ++ var wwHandler http.ServeMux ++ wwHandler.HandleFunc("/provision/", ProvisionSend) ++ wwHandler.HandleFunc("/ipxe/", ProvisionSend) ++ wwHandler.HandleFunc("/efiboot/", ProvisionSend) ++ wwHandler.HandleFunc("/kernel/", ProvisionSend) ++ wwHandler.HandleFunc("/kmods/", ProvisionSend) ++ wwHandler.HandleFunc("/container/", ProvisionSend) ++ wwHandler.HandleFunc("/overlay-system/", ProvisionSend) ++ wwHandler.HandleFunc("/overlay-runtime/", ProvisionSend) ++ wwHandler.HandleFunc("/status", StatusSend) ++ wwHandler.HandleFunc("/sentstatus", SentStatus) + + conf := warewulfconf.Get() + + daemonPort := conf.Warewulf.Port + wwlog.Serv("Starting HTTPD REST service on port %d", daemonPort) + +- err = http.ListenAndServe(":"+strconv.Itoa(daemonPort), nil) ++ err = http.ListenAndServe(":"+strconv.Itoa(daemonPort), &slashFix{&wwHandler}) + if err != nil { + return errors.Wrap(err, "Could not start listening service") + } +diff --git a/overlays/host/etc/dhcp/dhcpd.conf.ww b/overlays/host/etc/dhcp/dhcpd.conf.ww +index 4a9637f7..03fe54cb 100644 +--- a/overlays/host/etc/dhcp/dhcpd.conf.ww ++++ b/overlays/host/etc/dhcp/dhcpd.conf.ww +@@ -1,4 +1,4 @@ +-{{if .Dhcp.Enabled -}} ++{{if $.Dhcp.Enabled -}} + # This file is autogenerated by warewulf + # Host: {{.BuildHost}} + # Time: {{.BuildTime}} +@@ -15,8 +15,29 @@ option space ipxe; + option ipxe.no-pxedhcp code 176 = unsigned integer 8; + option ipxe.no-pxedhcp 1; + ++option space PXE; ++option PXE.mtftp-ip code 1 = ip-address; ++option PXE.mtftp-cport code 2 = unsigned integer 16; ++option PXE.mtftp-sport code 3 = unsigned integer 16; ++option PXE.mtftp-tmout code 4 = unsigned integer 8; ++option PXE.mtftp-delay code 5 = unsigned integer 8; ++ + option architecture-type code 93 = unsigned integer 16; + ++{{- if $.Warewulf.GrubBoot }} ++if substring (option vendor-class-identifier, 0, 9) = "PXEClient" { ++ next-server {{ $.Ipaddr }}; ++ if substring (option vendor-class-identifier, 15, 5) = "00000" { ++ # pure BIOS clients will get iPXE configuration ++ filename "http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/ipxe/${mac:hexhyp}"; ++ } else { ++ # EFI clients will get shim and grub instead ++ filename "warewulf/shim.efi"; ++ } ++} elsif substring (option vendor-class-identifier, 0, 10) = "HTTPClient" { ++ filename "http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/efiboot/shim.efi"; ++} ++{{- else }} + if exists user-class and option user-class = "iPXE" { + filename "http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/ipxe/${mac:hexhyp}"; + } else { +@@ -24,39 +45,32 @@ if exists user-class and option user-class = "iPXE" { + if option architecture-type = {{ $type }} { + filename "/warewulf/{{ basename $name }}"; + } +-{{ end }} ++{{- end }}{{/* range IpxeBinaries */}} + } ++{{- end }}{{/* BootMethod */}} + +-{{if eq .Dhcp.Template "static" -}} + subnet {{$.Network}} netmask {{$.Netmask}} { +- next-server {{$.Ipaddr}}; ++ max-lease-time 120; ++ range {{$.Dhcp.RangeStart}} {{$.Dhcp.RangeEnd}}; ++ next-server {{.Ipaddr}}; + } +-{{range $nodes := .AllNodes}} ++{{- range $nodes := $.AllNodes}} + {{- range $netname, $netdevs := $nodes.NetDevs}} +-host {{$nodes.Id.Get}}-{{if $netdevs.Device.Defined}}{{$netdevs.Device.Get}}{{else}}{{$netname}}{{end}} { ++host {{$nodes.Id.Get}}-{{$netdevs.Device.Get}} ++{ ++ {{- if $netdevs.Primary.GetB}} + {{- if $netdevs.Hwaddr.Defined}} + hardware ethernet {{$netdevs.Hwaddr.Get}}; +- {{- end}} ++ {{- end }} + {{- if $netdevs.Ipaddr.Defined}} + fixed-address {{$netdevs.Ipaddr.Get}}; +- {{- end}} +- {{- if $netdevs.Primary.GetB}} ++ {{- end }} + option host-name "{{$nodes.Id.Get}}"; +- {{else}} +- option host-name "{{$nodes.Id.Get}}-{{if $netdevs.Device.Defined}}{{$netdevs.Device.Get}}{{else}}{{$netname}}{{end}}"; +- {{- end}} +-} +-{{end -}} +-{{end -}} +- +-{{else -}} +-subnet {{$.Network}} netmask {{$.Netmask}} { +- max-lease-time 120; +- range {{$.Dhcp.RangeStart}} {{$.Dhcp.RangeEnd}}; +- next-server {{$.Ipaddr}}; ++ {{- end }} + } +-{{end}} ++{{end -}}{{/* range NetDevs */}} ++{{end -}}{{/* range AllNodes */}} + + {{- else}} + {{abort}} +-{{- end}} ++{{- end}}{{/* dhcp enabled */}} +diff --git a/overlays/host/etc/dhcpd.conf.ww b/overlays/host/etc/dhcpd.conf.ww +new file mode 120000 +index 00000000..e5b73f7b +--- /dev/null ++++ b/overlays/host/etc/dhcpd.conf.ww +@@ -0,0 +1 @@ ++dhcp/dhcpd.conf.ww +\ No newline at end of file +diff --git a/overlays/wwinit/warewulf/init.d/80-wwclient b/overlays/wwinit/warewulf/init.d/80-wwclient +new file mode 100644 +index 00000000..38347110 +--- /dev/null ++++ b/overlays/wwinit/warewulf/init.d/80-wwclient +@@ -0,0 +1,7 @@ ++#!/bin/sh ++. /warewulf/config ++# Only start if the systemd is no available ++test -d /run/systemd/system && exit 0 ++echo "Starting wwclient" ++nohup /warewulf/wwclient >/var/log/wwclient.log 2>&1 `_ ++can be used with the advantage that secure boot can be used. That means ++that only the signed kernel of a distribution can be booted. This can ++be a huge security benefit for some scenarios. ++ ++In order to enable the grub boot method it has to be enabled in `warewulf.conf`. ++Nodes which are not known to warewulf will then booted with the shim/grub from ++the host on which warewulf is installed. ++ ++ ++Boot process ++============ ++ ++The boot process can be summarized with following diagram ++ ++.. graphviz:: ++ ++ digraph foo { ++ node [shape=box]; ++ subgraph boot { ++ "EFI" [label="EFI",row=boot]; ++ "Shim" [label="Shim",row=boot]; ++ "Grub" [label="Grub",row=boot]; ++ "Kernel" [label="kernel",row=boot]; ++ EFI -> Shim[label="Check for Microsoft signature"]; ++ Shim -> Grub[label="Check for Distribution signature"]; ++ Grub->Kernel[label="Check for Distribution or MOK signature"]; ++ } ++ } ++ ++If secure boot is enabled at every step a signature is checked and the boot process ++will fail if this check fails. Also at moment a Shim only includes the key ++of one Distribution, which means that every Distribution needs a separate ++`shim` and `grub` executable and warewulf extracts these binaries from ++the containers. ++ ++For the case when the node is unknown to warewulf or ++can't be identified during the `tFTP`` boot phase, the shim/grub binaries of ++the host in which warewulf is running will be used. ++ ++PXE/tFTP boot ++------------- ++ ++The standard network boot process with `grub` and `iPXE` has following steps ++ ++.. graphviz:: ++ ++ digraph G{ ++ node [shape=box]; ++ compound=true; ++ edge [label2node=true] ++ bios [shape=record label="{Bios | boots filename from nextboot per tFTP}"] ++ subgraph cluster0 { ++ label="iPXE boot" ++ iPXE; ++ ipxe_cfg [shape=record label="{ipxe.cfg|generated for indivdual node}"]; ++ iPXE -> ipxe_cfg [label="http"]; ++ } ++ bios->iPXE [lhead=cluster0,label="filename=iPXE.efi"]; ++ bios->shim [lhead=cluster1,label="filename=shim.efi"]; ++ subgraph cluster1{ ++ label="Grub boot" ++ shim[shape=record label="{shim.efi|from ww4 host}"]; ++ grub[shape=record label="{grubx64.efi | name hardcoded in shim.efi|from ww4 host}"] ++ shim->grub[label="tFTP"]; ++ grubcfg[shape=record label="{grub.cfg|static under tFTP root}"]; ++ grub->grubcfg[label="tFTP"]; ++ } ++ kernel [shape=record label="{kernel|ramdisk (root fs)|wwinit overlay}|extracted from node container"]; ++ grubcfg->kernel[ltail=cluster1,label="http"]; ++ ipxe_cfg->kernel[ltail=cluster0,label="http"]; ++ } ++ ++As the tFTP server is independent of warewulf, the `shim` and `grub` EFI binaries ++for the tFTP server are copied from the host on which warewulf is running. ++This means that for secure boot the distributor e.g. SUSE of the container in ++the `default` profile must match the distributor of the container which then ++also must be signed by the SUSE key. ++ ++http boot ++--------- ++ ++Modern EFI systems have the possibility to directly boot per http. The flow diagram ++is the following: ++ ++.. graphviz:: ++ ++ digraph G{ ++ node [shape=box]; ++ efi [shape=record label="{EFI|boots from URI defined in filename}"]; ++ shim [shape=record label="{shim.efi|replaces shim.efi with grubx64.efi in URI|extracted from node container}"]; ++ grub [shape=record label="{grub.efi|checks for grub.cfg|extracted from node container}"] ++ kernel [shape=record label="{kernel|ramdisk (root fs)|wwinit overlay}|extracted from node container"]; ++ efi->shim [label="http"]; ++ shim->grub [label="http"]; ++ grub->kernel [label="http"]; ++ } ++ ++The main difference is that the initial `shim.efi` and `grub.efi` are delivered by http with warewulf ++and are taken directly from the container assigned to the node. This means that secure boot will work ++for containers from different distributors. ++ ++Install shim and efi ++-------------------- ++ ++The `shim.efi` and `grub.efi` must be installed via the package manager directly into the container. ++ ++Install on SUSE systems ++^^^^^^^^^^^^^^^^^^^^^^^ ++ ++.. code-block:: console ++ ++ # wwctl container shell leap15.5 ++ [leap15.5] Warewulf> zypper install grub2 shim ++ ++ ++Install on EL system ++^^^^^^^^^^^^^^^^^^^^ ++ ++.. code-block:: console ++ ++ # wwctl container shell rocky9 ++ [rocky9] Warewulf> dnf install shim-x64.x86_64 grub2-pc.x86_64 +\ No newline at end of file +diff --git a/userdocs/contents/security.rst b/userdocs/contents/security.rst +index 1f585de1..b0bb42a8 100644 +--- a/userdocs/contents/security.rst ++++ b/userdocs/contents/security.rst +@@ -55,7 +55,7 @@ when a user lands on a compute node, there is generally nothing + stopping them from spoofing a provision request and downloading the + provisioned raw materials for inspection. + +-In Warewulf there are two ways to secure the provisioning process: ++In Warewulf there are ways multiple to secure the provisioning process: + + #. The provisioning connections and transfers are not secure due to + not being able to manage a secure root of trust through a PXE +@@ -77,6 +77,11 @@ In Warewulf there are two ways to secure the provisioning process: + provision and communicate with requests from that system matching + that asset tag. + ++#. When the nodes are booted via `shim` and `grub` Secure Boot can be ++ enabled. This means that the nodes only boot the kernel which is ++ provided by the distributor and also custom complied modules can't ++ be loaded. ++ + Summary + ======= + +diff --git a/userdocs/index.rst b/userdocs/index.rst +index 71fb9da5..9d7a4b8c 100644 +--- a/userdocs/index.rst ++++ b/userdocs/index.rst +@@ -18,6 +18,7 @@ Welcome to the Warewulf User Guide! + Warewulf Initialization + Container Management + Kernel Management ++ Boot Management + Node Configuration + Node Profiles + Warewulf Overlays +diff --git a/warewulf.spec.in b/warewulf.spec.in +index 318703fb..e71a71a4 100644 +--- a/warewulf.spec.in ++++ b/warewulf.spec.in +@@ -139,6 +139,7 @@ yq e ' + %config(noreplace) %{_sysconfdir}/warewulf/wwapi*.conf + %config(noreplace) %{_sysconfdir}/warewulf/examples + %config(noreplace) %{_sysconfdir}/warewulf/ipxe ++%config(noreplace) %{_sysconfdir}/warewulf/grub + %config(noreplace) %attr(0640,-,-) %{_sysconfdir}/warewulf/nodes.conf + %{_sysconfdir}/bash_completion.d/wwctl + diff --git a/vendor.tar.gz b/vendor.tar.gz index 0d717d2..a2720b3 100644 --- a/vendor.tar.gz +++ b/vendor.tar.gz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fce8aa6a5c92d1ca8f334a35686e206a6567c1022dc037666366e273fb57dd5c -size 5488991 +oid sha256:ade18e7f3ca08bf1fee19b7c86350e80f463c6884dc6790d6d568e15fb3c56ae +size 5811432 diff --git a/warewulf4-v4.5.0rc0.tar.gz b/warewulf4-v4.5.0rc0.tar.gz new file mode 100644 index 0000000..a4c0e22 --- /dev/null +++ b/warewulf4-v4.5.0rc0.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0bb66935b1e4b11e542ef521284389a6e492fc775adfe8a9384fcc8efff76f2 +size 16927353 diff --git a/warewulf4.changes b/warewulf4.changes index fa21c8b..ad3d57b 100644 --- a/warewulf4.changes +++ b/warewulf4.changes @@ -1,3 +1,23 @@ +------------------------------------------------------------------- +Thu Dec 21 14:48:08 UTC 2023 - Christian Goll + +- updated to v4.5.0rc0 pre release with following new features: + * disks, partitions and file systems can set in the configration + and if ignition is present in the container, changes to the disks will be + made + * grub can be used as alternative boot method instead of iPXE. The + grub binairy is extracted from the container and shim is executed before + the grub. This enables secure boot + * wwctl has now the genconfig subcommand which will print/create + valid configurations + * all paths e.g the overlay dir, can now be configured in warewulf.conf +- notable bug fixes are: + * Fixed a bug where profile tags were erroneously overridden by empty node + values. + * Fixed bug where tags from profiles weren't rendered +- added grub-boot.patch which includes the not merged grub support + + ------------------------------------------------------------------- Thu Oct 12 07:43:32 UTC 2023 - Christian Goll diff --git a/warewulf4.spec b/warewulf4.spec index 04ce03c..4287c42 100644 --- a/warewulf4.spec +++ b/warewulf4.spec @@ -16,24 +16,23 @@ # -#%%define rls_cndt rc3 +%define rls_cndt rc0 +%global tftpdir /srv/tftpboot +%global srvdir %{_sharedstatedir} ExclusiveArch: x86_64 aarch64 Name: warewulf4 -Version: 4.4.0 +Version: 4.5.0%{?rls_cndt} Release: 0 Summary: A suite of tools for clustering License: BSD-3-Clause Group: Productivity/Clustering/Computing URL: https://warewulf.org -Source0: https://github.com/hpcng/warewulf/archive/v%{version}%{?rls_cndt}.tar.gz#/warewulf4-v%{version}.tar.gz -Source1: vendor.tar.gz -Source3: warewulf4-rpmlintrc +#Source0: https://github.com/hpcng/warewulf/archive/v%{version}%{?rls_cndt}.tar.gz#/warewulf4-v%{version}.tar.gz +Source0: https://github.com/hpcng/warewulf/releases/download/nightly/warewulf-4.5.x.tar.gz#/warewulf4-v%{version}.tar.gz Source10: config-ww4.sh -Patch1: make-ipxe-binary-source-configureable.patch -Patch2: CreateMt-Targets.patch -Patch10: Fix-for-CVE-2022-41723.patch +Patch10: grub-boot.patch # no firewalld in sle12 %if 0%{?sle_version} >= 150000 || 0%{?suse_version} > 1500 @@ -41,14 +40,15 @@ BuildRequires: firewalld %endif BuildRequires: distribution-release BuildRequires: dracut -BuildRequires: go >= 1.15 +BuildRequires: go >= 1.20 BuildRequires: golang-packaging -BuildRequires: libgpgme-devel +BuildRequires: libgpg-error-devel BuildRequires: make BuildRequires: munge BuildRequires: sysuser-tools BuildRequires: tftp BuildRequires: yq +BuildRequires: pkgconfig(gpgme) BuildRoot: %{_tmppath}/%{name}-%{version}-build %sysusers_requires Requires: %{name}-overlay = %{version} @@ -56,6 +56,7 @@ Requires: dhcp-server Requires: ipxe-bootimgs Requires: pigz Requires: tftp +Recommends: bash-completion Recommends: ipmitool Recommends: nfs-kernel-server @@ -79,10 +80,18 @@ Includes the default overlays so that they can be updated seprately. %package api Requires: %{name} -Summary: Contains the service for the warewulf rest API +Summary: Contains the services for the warewulf rest API %description api -Containts the binaries for the access of warewulf through a rest API and from the commandline from an external host. +Contains the binaries for the access of warewulf through a rest API and from +the commandline from an external host. + +%package doc +Requires: %{name} +Summary: Contains the documentation for warewulf + +%description doc +Documention and man pages for warewulf. %package slurm Summary: Configuration template for slurm @@ -94,51 +103,68 @@ This package install the necessary configuration files in order to run a slurm cluster on the configured warewulf nodes. %prep -%setup -q -n warewulf-%{version}%{?rls_cndt} -# extract the vendor stuff -tar xzf %{S:1} +%setup -q -n warewulf-4.5.x %autopatch -p1 %build -%global tftpdir /srv/tftpboot -make %{?_smp_mflags} genconfig \ +export OFFLINE_BUILD=1 +export IPXESOURCE=%{_datadir}/ipxe +export GOFLAGS="-buildmode=pie" +make defaults \ PREFIX=%{_prefix} \ BINDIR=%{_bindir} \ SYSCONFDIR=%{_sysconfdir} \ - DATADIR=%{_datadir}/ipxe \ - LOCALSTATEDIR=%{_datadir} \ + DATADIR=%{_datadir} \ + LOCALSTATEDIR=%{_sharedstatedir} \ SHAREDSTATEDIR=%{_sharedstatedir} \ MANDIR=%{_mandir} \ INFODIR=%{_infodir} \ DOCDIR=%{_docdir} \ - SRVDIR=%{_sharedstatedir} \ + SRVDIR=%{srvdir} \ TFTPDIR=%{tftpdir} \ SYSTEMDDIR=%{_unitdir} \ - BASHCOMPDIR=%{_datadir}/bash-completion/completions/ \ + BASHCOMPDIR=/etc/bash_completion.d/ \ + FIREWALLDDIR=/usr/lib/firewalld/services \ + WWCLIENTDIR=/warewulf +make %{?_smp_mflags} build \ + PREFIX=%{_prefix} \ + BINDIR=%{_bindir} \ + SYSCONFDIR=%{_sysconfdir} \ + DATADIR=%{_datadir} \ + LOCALSTATEDIR=%{_sharedstatedir} \ + SHAREDSTATEDIR=%{_sharedstatedir} \ + MANDIR=%{_mandir} \ + INFODIR=%{_infodir} \ + DOCDIR=%{_docdir} \ + SRVDIR=%{srvdir} \ + TFTPDIR=%{tftpdir} \ + SYSTEMDDIR=%{_unitdir} \ + BASHCOMPDIR=/etc/bash_completion.d/ \ FIREWALLDDIR=/usr/lib/firewalld/services \ WWCLIENTDIR=/warewulf %install # we have a broken symlink for wwclient export NO_BRP_STALE_LINK_ERROR=yes +export IPXESOURCE=%{_datadir}/ipxe +# overlays will end up here +export OFFLINE_BUILD=1 +export LOCALSTATEDIR=%{_localstatedir}/lib %{makeinstall} # cleanup -mv %{buildroot}%{_datadir}/warewulf/overlays/host/etc/dhcp/dhcpd.conf.ww \ - %{buildroot}%{_datadir}//warewulf/overlays/host/etc/dhcpd.conf.ww -rmdir %{buildroot}%{_datadir}/warewulf/overlays/host/etc/dhcp -rm %{buildroot}%{_datadir}/warewulf/overlays/host/etc/dhcpd.conf mkdir -p %{buildroot}%{_sbindir}/ ln -s %{_sbindir}/service %{buildroot}%{_sbindir}/rcwarewulfd + mkdir -p %{buildroot}%{_datadir}/bash-completion/completions -mv -v %{buildroot}%{_sysconfdir}/bash_completion.d/warewulf %{buildroot}%{_datadir}/bash-completion/completions/wwctl -#cp %{S:2} %{buildroot}%{_sysconfdir}/warewulf/warewulf.conf -#rm -rf %{buildroot}%{_datadir}/warewulf/ipxe +mv -v %{buildroot}%{_sysconfdir}/bash_completion.d/wwctl \ + %{buildroot}%{_datadir}/bash-completion/completions/wwctl +#rm -r %{buildroot}%{_datadir}/doc/warewulf +# copy the LICESNSE.md via %%doc rm -f %{buildroot}/usr/share/doc/packages/warewulf/LICENSE.md -rm -rf %{buildroot}%{_localstatedir}/lib/warewulf -# use ipxe-bootimgs images + +# use ipxe-bootimgs images from distribution yq e ' - .dhcpd.template = "static" | .tftp.ipxe."00:00" = "undionly.kpxe" | .tftp.ipxe."00:07" = "ipxe-x86_64.efi" | .tftp.ipxe."00:09" = "ipxe-x86_64.efi" | @@ -147,6 +173,9 @@ yq e ' .["container mounts"] += {"source": "/etc/zypp/credentials.d/SCCcredentials", "dest": "/etc/zypp/credentials.d/SCCcredentials", "readonly": true}' \ -i %{buildroot}%{_sysconfdir}/warewulf/warewulf.conf sed -i 's@\(^\s*\)\(.*:.*\):@\1"\2":@' %{buildroot}%{_sysconfdir}/warewulf/warewulf.conf +# fix dhcp for SUSE +mv %{buildroot}%{_localstatedir}/lib/warewulf/overlays/host/etc/dhcp/dhcpd.conf.ww %{buildroot}%{_localstatedir}/lib/warewulf/overlays/host/etc/dhcpd.conf.ww +rmdir %{buildroot}%{_localstatedir}/lib/warewulf/overlays/host/etc/dhcp # create systemuser echo "u warewulf -" > system-user-%{name}.conf @@ -155,20 +184,18 @@ echo "g warewulf -" >> system-user-%{name}.conf install -D -m 644 system-user-%{name}.conf %{buildroot}%{_sysusersdir}/system-user-%{name}.conf install -D -m 755 %{S:10} %{buildroot}%{_datadir}/warewulf/scripts/config-warewulf.sh -# get the slurm package readay -mkdir -p %{buildroot}%{_datadir}/warewulf/overlays/host/etc/slurm -mv %{buildroot}%{_sysconfdir}/warewulf/examples/slurm.conf.ww %{buildroot}%{_datadir}/warewulf/overlays/host/etc/slurm -mkdir -p %{buildroot}%{_datadir}/warewulf/overlays/generic/etc/munge -cat > %{buildroot}%{_datadir}/warewulf/overlays/generic/etc/munge/munge.key.ww < %{buildroot}%{_localstatedir}/lib/warewulf/overlays/generic/etc/munge/munge.key.ww < %{buildroot}%{_datadir}/warewulf/overlays/generic/etc/slurm/slurm.conf.ww < %{buildroot}%{_localstatedir}/lib/warewulf/overlays/generic/etc/slurm/slurm.conf.ww <