Added cow option to bind

The option cow can now be set for files which are then
mounted during exec, but copied into the image and removed
if not modified.

Signed-off-by: Christian Goll <cgoll@suse.com>
This commit is contained in:
Christian Goll
2024-08-20 17:41:29 +02:00
parent 25fbe5880b
commit dc263425e2
8 changed files with 113 additions and 7 deletions

View File

@@ -46,6 +46,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Added option for wwclient port number. #1349 - Added option for wwclient port number. #1349
- Additional helper directions during syncuser conflict. #1359 - Additional helper directions during syncuser conflict. #1359
- Add `:cow` suffix to `wwctl container exec --bind` to temporarily copy files into the node image. #1365
### Changed ### Changed

View File

@@ -92,7 +92,7 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
wwlog.Debug("overlay options: %s", options) wwlog.Debug("overlay options: %s", options)
err = syscall.Mount("overlay", containerPath, "overlay", 0, options) err = syscall.Mount("overlay", containerPath, "overlay", 0, options)
if err != nil { if err != nil {
wwlog.Warn(fmt.Sprintf("Couldn't create overlay for ephermal mount points: %s", err)) wwlog.Warn("Couldn't create overlay for ephermal mount points: %s", err)
} }
} else if nodename != "" { } else if nodename != "" {
nodeDB, err := node.New() nodeDB, err := node.New()
@@ -142,6 +142,10 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
} }
for _, mntPnt := range mountPts { for _, mntPnt := range mountPts {
if mntPnt.Cow {
continue
}
wwlog.Debug("bind mounting: %s -> %s", mntPnt.Source, path.Join(containerPath, mntPnt.Dest))
err = syscall.Mount(mntPnt.Source, path.Join(containerPath, mntPnt.Dest), "", syscall.MS_BIND, "") err = syscall.Mount(mntPnt.Source, path.Join(containerPath, mntPnt.Dest), "", syscall.MS_BIND, "")
if err != nil { if err != nil {
wwlog.Warn("Couldn't mount %s to %s: %s", mntPnt.Source, mntPnt.Dest, err) wwlog.Warn("Couldn't mount %s to %s: %s", mntPnt.Source, mntPnt.Dest, err)
@@ -195,6 +199,9 @@ the invalid mount points. Directories always have '/' as suffix
func checkMountPoints(containerName string, binds []*warewulfconf.MountEntry) (overlayObjects []string) { func checkMountPoints(containerName string, binds []*warewulfconf.MountEntry) (overlayObjects []string) {
overlayObjects = []string{} overlayObjects = []string{}
for _, b := range binds { for _, b := range binds {
if b.Cow {
continue
}
_, err := os.Stat(b.Source) _, err := os.Stat(b.Source)
if err != nil { if err != nil {
wwlog.Debug("Couldn't stat %s create no mount point in container", b.Source) wwlog.Debug("Couldn't stat %s create no mount point in container", b.Source)

View File

@@ -54,7 +54,6 @@ func runContainedCmd(cmd *cobra.Command, containerName string, args []string) (e
}() }()
logStr := fmt.Sprint(wwlog.GetLogLevel()) logStr := fmt.Sprint(wwlog.GetLogLevel())
childArgs := []string{"--warewulfconf", conf.GetWarewulfConf(), "--loglevel", logStr, "container", "exec", "__child"} childArgs := []string{"--warewulfconf", conf.GetWarewulfConf(), "--loglevel", logStr, "container", "exec", "__child"}
childArgs = append(childArgs, containerName) childArgs = append(childArgs, containerName)
for _, b := range binds { for _, b := range binds {
@@ -64,8 +63,40 @@ func runContainedCmd(cmd *cobra.Command, containerName string, args []string) (e
childArgs = append(childArgs, "--node", nodeName) childArgs = append(childArgs, "--node", nodeName)
} }
childArgs = append(childArgs, args...) childArgs = append(childArgs, args...)
// copy the files into the container at this stage, es in __child the
// command syscall.Exec which replaces the __child process with the
// exec command in the container. All the mounts, have to be done in
// __child so that the used mounts don't propagate outside on the host
// (see the CLONE attributes), but as for the cow copy option we need
// to see if a file was modified after it was copied into the container
// so do this here.
// At first read out conf, the parse commandline, as copy files has the
// same synatx as mount points
mountPts := conf.MountsContainer
mountPts = append(container.InitMountPnts(binds), mountPts...)
filesToCpy := getCopyFiles(containerName, mountPts)
for i, cpyFile := range filesToCpy {
if err = util.CopyFile(cpyFile.Src, path.Join(container.RootFsDir(containerName), cpyFile.FileName)); err != nil {
return fmt.Errorf("couldn't copy files into container: %w", err)
}
// we can ignore error as the file was copied
stat, _ := os.Stat(path.Join(container.RootFsDir(containerName), cpyFile.FileName))
filesToCpy[i].ModTime = stat.ModTime()
}
wwlog.Verbose("Running contained command: %s", childArgs) wwlog.Verbose("Running contained command: %s", childArgs)
return childCommandFunc(cmd, childArgs) retVal := childCommandFunc(cmd, childArgs)
for _, cpyFile := range filesToCpy {
if modStat, err := os.Stat(path.Join(container.RootFsDir(containerName), cpyFile.FileName)); err != nil {
wwlog.Info("copied file was removed: %s", err)
} else {
if modStat.ModTime() == cpyFile.ModTime {
if err := os.Remove(path.Join(container.RootFsDir(containerName), cpyFile.FileName)); err != nil {
wwlog.Warn("couldn't remove copied file: %s", err)
}
}
}
}
return retVal
} }
func CobraRunE(cmd *cobra.Command, args []string) error { func CobraRunE(cmd *cobra.Command, args []string) error {
@@ -154,3 +185,40 @@ func SetBinds(myBinds []string) {
func SetNode(myNode string) { func SetNode(myNode string) {
nodeName = myNode nodeName = myNode
} }
// file name and last modification time so we can remove the file if it wasn't modified
type cowFile struct {
FileName string
Src string
ModTime time.Time
Cow bool
}
/*
Check the objects we want to copy in, instead of mounting
*/
func getCopyFiles(containerNamer string, binds []*warewulfconf.MountEntry) (copyObjects []cowFile) {
for _, bind := range binds {
if !bind.Cow || bind.ReadOnly {
continue
}
if _, err := os.Stat(path.Join(container.RootFsDir(containerNamer), path.Dir(bind.Dest))); err != nil {
wwlog.Warn("destination directory doesn't exist: %s", err)
continue
}
if _, err := os.Stat(path.Join(container.RootFsDir(containerNamer), bind.Dest)); err == nil {
wwlog.Verbose("file exists in container: %s", bind.Dest)
continue
}
if _, err := os.Stat(bind.Source); err != nil {
wwlog.Warn("source doesn't exist: %s", err)
continue
}
copyObjects = append(copyObjects, cowFile{
FileName: bind.Dest,
Src: bind.Source,
Cow: bind.Cow,
})
}
return
}

View File

@@ -32,7 +32,11 @@ var (
func init() { func init() {
baseCmd.AddCommand(child.GetCommand()) baseCmd.AddCommand(child.GetCommand())
baseCmd.PersistentFlags().StringArrayVarP(&binds, "bind", "b", []string{}, "Bind a local path into the container (must exist)") baseCmd.PersistentFlags().StringArrayVarP(&binds, "bind", "b", []string{}, `Bind a local path which must exist into the container. Syntax is
source[:destination[:{ro|cow}]] If destination is not set,
it will the same path as source.The additional parameter is
ro for read only and cow, which means the file is copied into
the container and removed if not modified.`)
baseCmd.PersistentFlags().BoolVar(&SyncUser, "syncuser", false, "Synchronize UIDs/GIDs from host to container") baseCmd.PersistentFlags().BoolVar(&SyncUser, "syncuser", false, "Synchronize UIDs/GIDs from host to container")
baseCmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "Create a read only view of the container for the given node") baseCmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "Create a read only view of the container for the given node")
} }

View File

@@ -28,7 +28,11 @@ var (
) )
func init() { func init() {
baseCmd.PersistentFlags().StringArrayVarP(&binds, "bind", "b", []string{}, "Bind a local path into the container (must exist)") baseCmd.PersistentFlags().StringArrayVarP(&binds, "bind", "b", []string{}, `Bind a local path which must exist into the container. Syntax is
source[:destination[:{ro|cow}]] If destination is not set,
it will the same path as source.The additional parameter is
ro for read only and cow, which means the file is copied into
the container and removed if not modified.`)
baseCmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "Create a read only view of the container for the given node") baseCmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "Create a read only view of the container for the given node")
} }

View File

@@ -7,4 +7,5 @@ type MountEntry struct {
Dest string `yaml:"dest,omitempty"` Dest string `yaml:"dest,omitempty"`
ReadOnly bool `yaml:"readonly,omitempty"` ReadOnly bool `yaml:"readonly,omitempty"`
Options string `yaml:"options,omitempty"` // ignored at the moment Options string `yaml:"options,omitempty"` // ignored at the moment
Cow bool `yaml:"cow,omitempty"` // copy the file into the container and don't remove if modified
} }

View File

@@ -21,13 +21,19 @@ func InitMountPnts(binds []string) (mounts []*warewulfconf.MountEntry) {
dest = bind[1] dest = bind[1]
} }
readonly := false readonly := false
if len(bind) >= 3 && bind[2] == "ro" { cow := false
readonly = true if len(bind) >= 3 {
if bind[2] == "ro" {
readonly = true
} else if bind[2] == "cow" {
cow = true
}
} }
mntPnt := warewulfconf.MountEntry{ mntPnt := warewulfconf.MountEntry{
Source: bind[0], Source: bind[0],
Dest: dest, Dest: dest,
ReadOnly: readonly, ReadOnly: readonly,
Cow: cow,
} }
mounts = append(mounts, &mntPnt) mounts = append(mounts, &mntPnt)
} }

View File

@@ -213,6 +213,21 @@ when using the exec command. This works as follows:
location, as it is almost always present and empty in every Linux location, as it is almost always present and empty in every Linux
distribution (as prescribed by the LSB file hierarchy standard). distribution (as prescribed by the LSB file hierarchy standard).
Files which should always be present in a container image like ``resolv.conf``
can be specified in ``warewulf.conf`` with following container_exit
.. code-block:: yaml
container mounts:
- source: /etc/resolv.conf
dest: /etc/resolv.conf
readonly: true
.. note::
Instead of the ``readonly`` setting you can set ``cow``, which
has the effect, that the source file is copied to the container
and removed if it was modified. This useful for file used for
registrations.
When the command completes, if anything within the container changed, When the command completes, if anything within the container changed,
the container will be rebuilt into a bootable static object the container will be rebuilt into a bootable static object
automatically. automatically.