diff --git a/CreateMt-Targets.patch b/CreateMt-Targets.patch new file mode 100644 index 0000000..10ac360 --- /dev/null +++ b/CreateMt-Targets.patch @@ -0,0 +1,1004 @@ +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/config-ww4.sh b/config-ww4.sh new file mode 100644 index 0000000..eda006b --- /dev/null +++ b/config-ww4.sh @@ -0,0 +1,71 @@ +#!/bin/sh +# Configure warewulf with the primary network of the host +WW4CONF=/etc/warewulf/warewulf.conf + +# Get the mask from prefix +cdr2mask() +{ + # Number of args to shift, 255..255, first non-255 byte, zeroes + set -- $(( 5 - ($1 / 8) )) 255 255 255 255 $(( (255 << (8 - ($1 % 8))) & 255 )) 0 0 0 + [ $1 -gt 1 ] && shift $1 || shift + echo ${1-0}.${2-0}.${3-0}.${4-0} +} + +# Get the ip4 address of the netork +network_address() { + declare address prefix_length + IFS=/ read address prefix_length <<< "$1" + + declare -a octets + IFS=. read -a octets <<< "$address" + + declare mask + mask=$( printf "%08x" $(( (1 << 32) - (1 << (32 - prefix_length)) )) ) + + declare -i i + for i in {0..3}; do octets[$i]=$(( octets[i] & 16#${mask:2*i:2} )); done + + echo $( IFS=.; echo "${octets[*]}" ) +} +echo "-- WW4 CONFIGURAION $* --" + +# Make sure that a ip address was defined for out network so that +# we can configure dhcpd correctly +IP4CIDR=`ip addr | awk '/scope global/ {print $2;exit}'` +IP4=${IP4CIDR%/*} +IP4PREFIX=${IP4CIDR#*/} +IP4MASK=$(cdr2mask $IP4PREFIX) +IP4NET=$(network_address "$IP4/$IP4PREFIX") + +if [ "$IP4PREFIX" -gt 25 ] ; then + echo "ERROR: warewulf does at least a /25 network for dynamic addresses" + exit 1 +fi + +DYNSIZE=20 +DYNSTART=${IP4#*.*.*.} +DYNPRE=${IP4%.*} +DYNEND=$(( $DYNSTART + $DYNSIZE )) +if [ $DYNEND -gt 254 ] ; then + DYNEND=$(( $IPNET + 2 + $DYNSIZE )) + DYNSTART=$(( $IPNET + 2 )) +fi +DYNSTART="${DYNPRE}.${DYNSTART}" +DYNEND="${DYNPRE}.${DYNEND}" + + +if [ -e $WW4CONF ] ; then + test -n $IP4 && sed -i 's/^ipaddr:.*/ipaddr: '$IP4'/' $WW4CONF + test -n $IP4MASK && sed -i 's/^netmask:.*/netmask: '$IP4MASK'/' $WW4CONF + test -n $IP4NET && sed -i 's/^network:.*/network: '$IP4NET'/' $WW4CONF + test -n $DYNSTART && sed -i 's/^ range start:.*/ range start: '$DYNSTART'/' $WW4CONF + test -n $DYNEND && sed -i 's/^ range end:.*/ range end: '$DYNEND'/' $WW4CONF + cat << EOF +ipaddr: $IP4 +netmask: $IP4MASK +network: $IP4NET + range start: $DYNSTART + range end: $DYNEND +EOF +fi + diff --git a/warewulf4.changes b/warewulf4.changes index 03249e2..98fcdad 100644 --- a/warewulf4.changes +++ b/warewulf4.changes @@ -1,7 +1,17 @@ +------------------------------------------------------------------- +Tue Feb 7 15:34:13 UTC 2023 - Christian Goll + +- added CreateMt-Targets.patch + which moonts in resolv.conf and files for SCC registration +- added config-ww4.sh which is used for initial configuration for + simple network setups + ------------------------------------------------------------------- Tue Jan 24 11:17:32 UTC 2023 - Christian Goll - added make-ipxe-binary-source-configureable.patch +- use distro ipxe binaries instead of binaries blobs from the + warewulf github repo ------------------------------------------------------------------- Thu Jan 19 11:13:58 UTC 2023 - Christian Goll diff --git a/warewulf4.spec b/warewulf4.spec index c5a966e..26c145c 100644 --- a/warewulf4.spec +++ b/warewulf4.spec @@ -30,7 +30,9 @@ 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 +Source10: config-ww4.sh Patch1: make-ipxe-binary-source-configureable.patch +Patch2: CreateMt-Targets.patch # no firewalld in sle12 %if 0%{?sle_version} >= 150000 || 0%{?suse_version} > 1500 @@ -42,6 +44,7 @@ BuildRequires: golang-packaging BuildRequires: libgpgme-devel BuildRequires: make BuildRequires: munge +BuildRequires: suse-release BuildRequires: sysuser-tools BuildRequires: tftp BuildRequires: yq @@ -51,7 +54,6 @@ Requires: %{name}-overlay = %{version} Recommends: dhcp-server Recommends: ipmitool Recommends: ipxe-bootimgs -Recommends: ipxe-bootimgs Recommends: nfs-kernel-server Recommends: tftp @@ -146,7 +148,9 @@ yq e ' .tftp.ipxe."00:00" = "undionly.kpxe" | .tftp.ipxe."00:07" = "ipxe-x86_64.efi" | .tftp.ipxe."00:09" = "ipxe-x86_64.efi" | - .tftp.ipxe."00:0B" = "snp-arm64.efi"'\ + .tftp.ipxe."00:0B" = "snp-arm64.efi" | + .["container mounts"] += {"source": "/etc/SUSEConnect", "dest": "/etc/SUSEConnect", "readonly": true} | + .["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 @@ -155,6 +159,7 @@ echo "u warewulf -" > system-user-%{name}.conf echo "g warewulf -" >> system-user-%{name}.conf %sysusers_generate_pre system-user-%{name}.conf %{name} 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 @@ -174,6 +179,7 @@ EOF %post %service_add_post warewulfd.service +%{_datadir}/warewulf/scripts/config-warewulf.sh %preun %service_del_preun warewulfd.service @@ -201,6 +207,7 @@ EOF %{_sbindir}/rcwarewulfd %{_unitdir}/warewulfd.service %{_sysusersdir}/system-user-%{name}.conf +%{_datadir}/warewulf/scripts %files overlay # The configuration files in this location are for the compute