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