From fb49b6082e8f49047b69ac6f76925b704d38cef5 Mon Sep 17 00:00:00 2001 From: Jonathon Anderson Date: Wed, 9 Aug 2023 05:58:35 -0600 Subject: [PATCH] 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 --- CHANGELOG.md | 1 + internal/app/wwctl/overlay/edit/main.go | 178 +++++++++++++----------- 2 files changed, 100 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc0e55f1..3e4c690d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 file is create and will executed after the `ignition-disks-ww4.service` has finished. Entries in `/etc/fstab` for every file system are created with the `noauto` option. - wwclient has now a commandline switch for the location of warewulf.conf +- `wwctl overlay edit` uses a temporary file and checks mtime. ## [4.4.0] 2023-01-18 diff --git a/internal/app/wwctl/overlay/edit/main.go b/internal/app/wwctl/overlay/edit/main.go index 4ef1c019..3b6bda6c 100644 --- a/internal/app/wwctl/overlay/edit/main.go +++ b/internal/app/wwctl/overlay/edit/main.go @@ -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