Merge pull request #1365 from mslacken/CopyOnChange

add cow option for binds
This commit is contained in:
Christian Goll
2024-09-09 11:15:55 +02:00
committed by GitHub
8 changed files with 131 additions and 7 deletions

View File

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

View File

@@ -92,7 +92,7 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
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))
wwlog.Warn("Couldn't create overlay for ephermal mount points: %s", err)
}
} else if nodename != "" {
nodeDB, err := node.New()
@@ -142,6 +142,10 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
}
for _, mntPnt := range mountPts {
if mntPnt.Copy {
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, "")
if err != nil {
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) {
overlayObjects = []string{}
for _, b := range binds {
if b.Copy {
continue
}
_, err := os.Stat(b.Source)
if err != nil {
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())
childArgs := []string{"--warewulfconf", conf.GetWarewulfConf(), "--loglevel", logStr, "container", "exec", "__child"}
childArgs = append(childArgs, containerName)
for _, b := range binds {
@@ -64,8 +63,32 @@ func runContainedCmd(cmd *cobra.Command, containerName string, args []string) (e
childArgs = append(childArgs, "--node", nodeName)
}
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 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 := append(container.InitMountPnts(binds), conf.MountsContainer...)
filesToCpy := getCopyFiles(mountPts)
for _, cpyFile := range filesToCpy {
if err := (cpyFile).copyToContainer(containerName); err != nil {
wwlog.Warn("couldn't copy file: %s", err)
}
}
wwlog.Verbose("Running contained command: %s", childArgs)
return childCommandFunc(cmd, childArgs)
retVal := childCommandFunc(cmd, childArgs)
for _, cpyFile := range filesToCpy {
if cpyFile.shouldRemoveFromContainer(containerName) {
if err := cpyFile.removeFromContainer(containerName); err != nil {
wwlog.Warn("couldn't remove file: %s", err)
}
}
}
return retVal
}
func CobraRunE(cmd *cobra.Command, args []string) error {
@@ -154,3 +177,68 @@ func SetBinds(myBinds []string) {
func SetNode(myNode string) {
nodeName = myNode
}
// file name and last modification time so we can remove the file if it wasn't modified
type copyFile struct {
fileName string
src string
modTime time.Time
}
func (this *copyFile) containerDest(containerName string) string {
return path.Join(container.RootFsDir(containerName), this.fileName)
}
func (this *copyFile) copyToContainer(containerName string) error {
containerDest := this.containerDest(containerName)
if _, err := os.Stat(path.Dir(containerDest)); err != nil {
return err
} else if _, err := os.Stat(containerDest); err == nil {
return err
} else if _, err := os.Stat(this.src); err != nil {
return err
} else if err := util.CopyFile(this.src, containerDest); err != nil {
return err
} else if stat, err := os.Stat(containerDest); err != nil {
return err
} else {
this.modTime = stat.ModTime()
return nil
}
}
func (this *copyFile) shouldRemoveFromContainer(containerName string) bool {
containerDest := this.containerDest(containerName)
if this.modTime.IsZero() {
wwlog.Debug("file was not previously copied: %s", this.fileName)
return false
} else if destStat, err := os.Stat(containerDest); err != nil {
wwlog.Verbose("file is no longer present: %s (%s)", this.fileName, err)
return false
} else if destStat.ModTime() == this.modTime {
wwlog.Verbose("don't remove modified file:", this.fileName)
return false
} else {
return true
}
}
func (this *copyFile) removeFromContainer(containerName string) error {
containerDest := this.containerDest(containerName)
return os.Remove(containerDest)
}
/*
Check the objects we want to copy in, instead of mounting
*/
func getCopyFiles(binds []*warewulfconf.MountEntry) (copyObjects []*copyFile) {
for _, bind := range binds {
if bind.Copy {
copyObjects = append(copyObjects, &copyFile{
fileName: bind.Dest,
src: bind.Source,
})
}
}
return
}

View File

@@ -32,7 +32,10 @@ var (
func init() {
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{}, `source[:destination[:{ro|copy}]]
Bind a local path which must exist into the container. If destination is not
set, uses the same path as source. "ro" binds read-only. "copy" temporarily
copies the file into the 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")
}

View File

@@ -28,7 +28,10 @@ var (
)
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{}, `source[:destination[:{ro|copy}]]
Bind a local path which must exist into the container. If destination is not
set, uses the same path as source. "ro" binds read-only. "copy" temporarily
copies the file into the container.`)
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"`
ReadOnly bool `yaml:"readonly,omitempty"`
Options string `yaml:"options,omitempty"` // ignored at the moment
Copy bool `yaml:"copy,omitempty"` // temporarily copy the file into the container
}

View File

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

View File

@@ -213,6 +213,20 @@ when using the exec command. This works as follows:
location, as it is almost always present and empty in every Linux
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``:
.. code-block:: yaml
container mounts:
- source: /etc/resolv.conf
dest: /etc/resolv.conf
readonly: true
.. note::
Instead of ``readonly: true`` you can set ``copy: true``. This causes the
source file to be copied to the container and removed if it was not
modified. This can be useful for files used for registrations.
When the command completes, if anything within the container changed,
the container will be rebuilt into a bootable static object
automatically.