package edit import ( "bytes" "os" "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" "github.com/warewulf/warewulf/internal/pkg/config" "github.com/warewulf/warewulf/internal/pkg/testenv" "github.com/warewulf/warewulf/internal/pkg/warewulfd" ) func Test_OverlayEdit(t *testing.T) { tests := []struct { name string overlayName string fileName string parents bool editorScript string setup func(t *testing.T, conf *config.WarewulfYaml, overlayName, fileName string) (mtime time.Time) wantErr bool verify func(t *testing.T, conf *config.WarewulfYaml, overlayName, fileName string, mtime time.Time) }{ { name: "edit existing file", overlayName: "test-overlay", fileName: "test.txt", editorScript: `#!/bin/sh sed -i 's/hello/world/' "$1"`, setup: func(t *testing.T, conf *config.WarewulfYaml, overlayName, fileName string) time.Time { overlayDir := filepath.Join(conf.Paths.SiteOverlaydir(), overlayName) assert.NoError(t, os.MkdirAll(overlayDir, 0755)) assert.NoError(t, os.WriteFile(filepath.Join(overlayDir, fileName), []byte("hello"), 0644)) return time.Time{} }, wantErr: false, verify: func(t *testing.T, conf *config.WarewulfYaml, overlayName, fileName string, mtime time.Time) { overlayDir := filepath.Join(conf.Paths.SiteOverlaydir(), overlayName) content, err := os.ReadFile(filepath.Join(overlayDir, fileName)) assert.NoError(t, err) assert.Equal(t, "world", string(content)) }, }, { name: "edit non-existing file", overlayName: "test-overlay", fileName: "new-file.txt", editorScript: `#!/bin/sh echo "new content" > "$1"`, setup: func(t *testing.T, conf *config.WarewulfYaml, overlayName, fileName string) time.Time { overlayDir := filepath.Join(conf.Paths.SiteOverlaydir(), overlayName) assert.NoError(t, os.MkdirAll(overlayDir, 0755)) return time.Time{} }, wantErr: false, verify: func(t *testing.T, conf *config.WarewulfYaml, overlayName, fileName string, mtime time.Time) { overlayDir := filepath.Join(conf.Paths.SiteOverlaydir(), overlayName) content, err := os.ReadFile(filepath.Join(overlayDir, fileName)) assert.NoError(t, err) assert.Equal(t, "new content\n", string(content)) }, }, { name: "edit non-existing .ww file", overlayName: "test-overlay", fileName: "template.ww", editorScript: `#!/bin/sh sed -i 's/autogenerated by warewulf/edited by test/' "$1"`, setup: func(t *testing.T, conf *config.WarewulfYaml, overlayName, fileName string) time.Time { overlayDir := filepath.Join(conf.Paths.SiteOverlaydir(), overlayName) assert.NoError(t, os.MkdirAll(overlayDir, 0755)) return time.Time{} }, wantErr: false, verify: func(t *testing.T, conf *config.WarewulfYaml, overlayName, fileName string, mtime time.Time) { overlayDir := filepath.Join(conf.Paths.SiteOverlaydir(), overlayName) content, err := os.ReadFile(filepath.Join(overlayDir, fileName)) assert.NoError(t, err) assert.Contains(t, string(content), "edited by test") }, }, { name: "edit without changes", overlayName: "test-overlay", fileName: "no-change.txt", editorScript: `#!/bin/sh # Do nothing to the file exit 0`, setup: func(t *testing.T, conf *config.WarewulfYaml, overlayName, fileName string) time.Time { overlayDir := filepath.Join(conf.Paths.SiteOverlaydir(), overlayName) assert.NoError(t, os.MkdirAll(overlayDir, 0755)) filePath := filepath.Join(overlayDir, fileName) assert.NoError(t, os.WriteFile(filePath, []byte("original"), 0644)) stat, err := os.Stat(filePath) assert.NoError(t, err) return stat.ModTime() }, wantErr: false, verify: func(t *testing.T, conf *config.WarewulfYaml, overlayName, fileName string, mtime time.Time) { overlayDir := filepath.Join(conf.Paths.SiteOverlaydir(), overlayName) filePath := filepath.Join(overlayDir, fileName) stat, err := os.Stat(filePath) assert.NoError(t, err) assert.True(t, stat.ModTime().Equal(mtime), "ModTime should not have changed") }, }, { name: "edit file in dist overlay (CoW)", overlayName: "dist-overlay", fileName: "test.txt", editorScript: `#!/bin/sh sed -i 's/dist/site/' "$1"`, setup: func(t *testing.T, conf *config.WarewulfYaml, overlayName, fileName string) time.Time { overlayDir := filepath.Join(conf.Paths.DistributionOverlaydir(), overlayName) assert.NoError(t, os.MkdirAll(overlayDir, 0755)) assert.NoError(t, os.WriteFile(filepath.Join(overlayDir, fileName), []byte("dist content"), 0644)) return time.Time{} }, wantErr: false, verify: func(t *testing.T, conf *config.WarewulfYaml, overlayName, fileName string, mtime time.Time) { distOverlayFile := filepath.Join(conf.Paths.DistributionOverlaydir(), overlayName, fileName) content, err := os.ReadFile(distOverlayFile) assert.NoError(t, err) assert.Equal(t, "dist content", string(content)) siteOverlayFile := filepath.Join(conf.Paths.SiteOverlaydir(), overlayName, fileName) content, err = os.ReadFile(siteOverlayFile) assert.NoError(t, err) assert.Equal(t, "site content", string(content)) }, }, { name: "edit in missing dir no parents", overlayName: "test-overlay", fileName: "newdir/test.txt", editorScript: `#!/bin/sh echo "content" > "$1"`, setup: func(t *testing.T, conf *config.WarewulfYaml, overlayName, fileName string) time.Time { overlayDir := filepath.Join(conf.Paths.SiteOverlaydir(), overlayName) assert.NoError(t, os.MkdirAll(overlayDir, 0755)) return time.Time{} }, wantErr: true, verify: func(t *testing.T, conf *config.WarewulfYaml, overlayName, fileName string, mtime time.Time) { overlayFile := filepath.Join(conf.Paths.SiteOverlaydir(), overlayName, fileName) _, err := os.Stat(overlayFile) assert.True(t, os.IsNotExist(err)) }, }, { name: "edit in missing dir with parents", overlayName: "test-overlay", fileName: "newdir/test.txt", parents: true, editorScript: `#!/bin/sh echo "content" > "$1"`, setup: func(t *testing.T, conf *config.WarewulfYaml, overlayName, fileName string) time.Time { overlayDir := filepath.Join(conf.Paths.SiteOverlaydir(), overlayName) assert.NoError(t, os.MkdirAll(overlayDir, 0755)) return time.Time{} }, wantErr: false, verify: func(t *testing.T, conf *config.WarewulfYaml, overlayName, fileName string, mtime time.Time) { overlayFile := filepath.Join(conf.Paths.SiteOverlaydir(), overlayName, fileName) content, err := os.ReadFile(overlayFile) assert.NoError(t, err) assert.Equal(t, "content\n", string(content)) }, }, } warewulfd.SetNoDaemon() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { env := testenv.New(t) defer env.RemoveAll() conf := env.Configure() scriptPath := filepath.Join(t.TempDir(), "fake_editor.sh") err := os.WriteFile(scriptPath, []byte(tt.editorScript), 0755) assert.NoError(t, err) t.Setenv("EDITOR", scriptPath) var mtime time.Time if tt.setup != nil { mtime = tt.setup(t, conf, tt.overlayName, tt.fileName) } var args []string if tt.parents { args = []string{"--parents", tt.overlayName, tt.fileName} } else { args = []string{tt.overlayName, tt.fileName} } baseCmd := GetCommand() baseCmd.SetArgs(args) buf := new(bytes.Buffer) baseCmd.SetOut(buf) baseCmd.SetErr(buf) err = baseCmd.Execute() if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } if tt.verify != nil { tt.verify(t, conf, tt.overlayName, tt.fileName, mtime) } }) } }