Update overlay edit with tempfile and mtime

The recent `overlay edit` command reimplementation included some
incorrect ordering that led to spurious error messages for files that
did not yet exist. This reimplementation reorders operations to avoid
such error messages and clarifies the implementation by editing in a
temporary file and using mtime to discern whether an edit has occurred.

Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
Jonathon Anderson
2023-08-09 05:58:35 -06:00
parent f1eab8c363
commit fb49b6082e
2 changed files with 100 additions and 79 deletions

View File

@@ -1,13 +1,11 @@
package edit
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"os"
"path"
"path/filepath"
"time"
"github.com/hpcng/warewulf/internal/pkg/overlay"
"github.com/hpcng/warewulf/internal/pkg/util"
@@ -15,50 +13,8 @@ import (
"github.com/spf13/cobra"
)
func CobraRunE(cmd *cobra.Command, args []string) error {
editor := os.Getenv("EDITOR")
var overlaySourceDir string
overlayName := args[0]
fileName := args[1]
if editor == "" {
editor = "/bin/vi"
}
overlaySourceDir = overlay.OverlaySourceDir(overlayName)
if !util.IsDir(overlaySourceDir) {
wwlog.Error("Overlay does not exist: %s", overlayName)
os.Exit(1)
}
overlayFile := path.Join(overlaySourceDir, fileName)
wwlog.Debug("Will edit overlay file: %s", overlayFile)
if CreateDirs {
err := os.MkdirAll(path.Dir(overlayFile), 0755)
if err != nil {
wwlog.Error("Could not create directory: %s", path.Dir(overlayFile))
os.Exit(1)
}
} else {
if !util.IsDir(path.Dir(overlayFile)) {
wwlog.Error("Can not create file, parent directory does not exist, try adding the")
wwlog.Error("'--parents' option to create the directory.")
os.Exit(1)
}
}
newFile := false
if !util.IsFile(overlayFile) && filepath.Ext(overlayFile) == ".ww" {
wwlog.Verbose("This is a new file, creating some default content")
newFile = true
w, err := os.OpenFile(overlayFile, os.O_RDWR|os.O_CREATE, os.FileMode(PermMode))
if err != nil {
wwlog.Warn("Could not create file for writing: %s", err)
}
fmtStr := `# This is a Warewulf Template file.
const initialTemplate = `# This is a Warewulf Template file.
#
# This file (suffix '.ww') will be automatically rewritten without the suffix
# when the overlay is rendered for the individual nodes. Here are some examples
@@ -75,43 +31,107 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
# Time: {{.BuildTime}}
# Source: {{.BuildSource}}
`
fmt.Fprint(w, fmtStr)
w.Close()
}
fileDesc1, err := os.OpenFile(overlayFile, os.O_RDWR, os.FileMode(PermMode))
if err != nil {
wwlog.Verbose("Files doesn't exist or can't be created: %s", err)
}
defer fileDesc1.Close()
_, _ = fileDesc1.Seek(0, 0)
hasher := sha256.New()
if _, err := io.Copy(hasher, fileDesc1); err != nil {
wwlog.Verbose("Problems getting checksum of file %s\n", err)
}
sum1 := hex.EncodeToString(hasher.Sum(nil))
fileDesc1.Close()
err = util.ExecInteractive(editor, overlayFile)
if err != nil {
wwlog.Error("Editor process existed with non-zero")
func CobraRunE(cmd *cobra.Command, args []string) error {
overlayName := args[0]
fileName := args[1]
overlaySourceDir := overlay.OverlaySourceDir(overlayName)
if !util.IsDir(overlaySourceDir) {
wwlog.Error("Overlay does not exist: %s", overlayName)
os.Exit(1)
}
fileDesc2, err := os.OpenFile(overlayFile, os.O_RDWR, os.FileMode(PermMode))
if err != nil {
wwlog.Warn("Could not open file for editing: %s", err)
}
defer fileDesc2.Close()
_, _ = fileDesc2.Seek(0, 0)
hasher.Reset()
if _, err := io.Copy(hasher, fileDesc2); err != nil {
wwlog.Error("Problems getting checksum of file %s\n", err)
}
sum2 := hex.EncodeToString(hasher.Sum(nil))
if sum1 == sum2 && newFile {
wwlog.Verbose("New template %s wasn't modified, deleting it", overlayFile)
err = os.Remove(overlayFile)
overlayFile := path.Join(overlaySourceDir, fileName)
wwlog.Debug("Will edit overlay file: %s", overlayFile)
overlayFileDir := path.Dir(overlayFile)
if CreateDirs {
err := os.MkdirAll(overlayFileDir, 0755)
if err != nil {
wwlog.Warn("Could not delete %s", overlayFile)
wwlog.Error("Could not create directory: %s", overlayFileDir)
os.Exit(1)
}
} else {
if !util.IsDir(overlayFileDir) {
wwlog.Error("%s does not exist. Use '--parents' option to create automatically.", overlayFileDir)
os.Exit(1)
}
}
tempFile, tempFileErr := os.CreateTemp("", "ww-overlay-edit-")
if tempFileErr != nil {
wwlog.Error("Unable to create temporary file for editing: %s", tempFileErr)
os.Exit(1)
}
defer os.Remove(tempFile.Name())
wwlog.Debug("Using temporary file %s", tempFile.Name())
if util.IsFile(overlayFile) {
originalFile, openErr := os.Open(overlayFile)
if openErr != nil {
wwlog.Error("Unable to open %s: %s", overlayFile, openErr)
os.Exit(1)
}
if _, err := io.Copy(tempFile, originalFile); err != nil {
wwlog.Error("Unable to copy %s to %s for editing: %s", originalFile.Name(), tempFile.Name(), err)
os.Exit(1)
}
originalFile.Close()
} else if filepath.Ext(overlayFile) == ".ww" {
if _, err := tempFile.Write([]byte(initialTemplate)); err != nil {
wwlog.Error("Unable to write to %s: %s", tempFile.Name(), err)
os.Exit(1)
}
}
tempFile.Close()
var startTime time.Time
if fileInfo, err := os.Stat(tempFile.Name()); err != nil {
wwlog.Error("Unable to stat %s: %s", tempFile.Name(), err)
os.Exit(1)
} else {
startTime = fileInfo.ModTime()
}
editor := os.Getenv("EDITOR")
if editor == "" {
editor = "/bin/vi"
}
if editorErr := util.ExecInteractive(editor, tempFile.Name()); editorErr != nil {
wwlog.Error("Editor process exited with an error: %s", editorErr)
os.Exit(1)
}
tempFileReader, tempFileErr := os.Open(tempFile.Name())
if tempFileErr != nil {
wwlog.Error("Unable to open %s: %s", tempFile.Name(), tempFileErr)
os.Exit(1)
}
defer tempFileReader.Close()
if fileInfo, err := os.Stat(tempFile.Name()); err != nil {
wwlog.Error("Unable to stat %s: %s", tempFile.Name(), err)
os.Exit(1)
} else {
if startTime == fileInfo.ModTime() {
wwlog.Debug("No change detected. Not updating overlay.")
os.Exit(0)
}
}
destination, destinationErr := os.OpenFile(overlayFile, os.O_RDWR|os.O_CREATE, os.FileMode(PermMode))
if destinationErr != nil {
wwlog.Error("Unable to update %s: %s", overlayFile, destinationErr)
os.Exit(1)
}
defer destination.Close()
wwlog.Debug("Copy %s to %s", tempFileReader.Name(), destination.Name())
if _, copyErr := io.Copy(destination, tempFileReader); copyErr != nil {
wwlog.Error("Unable to update %s: %s", destination.Name(), copyErr)
os.Exit(1)
}
return nil