Merge pull request #1968 from mslacken/FixSiteOverlays

refactored overlay class
This commit is contained in:
Jonathon Anderson
2025-08-27 22:42:39 -06:00
committed by GitHub
35 changed files with 1220 additions and 508 deletions

View File

@@ -7,13 +7,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased
### Added
- Build support for EL10, EL10 depends on `dnsmasq` and no longer `dhcpd-server` (EOL). #1974
- `make rpm` added for local development rpm builds. #1974
### Changed
- `make dist` now uses `mktemp` instead of `.dist`. #1974
- Fixed dnsmasq template file to use basename for ipxe files. #1974
- For EL10 default to dnsmasq for dhcpd and tftp. #1974
- Refactored overlay class. #1968
## v4.6.3, 2025-08-01

View File

@@ -89,7 +89,8 @@ func OverlayList(cmd *cobra.Command, args []string, toComplete string) ([]string
}
func OverlayFiles(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
ret, _ := overlay.OverlayGetFiles(args[0])
myOverlay, _ := overlay.Get(args[0])
ret, _ := myOverlay.GetFiles()
return ret, cobra.ShellCompDirectiveNoFileComp
}

View File

@@ -1,21 +0,0 @@
package chmod
import (
"testing"
)
// TestArgsOverlayChmod is a regression test for 260.
// Two arguments should fail, three should succeed.
func TestArgsOverlayChmod(t *testing.T) {
command := GetCommand()
err := command.Args(command, []string{"overlay_name", "file_name"})
if err == nil {
t.Errorf("two arguments to overlay chmod should fail")
}
err = command.Args(command, []string{"overlay_name", "file_name", "0755"})
if err != nil {
t.Errorf("three arguments to overlay chmod should succeed")
}
}

View File

@@ -2,42 +2,22 @@ package chmod
import (
"fmt"
"os"
"strconv"
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/overlay"
"github.com/warewulf/warewulf/internal/pkg/util"
)
func CobraRunE(cmd *cobra.Command, args []string) error {
overlayName := args[0]
fileName := args[1]
myOverlay, err := overlay.Get(args[0])
if err != nil {
return err
}
path := args[1]
permissionMode, err := strconv.ParseUint(args[2], 8, 32)
if err != nil {
return fmt.Errorf("could not convert requested mode: %s", err)
}
overlay_ := overlay.GetOverlay(overlayName)
if !overlay_.IsSiteOverlay() {
overlay_, err = overlay_.CloneSiteOverlay()
if err != nil {
return err
}
}
if !overlay_.Exists() {
return fmt.Errorf("overlay does not exist: %s", overlayName)
}
overlayFile := overlay_.File(fileName)
if !(util.IsFile(overlayFile) || util.IsDir(overlayFile)) {
return fmt.Errorf("file does not exist within overlay: %s:%s", overlayName, fileName)
}
err = os.Chmod(overlayFile, os.FileMode(permissionMode))
if err != nil {
return fmt.Errorf("could not set permission: %s", err)
}
return nil
return myOverlay.Chmod(path, permissionMode)
}

View File

@@ -0,0 +1,95 @@
package chmod
import (
"bytes"
"io/fs"
"os"
"path/filepath"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/warewulf/warewulf/internal/pkg/testenv"
"github.com/warewulf/warewulf/internal/pkg/warewulfd"
)
func Test_OverlayChmod(t *testing.T) {
tests := []struct {
name string
overlayName string
fileName string
perm string
wantErr bool
system bool
startPerm fs.FileMode
}{
{
name: "simple chmod",
overlayName: "test-overlay",
fileName: "test.txt",
perm: "0600",
wantErr: false,
system: false,
startPerm: 0644,
},
{
name: "system overlay chmod",
overlayName: "wwinit", // A known system overlay
fileName: "init.sh",
perm: "0700",
wantErr: false,
system: true,
startPerm: 0755,
},
}
warewulfd.SetNoDaemon()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
env := testenv.New(t)
conf := env.Configure()
// Setup overlay
var overlayDir string
if tt.system {
overlayDir = filepath.Join(conf.Paths.DistributionOverlaydir(), tt.overlayName)
} else {
overlayDir = filepath.Join(conf.Paths.WWOverlaydir, tt.overlayName)
}
err := os.MkdirAll(overlayDir, 0755)
assert.NoError(t, err)
filePath := filepath.Join(overlayDir, tt.fileName)
err = os.WriteFile(filePath, []byte("test"), tt.startPerm)
assert.NoError(t, err)
baseCmd := GetCommand()
baseCmd.SetArgs([]string{tt.overlayName, tt.fileName, tt.perm})
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)
}
// Check permissions
// for system overlays, the file is copied to a site overlay
if tt.system {
overlayDir = filepath.Join(conf.Paths.WWOverlaydir, tt.overlayName)
filePath = filepath.Join(overlayDir, tt.fileName)
}
stat, err := os.Stat(filePath)
assert.NoError(t, err)
if !tt.wantErr {
mode, err := strconv.ParseUint(tt.perm, 8, 32)
assert.NoError(t, err)
assert.Equal(t, fs.FileMode(mode), stat.Mode().Perm())
} else {
assert.Equal(t, tt.startPerm, stat.Mode().Perm())
}
})
}
}

View File

@@ -2,58 +2,44 @@ package chown
import (
"fmt"
"os"
"strconv"
"github.com/warewulf/warewulf/internal/pkg/overlay"
"github.com/warewulf/warewulf/internal/pkg/util"
"strings"
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/overlay"
)
func CobraRunE(cmd *cobra.Command, args []string) error {
var uid int
var gid int
fileName := args[1]
chownSpec := args[2]
var uid, gid = -1, -1
var err error
overlayName := args[0]
fileName := args[1]
uid, err = strconv.Atoi(args[2])
if strings.Contains(chownSpec, ":") {
parts := strings.SplitN(chownSpec, ":", 2)
if parts[0] != "" {
uid, err = strconv.Atoi(parts[0])
if err != nil {
return fmt.Errorf("UID is not an integer: %s", args[2])
return fmt.Errorf("UID is not an integer: %s", parts[0])
}
if len(args) > 3 {
gid, err = strconv.Atoi(args[3])
}
if parts[1] != "" {
gid, err = strconv.Atoi(parts[1])
if err != nil {
return fmt.Errorf("GID is not an integer: %s", args[3])
return fmt.Errorf("GID is not an integer: %s", parts[1])
}
}
} else {
gid = -1
uid, err = strconv.Atoi(chownSpec)
if err != nil {
return fmt.Errorf("UID is not an integer: %s", chownSpec)
}
}
overlay_ := overlay.GetOverlay(overlayName)
if !overlay_.Exists() {
return fmt.Errorf("overlay does not exist: %s", overlayName)
}
if !overlay_.IsSiteOverlay() {
overlay_, err = overlay_.CloneSiteOverlay()
myOverlay, err := overlay.Get(args[0])
if err != nil {
return err
}
}
overlayFile := overlay_.File(fileName)
if !(util.IsFile(overlayFile) || util.IsDir(overlayFile)) {
return fmt.Errorf("file does not exist within overlay: %s:%s", overlayName, fileName)
}
err = os.Chown(overlayFile, uid, gid)
if err != nil {
return fmt.Errorf("could not set ownership: %s", err)
}
return nil
return myOverlay.Chown(fileName, uid, gid)
}

View File

@@ -0,0 +1,156 @@
package chown
import (
"bytes"
"os"
"path/filepath"
"strconv"
"syscall"
"testing"
"github.com/stretchr/testify/assert"
"github.com/warewulf/warewulf/internal/pkg/testenv"
"github.com/warewulf/warewulf/internal/pkg/warewulfd"
)
func Test_OverlayChown(t *testing.T) {
currentUser := strconv.Itoa(os.Getuid())
currentGroup := strconv.Itoa(os.Getgid())
tests := []struct {
name string
overlayName string
fileName string
chownSpec string
wantErr bool
system bool
checkUser bool
checkGroup bool
}{
{
name: "simple chown user and group",
overlayName: "test-overlay-1",
fileName: "test.txt",
chownSpec: currentUser + ":" + currentGroup,
wantErr: false,
system: false,
checkUser: true,
checkGroup: true,
},
{
name: "simple chown user only",
overlayName: "test-overlay-2",
fileName: "test.txt",
chownSpec: currentUser,
wantErr: false,
system: false,
checkUser: true,
checkGroup: false, // group should not change
},
{
name: "simple chown group only",
overlayName: "test-overlay-3",
fileName: "test.txt",
chownSpec: ":" + currentGroup,
wantErr: false,
system: false,
checkUser: false, // user should not change
checkGroup: true,
},
{
name: "system overlay chown",
overlayName: "wwinit", // A known system overlay
fileName: "init.sh",
chownSpec: currentUser + ":" + currentGroup,
wantErr: false,
system: true,
checkUser: true,
checkGroup: true,
},
{
name: "bad chown spec",
overlayName: "test-overlay-4",
fileName: "test.txt",
chownSpec: "bad:bad",
wantErr: true,
system: false,
checkUser: false,
checkGroup: false,
},
}
warewulfd.SetNoDaemon()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
env := testenv.New(t)
conf := env.Configure()
// Setup overlay
var overlayDir string
if tt.system {
overlayDir = filepath.Join(conf.Paths.DistributionOverlaydir(), tt.overlayName)
} else {
overlayDir = filepath.Join(conf.Paths.WWOverlaydir, tt.overlayName)
}
err := os.MkdirAll(overlayDir, 0755)
assert.NoError(t, err)
filePath := filepath.Join(overlayDir, tt.fileName)
f, err := os.Create(filePath)
assert.NoError(t, err)
f.Close()
// get initial owner
stat, err := os.Stat(filePath)
assert.NoError(t, err)
sysStat, ok := stat.Sys().(*syscall.Stat_t)
assert.True(t, ok)
startUID := int(sysStat.Uid)
startGID := int(sysStat.Gid)
baseCmd := GetCommand()
baseCmd.SetArgs([]string{tt.overlayName, tt.fileName, tt.chownSpec})
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)
}
// Check ownership
// for system overlays, the file is copied to a site overlay
if tt.system {
overlayDir = filepath.Join(conf.Paths.WWOverlaydir, tt.overlayName)
filePath = filepath.Join(overlayDir, tt.fileName)
}
stat, err = os.Stat(filePath)
assert.NoError(t, err)
sysStat, ok = stat.Sys().(*syscall.Stat_t)
assert.True(t, ok)
endUID := int(sysStat.Uid)
endGID := int(sysStat.Gid)
if !tt.wantErr {
if tt.checkUser {
u, err := strconv.Atoi(currentUser)
assert.NoError(t, err)
assert.Equal(t, u, endUID)
} else {
assert.Equal(t, startUID, endUID)
}
if tt.checkGroup {
g, err := strconv.Atoi(currentGroup)
assert.NoError(t, err)
assert.Equal(t, g, endGID)
} else {
assert.Equal(t, startGID, endGID)
}
} else {
assert.Equal(t, startUID, endUID)
assert.Equal(t, startGID, endGID)
}
})
}
}

View File

@@ -6,5 +6,6 @@ import (
)
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
return overlay.GetSiteOverlay(args[0]).Create()
_, err = overlay.Create(args[0])
return err
}

View File

@@ -0,0 +1,63 @@
package create
import (
"bytes"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/warewulf/warewulf/internal/pkg/testenv"
"github.com/warewulf/warewulf/internal/pkg/warewulfd"
)
func Test_OverlayCreate(t *testing.T) {
tests := []struct {
name string
overlayName string
setup func(t *testing.T, overlayDir string)
wantErr bool
}{
{
name: "simple create",
overlayName: "test-overlay",
setup: func(t *testing.T, overlayDir string) {},
wantErr: false,
},
{
name: "create existing overlay",
overlayName: "test-overlay-exists",
setup: func(t *testing.T, overlayDir string) {
err := os.MkdirAll(overlayDir, 0755)
assert.NoError(t, err)
},
wantErr: true,
},
}
warewulfd.SetNoDaemon()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
env := testenv.New(t)
conf := env.Configure()
overlayDir := filepath.Join(conf.Paths.WWOverlaydir, tt.overlayName)
tt.setup(t, overlayDir)
baseCmd := GetCommand()
baseCmd.SetArgs([]string{tt.overlayName})
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)
_, err := os.Stat(overlayDir)
assert.NoError(t, err, "overlay directory should have been created")
}
})
}
}

View File

@@ -1,81 +1,25 @@
package delete
import (
"fmt"
"os"
"path"
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/overlay"
"github.com/warewulf/warewulf/internal/pkg/util"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func CobraRunE(cmd *cobra.Command, args []string) error {
var fileName string
overlayName := args[0]
fileName := ""
if len(args) == 2 {
fileName = args[1]
}
overlay_ := overlay.GetOverlay(overlayName)
if overlay_.IsDistributionOverlay() {
return fmt.Errorf("distribution overlay can't deleted")
}
if !overlay_.Exists() {
return fmt.Errorf("overlay does not exist: %s", overlayName)
myOverlay, err := overlay.Get(args[0])
if err != nil {
return err
}
if fileName == "" {
if Force {
err := os.RemoveAll(overlay_.Path())
if err != nil {
return fmt.Errorf("failed deleting overlay: %w", err)
}
return myOverlay.Delete(Force)
} else {
err := os.Remove(overlay_.Path())
if err != nil {
return fmt.Errorf("failed deleting overlay: %w", err)
return myOverlay.DeleteFile(fileName, Force, Parents)
}
}
wwlog.Info("Deleted overlay: %s\n", args[0])
} else {
removePath := overlay_.File(fileName)
if !(util.IsDir(removePath) || util.IsFile(removePath)) {
return fmt.Errorf("path to remove doesn't exist in overlay: %s", removePath)
}
if Force {
err := os.RemoveAll(removePath)
if err != nil {
return fmt.Errorf("failed deleting file from overlay: %s:%s", overlayName, removePath)
}
} else {
err := os.Remove(removePath)
if err != nil {
return fmt.Errorf("failed deleting overlay: %s:%s", overlayName, removePath)
}
}
if Parents {
// Cleanup any empty directories left behind...
i := path.Dir(removePath)
for i != overlay_.Rootfs() {
wwlog.Debug("Evaluating directory to remove: %s", i)
err := os.Remove(i)
if err != nil {
break
}
wwlog.Verbose("Removed empty directory: %s", i)
i = path.Dir(i)
}
}
}
return nil
}

View File

@@ -0,0 +1,141 @@
package delete
import (
"bytes"
"os"
"path/filepath"
"testing"
"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_OverlayDelete(t *testing.T) {
tests := []struct {
name string
overlayName string
fileName string
setup func(t *testing.T, conf *config.WarewulfYaml, overlayName string, fileName string)
wantErr bool
verify func(t *testing.T, conf *config.WarewulfYaml, overlayName string, fileName string)
}{
{
name: "delete site overlay",
overlayName: "test-overlay",
setup: func(t *testing.T, conf *config.WarewulfYaml, overlayName string, fileName string) {
siteOverlayDir := filepath.Join(conf.Paths.SiteOverlaydir(), overlayName)
err := os.MkdirAll(siteOverlayDir, 0755)
assert.NoError(t, err)
},
wantErr: false,
verify: func(t *testing.T, conf *config.WarewulfYaml, overlayName string, fileName string) {
siteOverlayDir := filepath.Join(conf.Paths.SiteOverlaydir(), overlayName)
_, err := os.Stat(siteOverlayDir)
assert.True(t, os.IsNotExist(err), "site overlay directory should have been deleted")
},
},
{
name: "delete file in site overlay",
overlayName: "test-overlay-file",
fileName: "test.txt",
setup: func(t *testing.T, conf *config.WarewulfYaml, overlayName string, fileName string) {
siteOverlayDir := filepath.Join(conf.Paths.SiteOverlaydir(), overlayName)
err := os.MkdirAll(siteOverlayDir, 0755)
assert.NoError(t, err)
err = os.WriteFile(filepath.Join(siteOverlayDir, fileName), []byte("test"), 0644)
assert.NoError(t, err)
},
wantErr: false,
verify: func(t *testing.T, conf *config.WarewulfYaml, overlayName string, fileName string) {
siteOverlayDir := filepath.Join(conf.Paths.SiteOverlaydir(), overlayName)
_, err := os.Stat(filepath.Join(siteOverlayDir, fileName))
assert.True(t, os.IsNotExist(err), "file in site overlay should have been deleted")
_, err = os.Stat(siteOverlayDir)
assert.NoError(t, err, "site overlay directory should still exist")
},
},
{
name: "delete non-existent overlay",
overlayName: "non-existent-overlay",
setup: func(t *testing.T, conf *config.WarewulfYaml, overlayName string, fileName string) {},
wantErr: true,
verify: func(t *testing.T, conf *config.WarewulfYaml, overlayName string, fileName string) {},
},
{
name: "delete distribution overlay",
overlayName: "dist-overlay",
setup: func(t *testing.T, conf *config.WarewulfYaml, overlayName string, fileName string) {
distOverlayDir := filepath.Join(conf.Paths.DistributionOverlaydir(), overlayName)
err := os.MkdirAll(distOverlayDir, 0755)
assert.NoError(t, err)
},
wantErr: true,
verify: func(t *testing.T, conf *config.WarewulfYaml, overlayName string, fileName string) {
distOverlayDir := filepath.Join(conf.Paths.DistributionOverlaydir(), overlayName)
_, err := os.Stat(distOverlayDir)
assert.NoError(t, err, "distribution overlay directory should not have been deleted")
},
},
{
name: "delete file in distribution overlay",
overlayName: "dist-overlay-file",
fileName: "test.txt",
setup: func(t *testing.T, conf *config.WarewulfYaml, overlayName string, fileName string) {
distOverlayDir := filepath.Join(conf.Paths.DistributionOverlaydir(), overlayName)
err := os.MkdirAll(distOverlayDir, 0755)
assert.NoError(t, err)
err = os.WriteFile(filepath.Join(distOverlayDir, fileName), []byte("test"), 0644)
assert.NoError(t, err)
},
// This will trigger a copy-on-write. A site overlay will be created.
wantErr: false,
verify: func(t *testing.T, conf *config.WarewulfYaml, overlayName string, fileName string) {
distOverlayDir := filepath.Join(conf.Paths.DistributionOverlaydir(), overlayName)
_, err := os.Stat(filepath.Join(distOverlayDir, fileName))
assert.NoError(t, err, "file in distribution overlay should still exist")
siteOverlayDir := filepath.Join(conf.Paths.SiteOverlaydir(), overlayName)
_, err = os.Stat(siteOverlayDir)
assert.NoError(t, err, "site overlay should have been created")
_, err = os.Stat(filepath.Join(siteOverlayDir, fileName))
assert.True(t, os.IsNotExist(err), "file should not exist in the new site overlay")
},
},
}
warewulfd.SetNoDaemon()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
env := testenv.New(t)
defer env.RemoveAll()
conf := env.Configure()
tt.setup(t, conf, tt.overlayName, tt.fileName)
var args []string
if tt.fileName != "" {
args = []string{tt.overlayName, tt.fileName, "--force"}
} else {
args = []string{tt.overlayName, "--force"}
}
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)
}
tt.verify(t, conf, tt.overlayName, tt.fileName)
})
}
}

View File

@@ -35,19 +35,19 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
overlayName := args[0]
fileName := args[1]
overlay_ := overlay.GetOverlay(overlayName)
if !overlay_.Exists() {
return fmt.Errorf("overlay does not exist: %s", overlayName)
myOverlay, err := overlay.Get(overlayName)
if err != nil {
return err
}
overlayFile := overlay_.File(fileName)
overlayFile := myOverlay.File(fileName)
wwlog.Debug("Will edit overlay file: %s", overlayFile)
overlayFileDir := path.Dir(overlayFile)
if !(util.IsDir(overlayFileDir) || CreateDirs) {
return fmt.Errorf("%s does not exist. Use '--parents' option to create automatically", overlayFileDir)
}
tempFile, tempFileErr := os.CreateTemp(overlay_.Path(), "ww-overlay-edit-")
tempFile, tempFileErr := os.CreateTemp(myOverlay.Path(), "ww-overlay-edit-")
if tempFileErr != nil {
return fmt.Errorf("unable to create temporary file for editing: %s", tempFileErr)
}
@@ -103,15 +103,14 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
return nil
}
if !overlay_.IsSiteOverlay() {
overlay_, err = overlay_.CloneSiteOverlay()
if !myOverlay.IsSiteOverlay() {
myOverlay, err = myOverlay.CloneToSite()
if err != nil {
return err
}
}
// re-generate because overlay_ may have changed
overlayFile = overlay_.File(fileName)
overlayFile = myOverlay.File(fileName)
overlayFileDir = path.Dir(overlayFile)
}
if CreateDirs {
if err := os.MkdirAll(overlayFileDir, 0755); err != nil {

View File

@@ -0,0 +1,217 @@
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)
}
})
}
}

View File

@@ -5,10 +5,8 @@ import (
"os"
"path"
"path/filepath"
"runtime"
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/overlay"
"github.com/warewulf/warewulf/internal/pkg/util"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
@@ -17,7 +15,6 @@ import (
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
var dest string
overlayName := args[0]
source := args[1]
if len(args) == 3 {
@@ -25,17 +22,15 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
} else {
dest = source
}
wwlog.Verbose("Copying '%s' into overlay '%s:%s'", source, overlayName, dest)
overlay_ := overlay.GetOverlay(overlayName)
if !overlay_.IsSiteOverlay() {
overlay_, err = overlay_.CloneSiteOverlay()
overlay_, err := overlay.Get(args[0])
if err != nil {
return err
}
if !overlay_.IsSiteOverlay() {
overlay_, err = overlay_.CloneToSite()
if err != nil {
return err
}
if !overlay_.Exists() {
return fmt.Errorf("overlay does not exist: %s", overlayName)
}
if util.IsDir(overlay_.File(dest)) {
@@ -43,7 +38,7 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
}
if !OverwriteFile && util.IsFile(overlay_.File(dest)) {
return fmt.Errorf("a file with that name already exists in the overlay: %s", overlayName)
return fmt.Errorf("a file with that name already exists in the overlay")
}
if CreateDirs {
@@ -68,33 +63,5 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
return fmt.Errorf("could not copy file into overlay: %w", err)
}
if !NoOverlayUpdate {
n, err := node.New()
if err != nil {
return fmt.Errorf("could not open node configuration: %s", err)
}
nodes, err := n.FindAllNodes()
if err != nil {
return fmt.Errorf("could not get node list: %s", err)
}
var updateNodes []node.Node
for _, node := range nodes {
if util.InSlice(node.SystemOverlay, overlayName) {
updateNodes = append(updateNodes, node)
} else if util.InSlice(node.RuntimeOverlay, overlayName) {
updateNodes = append(updateNodes, node)
}
}
workers := Workers
if workers <= 0 {
workers = runtime.NumCPU()
}
return overlay.BuildSpecificOverlays(updateNodes, nodes, []string{overlayName}, workers)
}
return nil
}

View File

@@ -77,7 +77,7 @@ nodes: {}
t.FailNow()
}
baseCmd.SetArgs([]string{"-p", "-n", "test", file.Name()})
baseCmd.SetArgs([]string{"-p", "test", file.Name()})
baseCmd.SetOut(nil)
baseCmd.SetErr(nil)
err = baseCmd.Execute()

View File

@@ -25,14 +25,12 @@ var (
},
}
OverwriteFile bool
NoOverlayUpdate bool
CreateDirs bool
Workers int
)
func init() {
baseCmd.PersistentFlags().BoolVarP(&OverwriteFile, "overwrite", "o", false, "Overwrite file if exists")
baseCmd.PersistentFlags().BoolVarP(&NoOverlayUpdate, "noupdate", "n", false, "Don't update overlays")
baseCmd.PersistentFlags().BoolVarP(&CreateDirs, "parents", "p", false, "Create any necessary parent directories")
baseCmd.PersistentFlags().IntVar(&Workers, "workers", 0, "The number of parallel workers building overlays (<=0 indicates 1 worker per CPU)")
}

View File

@@ -2,6 +2,7 @@ package list
import (
"os"
"strconv"
"syscall"
"github.com/spf13/cobra"
@@ -11,7 +12,13 @@ import (
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func CobraRunE(cmd *cobra.Command, args []string) error {
/*
RunE needs a function of type func(*cobraCommand,[]string) err, but
in order to avoid global variables which mess up testing a function of
the required type is returned
*/
func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
var overlays []string
if len(args) > 0 {
@@ -21,52 +28,64 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
}
t := table.New(cmd.OutOrStdout())
if ListLong {
t.AddHeader("PERM MODE", "UID", "GID", "OVERLAY", "FILE PATH", "SITE")
locationStr := "SITE"
if vars.ShowPath {
locationStr = "PATH"
}
if vars.ListLong {
t.AddHeader("PERM MODE", "UID", "GID", "OVERLAY", "FILE PATH", locationStr)
} else {
t.AddHeader("OVERLAY NAME", "FILES/DIRS", "SITE")
t.AddHeader("OVERLAY NAME", "FILES/DIRS", locationStr)
}
for _, name := range overlays {
overlay_ := overlay.GetOverlay(name)
overlay_, err := overlay.Get(name)
if !overlay_.Exists() {
wwlog.Error("system/%s (path not found:%s)", name, overlay_.Rootfs())
if err != nil {
wwlog.Error("%s:%s", name, err)
continue
}
files := util.FindFiles(overlay_.Rootfs())
wwlog.Debug("Iterating overlay rootfs: %s", overlay_.Rootfs())
if ListLong {
if vars.ListLong {
for file := range files {
s, err := os.Stat(overlay_.File(files[file]))
if err != nil {
wwlog.Warn("%s: %s: %s", name, files[file], err)
continue
}
fileMode := s.Mode()
perms := fileMode & os.ModePerm
sys := s.Sys()
t.AddLine(perms, sys.(*syscall.Stat_t).Uid, sys.(*syscall.Stat_t).Gid, name, files[file], overlay_.IsSiteOverlay())
locLine := strconv.FormatBool(overlay_.IsSiteOverlay())
if vars.ShowPath {
locLine = overlay_.Path()
}
} else if ListContents {
t.AddLine(perms, sys.(*syscall.Stat_t).Uid, sys.(*syscall.Stat_t).Gid, name, files[file], locLine)
}
} else {
locLine := strconv.FormatBool(overlay_.IsSiteOverlay())
if vars.ShowPath {
locLine = overlay_.Path()
}
if vars.ListContents {
var fileCount int
for file := range files {
t.AddLine(name, files[file], overlay_.IsSiteOverlay())
t.AddLine(name, files[file], locLine)
fileCount++
}
if fileCount == 0 {
t.AddLine(name, 0, overlay_.IsSiteOverlay())
t.AddLine(name, 0, locLine)
}
} else {
t.AddLine(name, len(files), overlay_.IsSiteOverlay())
t.AddLine(name, len(files), locLine)
}
}
}
t.Print()
return nil
}
}

View File

@@ -29,8 +29,8 @@ func Test_Overlay_List(t *testing.T) {
assert.Contains(t, buf.String(), "testoverlay")
})
t.Run("overlay list all", func(t *testing.T) {
baseCmd.SetArgs([]string{"-a"})
baseCmd := GetCommand()
baseCmd.SetArgs([]string{"-a"})
buf := new(bytes.Buffer)
baseCmd.SetOut(buf)
baseCmd.SetErr(buf)
@@ -40,8 +40,8 @@ func Test_Overlay_List(t *testing.T) {
assert.Contains(t, buf.String(), "email.ww")
})
t.Run("overlay list long", func(t *testing.T) {
baseCmd.SetArgs([]string{"--long"})
baseCmd := GetCommand()
baseCmd.SetArgs([]string{"--long"})
buf := new(bytes.Buffer)
baseCmd.SetOut(buf)
baseCmd.SetErr(buf)
@@ -50,4 +50,16 @@ func Test_Overlay_List(t *testing.T) {
assert.NoError(t, err)
assert.Contains(t, buf.String(), "email.ww")
})
t.Run("overlay list path", func(t *testing.T) {
baseCmd := GetCommand()
baseCmd.SetArgs([]string{"--path"})
buf := new(bytes.Buffer)
baseCmd.SetOut(buf)
baseCmd.SetErr(buf)
wwlog.SetLogWriter(buf)
err := baseCmd.Execute()
assert.NoError(t, err)
assert.Contains(t, buf.String(), env.BaseDir)
})
}

View File

@@ -5,28 +5,29 @@ import (
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
)
var (
baseCmd = &cobra.Command{
// Holds the variables which are needed in CobraRunE
type variables struct {
ListContents bool
ListLong bool
ShowPath bool
}
// GetCommand returns the root cobra.Command for the application.
func GetCommand() *cobra.Command {
vars := variables{}
baseCmd := &cobra.Command{
DisableFlagsInUseLine: true,
Use: "list [OPTIONS] OVERLAY_NAME",
Short: "List Warewulf Overlays and files",
Long: "This command displays information about all Warewulf overlays or the specified\nOVERLAY_NAME. It also supports listing overlay content information.",
RunE: CobraRunE,
RunE: CobraRunE(&vars),
Aliases: []string{"ls"},
ValidArgsFunction: completions.Overlays,
Args: cobra.ArbitraryArgs,
}
ListContents bool
ListLong bool
)
baseCmd.PersistentFlags().BoolVarP(&vars.ListContents, "all", "a", false, "List the contents of overlays")
baseCmd.PersistentFlags().BoolVarP(&vars.ListLong, "long", "l", false, "List 'long' of all overlay contents")
baseCmd.PersistentFlags().BoolVarP(&vars.ShowPath, "path", "p", false, "Show the absolute path to the overlay")
func init() {
baseCmd.PersistentFlags().BoolVarP(&ListContents, "all", "a", false, "List the contents of overlays")
baseCmd.PersistentFlags().BoolVarP(&ListLong, "long", "l", false, "List 'long' of all overlay contents")
}
// GetRootCommand returns the root cobra.Command for the application.
func GetCommand() *cobra.Command {
return baseCmd
}

View File

@@ -1,36 +1,15 @@
package mkdir
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/overlay"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
overlayName := args[0]
dirName := args[1]
overlay_ := overlay.GetOverlay(overlayName)
if !overlay_.IsSiteOverlay() {
overlay_, err = overlay_.CloneSiteOverlay()
myOverlay, err := overlay.Get(args[0])
if err != nil {
return err
}
}
if !overlay_.Exists() {
return fmt.Errorf("overlay does not exist: %s", overlayName)
}
overlayDir := overlay_.File(dirName)
wwlog.Debug("Will create directory in overlay: %s:%s", overlayName, dirName)
err = os.MkdirAll(overlayDir, os.FileMode(PermMode))
if err != nil {
return fmt.Errorf("could not create directory: %s", overlayDir)
}
return nil
return myOverlay.Mkdir(args[1], PermMode)
}

View File

@@ -0,0 +1,89 @@
package mkdir
import (
"bytes"
"io/fs"
"os"
"path/filepath"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/warewulf/warewulf/internal/pkg/testenv"
"github.com/warewulf/warewulf/internal/pkg/warewulfd"
)
func Test_OverlayMkdir(t *testing.T) {
tests := []struct {
name string
overlayName string
dirName string
perm string
wantErr bool
system bool
}{
{
name: "simple mkdir",
overlayName: "test-overlay",
dirName: "testdir",
perm: "0755",
wantErr: false,
system: false,
},
{
name: "system overlay mkdir",
overlayName: "wwinit", // A known system overlay
dirName: "init.d",
perm: "0700",
wantErr: false,
system: true,
},
}
warewulfd.SetNoDaemon()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
env := testenv.New(t)
conf := env.Configure()
// Setup overlay
var overlayDir string
if tt.system {
overlayDir = filepath.Join(conf.Paths.DistributionOverlaydir(), tt.overlayName)
err := os.MkdirAll(overlayDir, 0755)
assert.NoError(t, err)
} else {
overlayDir = filepath.Join(conf.Paths.WWOverlaydir, tt.overlayName)
err := os.MkdirAll(overlayDir, 0755)
assert.NoError(t, err)
}
baseCmd := GetCommand()
baseCmd.SetArgs([]string{"-m", tt.perm, tt.overlayName, tt.dirName})
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)
}
// Check permissions
// for system overlays, the file is copied to a site overlay
if tt.system {
overlayDir = filepath.Join(conf.Paths.WWOverlaydir, tt.overlayName)
}
dirPath := filepath.Join(overlayDir, tt.dirName)
stat, err := os.Stat(dirPath)
assert.NoError(t, err)
if !tt.wantErr {
mode, err := strconv.ParseUint(tt.perm, 8, 32)
assert.NoError(t, err)
assert.Equal(t, fs.FileMode(mode), stat.Mode().Perm())
}
})
}
}

View File

@@ -1,21 +0,0 @@
package mkdir
import (
"testing"
)
// TestArgsOverlayMkdir is a regression test for 248.
// One argument should fail, two should succeed.
func TestArgsOverlayMkdir(t *testing.T) {
command := GetCommand()
err := command.Args(command, []string{"overlay_name"})
if err == nil {
t.Errorf("one argument to overlay mkdir should fail")
}
err = command.Args(command, []string{"overlay_name", "directory"})
if err != nil {
t.Errorf("two arguments to overlay mkdir should succeed")
}
}

View File

@@ -20,9 +20,9 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
overlayName := args[0]
fileName := args[1]
overlay_ := overlay.GetOverlay(overlayName)
if !overlay_.Exists() {
return fmt.Errorf("overlay does not exist: %s", overlayName)
overlay_, err := overlay.Get(overlayName)
if err != nil {
return err
}
overlayFile := overlay_.File(fileName)

View File

@@ -14,7 +14,10 @@ import (
Creates '/etc/hosts' from the host template.
*/
func Hostfile() (err error) {
overlay_ := overlay.GetOverlay("host")
overlay_, err := overlay.Get("host")
if err != nil {
return err
}
hostTemplate := path.Join(overlay_.Rootfs(), "/etc/hosts.ww")
if !(util.IsFile(hostTemplate)) {
return fmt.Errorf("'the overlay template '/etc/hosts.ww' does not exists in 'host' overlay")

View File

@@ -14,60 +14,56 @@ import (
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
// GetOverlay returns the filesystem path of an overlay identified by its name,
// along with a boolean indicating whether the returned overlayPath corresponds
// to a site-specific overlay.
func GetOverlay(name string) (overlay Overlay) {
overlay = GetSiteOverlay(name)
// Get returns the filesystem path of an overlay identified by its name,
func Get(name string) (overlay Overlay, err error) {
overlay = getSiteOverlay(name)
if overlay.Exists() {
return overlay
return overlay, nil
}
overlay = GetDistributionOverlay(name)
overlay = getDistributionOverlay(name)
if overlay.Exists() {
return overlay
return overlay, nil
}
return GetSiteOverlay(name)
}
// GetDistributionOverlay returns the filesystem path of a distribution overlay
// identified by the given name.
func GetDistributionOverlay(name string) (overlay Overlay) {
return getOverlay(config.Get().Paths.DistributionOverlaydir(), name)
}
// GetSiteOverlay returns the filesystem path of a site-specific overlay
// identified by the given name.
func GetSiteOverlay(name string) (overlay Overlay) {
return getOverlay(config.Get().Paths.SiteOverlaydir(), name)
}
// getOverlay constructs an overlay based on the given overlay directory and
// overlay name. The overlay does not necessarily exist.
func getOverlay(overlaydir, name string) (overlay Overlay) {
return Overlay(path.Join(overlaydir, name))
return "", ErrDoesNotExist
}
// Create creates a new overlay directory for the given overlay
//
// Returns an error if the overlay already exists or if directory creation fails.
func (overlay Overlay) Create() error {
if util.IsDir(overlay.Path()) {
return fmt.Errorf("overlay already exists: %s", overlay)
func Create(name string) (overlay Overlay, err error) {
overlay = getSiteOverlay(name)
if overlay.Exists() {
return overlay, fmt.Errorf("overlay already exists: %s", name)
}
return os.MkdirAll(overlay.Rootfs(), 0o755)
wwlog.Verbose("created site overlay under: %s", overlay.Path())
return overlay, os.MkdirAll(path.Join(overlay.Path(), "rootfs"), 0o755)
}
// Creates a site overlay from an existing distribution overlay.
// GetDistributionOverlay returns a distribution overlay identified by the given
// name.
func getDistributionOverlay(name string) Overlay {
return Overlay(path.Join(config.Get().Paths.DistributionOverlaydir(), name))
}
// getSiteOverlay returns a site-specific overlay identified by the given name.
func getSiteOverlay(name string) (overlay Overlay) {
return Overlay(path.Join(config.Get().Paths.SiteOverlaydir(), name))
}
// CloneToSite creates a site overlay from an existing distribution overlay.
//
// If the distribution overlay doesn't exist, return an error.
func (overlay Overlay) CloneSiteOverlay() (siteOverlay Overlay, err error) {
siteOverlay = GetSiteOverlay(overlay.Name())
if !util.IsDir(overlay.Path()) {
func (overlay Overlay) CloneToSite() (siteOverlay Overlay, err error) {
wwlog.Verbose("Cloning to site overlay: %s", overlay.Name())
siteOverlay = getSiteOverlay(overlay.Name())
if siteOverlay.Exists() {
return siteOverlay, nil
}
if !overlay.Exists() {
return siteOverlay, fmt.Errorf("source overlay does not exist: %s", overlay.Name())
}
if siteOverlay.Exists() {
return siteOverlay, fmt.Errorf("site overlay already exists: %s", siteOverlay.Name())
}
if !util.IsDir(filepath.Dir(siteOverlay.Path())) {
if err := os.MkdirAll(filepath.Dir(siteOverlay.Path()), 0o755); err != nil {
return siteOverlay, err
@@ -77,7 +73,7 @@ func (overlay Overlay) CloneSiteOverlay() (siteOverlay Overlay, err error) {
return siteOverlay, err
}
// OverlayImage returns the full path to an overlay image based on the
// Image returns the full path to an overlay image based on the
// context and the overlays contained in it.
//
// If a context is provided, the image file name is based on that
@@ -88,7 +84,7 @@ func (overlay Overlay) CloneSiteOverlay() (siteOverlay Overlay, err error) {
//
// If the context is empty and no overlays are specified, the empty
// string is returned.
func OverlayImage(nodeName string, context string, overlayNames []string) string {
func Image(nodeName string, context string, overlayNames []string) string {
var name string
if context != "" {
if len(overlayNames) > 0 {
@@ -106,8 +102,8 @@ func OverlayImage(nodeName string, context string, overlayNames []string) string
return path.Join(config.Get().Paths.OverlayProvisiondir(), nodeName, name)
}
func ClearOverlayImage(nodeName string, context string, overlayNames []string) error {
imagePath := OverlayImage(nodeName, context, overlayNames)
func RemoveImage(nodeName string, context string, overlayNames []string) error {
imagePath := Image(nodeName, context, overlayNames)
if util.IsFile(imagePath) {
if err := os.Remove(imagePath); err != nil {
return fmt.Errorf("failed to remove overlay image: %w", err)

View File

@@ -30,7 +30,7 @@ func Test_OverlayImage(t *testing.T) {
conf.Paths.WWProvisiondir = "p"
for _, tt := range overlayImageTests {
t.Run(tt.description, func(t *testing.T) {
out := OverlayImage(tt.node, tt.context, tt.overlays)
out := Image(tt.node, tt.context, tt.overlays)
if tt.image != out {
t.Errorf("got %q, want %q", out, tt.image)
}

View File

@@ -100,7 +100,13 @@ func (overlay Overlay) Exists() bool {
// Returns:
// - true if the overlay is a site overlay; false otherwise.
func (overlay Overlay) IsSiteOverlay() bool {
return path.Dir(overlay.Path()) == config.Get().Paths.SiteOverlaydir()
siteDir := filepath.Clean(config.Get().Paths.SiteOverlaydir())
overlayPath := filepath.Clean(overlay.Path())
if rel, err := filepath.Rel(siteDir, overlayPath); err != nil {
return false
} else {
return !strings.HasPrefix(rel, "..")
}
}
// IsDistributionOverlay determines whether the overlay is a distribution overlay.
@@ -111,21 +117,26 @@ func (overlay Overlay) IsSiteOverlay() bool {
// Returns:
// - true if the overlay is a distribution overlay; false otherwise.
func (overlay Overlay) IsDistributionOverlay() bool {
return path.Dir(overlay.Path()) == config.Get().Paths.DistributionOverlaydir()
siteDir := filepath.Clean(config.Get().Paths.DistributionOverlaydir())
overlayPath := filepath.Clean(overlay.Path())
if rel, err := filepath.Rel(siteDir, overlayPath); err != nil {
return false
} else {
return !strings.HasPrefix(rel, "..")
}
}
func (overlay Overlay) AddFile(filePath string, content []byte, parents bool, force bool) error {
wwlog.Info("Creating file %s in overlay %s, force: %v", filePath, overlay.Name(), force)
if overlay.IsDistributionOverlay() {
siteOverlay, err := overlay.CloneSiteOverlay()
if !overlay.IsSiteOverlay() {
siteOverlay, err := overlay.CloneToSite()
if err != nil {
return fmt.Errorf("failed to clone distribution overlay '%s' to site overlay: %w", overlay.Name(), err)
}
// replace the overlay with newly created siteOverlay
overlay = siteOverlay
}
fullPath := overlay.File(filePath)
// create necessary parent directories
if parents {
@@ -145,38 +156,47 @@ func (overlay Overlay) AddFile(filePath string, content []byte, parents bool, fo
return os.WriteFile(fullPath, content, 0o644)
}
// DeleteFile deletes a file or the entire overlay directory.
// If the file belongs to a distribution overlay, it will be cloned to a site overlay
// before deletion.
func (overlay Overlay) DeleteFile(filePath string, force, cleanup bool) error {
wwlog.Info("Deleting file %s from overlay %s, force: %v, cleanup: %v", filePath, overlay.Name(), force, cleanup)
if filePath == "" {
func (overlay Overlay) Delete(force bool) (err error) {
wwlog.Info("Deleting overlay %s, force: %v", overlay.Name(), force)
if overlay.IsDistributionOverlay() {
return fmt.Errorf("cannot delete a distribution overlay: %s", overlay.Name())
}
if force {
err := os.RemoveAll(overlay.Path())
if err != nil {
return fmt.Errorf("failed to delete overlay forcely: %w", err)
}
} else {
err := os.Remove(overlay.Path())
if err != nil {
// remove rootfs at first
if err = os.Remove(overlay.Rootfs()); err != nil {
return fmt.Errorf("failed to delete overlay: %w", err)
}
if overlay.Exists() {
if err = os.Remove(overlay.Path()); err != nil {
return fmt.Errorf("failed to delete overlay: %w", err)
}
}
} else {
}
return nil
}
// DeleteFile deletes a file or the entire overlay directory.
// before deletion.
func (overlay Overlay) DeleteFile(filePath string, force, cleanup bool) (err error) {
wwlog.Info("Deleting file %s from overlay %s, force: %v, cleanup: %v", filePath, overlay.Name(), force, cleanup)
// first check if file exists
if !util.IsFile(overlay.File(filePath)) {
return fmt.Errorf("file %s does not exist in overlay %s", filePath, overlay.Name())
}
if overlay.IsDistributionOverlay() {
siteOverlay, err := overlay.CloneSiteOverlay()
siteOverlay, err := overlay.CloneToSite()
if err != nil {
return fmt.Errorf("failed to clone distribution overlay '%s' to site overlay: %w", overlay.Name(), err)
}
// replace the overlay with newly created siteOverlay
overlay = siteOverlay
}
fullPath := overlay.File(filePath)
if !util.IsFile(fullPath) {
return fmt.Errorf("file %s does not exist in overlay %s", filePath, overlay.Name())
}
if force {
if err := os.RemoveAll(fullPath); err != nil {
return fmt.Errorf("failed to delete file %s from overlay %s: %w", filePath, overlay.Name(), err)
@@ -204,10 +224,54 @@ func (overlay Overlay) DeleteFile(filePath string, force, cleanup bool) error {
i = path.Dir(i)
}
}
}
return nil
}
// chmod for the given path in the overlay
func (overlay Overlay) Chmod(path string, mode uint64) (err error) {
if !overlay.IsSiteOverlay() {
overlay, err = overlay.CloneToSite()
if err != nil {
return err
}
}
fullPath := overlay.File(path)
if !(util.IsFile(fullPath) || util.IsDir(fullPath)) {
return fmt.Errorf("file does not exist within overlay: %s:%s", overlay.Name(), fullPath)
}
return os.Chmod(fullPath, os.FileMode(mode))
}
// chown file or dir in overlay
func (overlay Overlay) Chown(path string, uid, gid int) (err error) {
if !overlay.IsSiteOverlay() {
overlay, err = overlay.CloneToSite()
if err != nil {
return err
}
}
fullPath := overlay.File(path)
if !(util.IsFile(fullPath) || util.IsDir(fullPath)) {
return fmt.Errorf("file does not exist within overlay: %s:%s", overlay.Name(), fullPath)
}
return os.Chown(fullPath, uid, gid)
}
func (overlay Overlay) Mkdir(path string, mode int32) (err error) {
if !overlay.IsSiteOverlay() {
overlay, err = overlay.CloneToSite()
if err != nil {
return err
}
}
fullPath := overlay.File(path)
if util.IsFile(fullPath) || util.IsDir(fullPath) {
wwlog.Warn("path already exists, overwriting permissions: %s:%s", overlay.Name(), fullPath)
}
return os.MkdirAll(fullPath, os.FileMode(mode))
}
func BuildAllOverlays(nodes []node.Node, allNodes []node.Node, workerCount int) error {
nodeChan := make(chan node.Node, len(nodes))
errChan := make(chan error, len(nodes)*2)
@@ -297,13 +361,16 @@ func BuildHostOverlay() error {
hostname, _ := os.Hostname()
hostData := node.NewNode(hostname)
wwlog.Info("Building overlay for %s: host", hostname)
hostdir := GetOverlay("host").Rootfs()
stats, err := os.Stat(hostdir)
hostdir, err := Get("host")
if err != nil {
return err
}
stats, err := os.Stat(hostdir.Rootfs())
if err != nil {
return fmt.Errorf("could not build host overlay: %w ", err)
}
if !(stats.Mode() == os.FileMode(0o750|os.ModeDir) || stats.Mode() == os.FileMode(0o700|os.ModeDir)) {
wwlog.SecWarn("Permissions of host overlay dir %s are %s (750 is considered as secure)", hostdir, stats.Mode())
wwlog.SecWarn("Permissions of host overlay dir %s are %s (750 is considered as secure)", hostdir.Rootfs(), stats.Mode())
}
registry, err := node.New()
if err != nil {
@@ -360,7 +427,7 @@ func BuildOverlay(nodeConf node.Node, allNodes []node.Node, context string, over
} else {
name = fmt.Sprintf("%s overlay/%v", nodeConf.Id(), overlayNames)
}
overlayImage := OverlayImage(nodeConf.Id(), context, overlayNames)
overlayImage := Image(nodeConf.Id(), context, overlayNames)
overlayImageDir := path.Dir(overlayImage)
err := os.MkdirAll(overlayImageDir, 0o750)
@@ -424,19 +491,19 @@ func BuildOverlayIndir(nodeData node.Node, allNodes []node.Node, overlayNames []
wwlog.Verbose("Processing node/overlays: %s/%s", nodeData.Id(), strings.Join(overlayNames, ","))
for _, overlayName := range overlayNames {
wwlog.Verbose("Building overlay %s for node %s in %s", overlayName, nodeData.Id(), outputDir)
overlayRootfs := GetOverlay(overlayName).Rootfs()
if !util.IsDir(overlayRootfs) {
return fmt.Errorf("overlay %s: %w", overlayName, ErrDoesNotExist)
overlayRootfs, err := Get(overlayName)
if err != nil {
return err
}
wwlog.Debug("Walking the overlay structure: %s", overlayRootfs)
err := filepath.Walk(overlayRootfs, func(walkPath string, info os.FileInfo, err error) error {
wwlog.Debug("Walking the overlay structure: %s", overlayRootfs.Rootfs())
err = filepath.Walk(overlayRootfs.Rootfs(), func(walkPath string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("error for %s: %w", walkPath, err)
}
wwlog.Debug("Found overlay file: %s", walkPath)
relPath, relErr := filepath.Rel(overlayRootfs, walkPath)
relPath, relErr := filepath.Rel(overlayRootfs.Rootfs(), walkPath)
if relErr != nil {
wwlog.Warn("Error computing relative path for %s: %v", walkPath, relErr)
return relErr
@@ -669,15 +736,10 @@ func ScanLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
}
// Get all the files as a string slice for a given overlay
func OverlayGetFiles(name string) (files []string, err error) {
baseDir := GetOverlay(name).Rootfs()
if !util.IsDir(baseDir) {
err = fmt.Errorf("overlay %s doesn't exist", name)
return
}
err = filepath.Walk(baseDir, func(path string, info fs.FileInfo, err error) error {
func (overlay Overlay) GetFiles() (files []string, err error) {
err = filepath.Walk(overlay.Rootfs(), func(path string, info fs.FileInfo, err error) error {
if util.IsFile(path) {
files = append(files, strings.TrimPrefix(path, baseDir))
files = append(files, strings.TrimPrefix(path, overlay.Rootfs()))
}
return nil
})

View File

@@ -139,7 +139,9 @@ func Test_OverlayMethods(t *testing.T) {
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
overlay := GetOverlay(tt.name)
overlay, err := Get(tt.name)
if tt.exists {
assert.NoError(t, err)
assert.Equal(t, tt.name, overlay.Name())
assert.Equal(t, env.GetPath(tt.path), overlay.Path())
assert.Equal(t, env.GetPath(tt.rootfs), overlay.Rootfs())
@@ -152,6 +154,7 @@ func Test_OverlayMethods(t *testing.T) {
assert.Equal(t, tt.exists, overlay.Exists())
assert.Equal(t, tt.isSite, overlay.IsSiteOverlay())
assert.Equal(t, tt.isDist, overlay.IsDistributionOverlay())
}
})
}
}
@@ -719,8 +722,8 @@ func Test_CreateOverlayFile(t *testing.T) {
content []byte
force bool
}{
{"create file", "test", "newfile.ww", []byte("new file"), false},
{"overwrite existing file", "test", "existingfile.ww", []byte("overwrite file"), true},
{"create file", "test", "test.ww", []byte("new file"), false},
{"overwrite existing file", "test", "test.ww", []byte("overwrite file"), true},
}
conf := warewulfconf.Get()
@@ -728,11 +731,15 @@ func Test_CreateOverlayFile(t *testing.T) {
assert.NoError(t, overlayDirErr)
defer os.RemoveAll(overlayDir)
conf.Paths.WWOverlaydir = overlayDir
conf.Paths.Datadir = "/dev/null"
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
newOverlay := GetSiteOverlay(tt.overlayName)
err := newOverlay.AddFile(tt.filePath, tt.content, true, tt.force)
newOverlay, err := Get(tt.overlayName)
if err != nil {
newOverlay, err = Create(tt.overlayName)
}
assert.NoError(t, err)
err = newOverlay.AddFile(tt.filePath, tt.content, true, tt.force)
assert.NoError(t, err)
newFile := newOverlay.File(tt.filePath)

View File

@@ -70,13 +70,13 @@ func getNodeOverlayInfo() usecase.Interactor {
Overlays: node_.RuntimeOverlay,
},
}
sysImagePath := overlay.OverlayImage(input.ID, "system", node_.SystemOverlay)
sysImagePath := overlay.Image(input.ID, "system", node_.SystemOverlay)
if sysImageStat, err := os.Stat(sysImagePath); err == nil {
mtime := sysImageStat.ModTime()
out.SystemOverlay.MTime = &mtime
}
runtimeImagePath := overlay.OverlayImage(input.ID, "runtime", node_.RuntimeOverlay)
runtimeImagePath := overlay.Image(input.ID, "runtime", node_.RuntimeOverlay)
if runtimeImageStat, err := os.Stat(runtimeImagePath); err == nil {
mtime := runtimeImageStat.ModTime()
out.RuntimeOverlay.MTime = &mtime
@@ -194,12 +194,12 @@ func addNode() usecase.Interactor {
return status.Wrap(fmt.Errorf("image '%s' does not exist", input.Node.ImageName), status.InvalidArgument)
}
for _, overlay_ := range input.Node.SystemOverlay {
if !overlay.GetOverlay(overlay_).Exists() {
if _, err = overlay.Get(overlay_); err != nil {
return status.Wrap(fmt.Errorf("overlay '%s' does not exist", overlay_), status.InvalidArgument)
}
}
for _, overlay_ := range input.Node.RuntimeOverlay {
if !overlay.GetOverlay(overlay_).Exists() {
if _, err = overlay.Get(overlay_); err != nil {
return status.Wrap(fmt.Errorf("overlay '%s' does not exist", overlay_), status.InvalidArgument)
}
}
@@ -269,12 +269,12 @@ func updateNode() usecase.Interactor {
return status.Wrap(fmt.Errorf("image '%s' does not exist", input.Node.ImageName), status.InvalidArgument)
}
for _, overlay_ := range input.Node.SystemOverlay {
if !overlay.GetOverlay(overlay_).Exists() {
if _, err = overlay.Get(overlay_); err != nil {
return status.Wrap(fmt.Errorf("overlay '%s' does not exist", overlay_), status.InvalidArgument)
}
}
for _, overlay_ := range input.Node.RuntimeOverlay {
if !overlay.GetOverlay(overlay_).Exists() {
if _, err = overlay.Get(overlay_); err != nil {
return status.Wrap(fmt.Errorf("overlay '%s' does not exist", overlay_), status.InvalidArgument)
}
}

View File

@@ -24,10 +24,13 @@ type OverlayResponse struct {
func NewOverlayResponse(name string) *OverlayResponse {
o := new(OverlayResponse)
o.Files = []string{}
if files, err := overlay.OverlayGetFiles(name); err == nil {
myOverlay, err := overlay.Get(name)
if err == nil {
if files, err := myOverlay.GetFiles(); err == nil {
o.Files = files
}
o.Site = overlay.GetOverlay(name).IsSiteOverlay()
o.Site = myOverlay.IsSiteOverlay()
}
return o
}
@@ -55,7 +58,7 @@ func getOverlayByName() usecase.Interactor {
u := usecase.NewInteractor(func(ctx context.Context, input getOverlayByNameInput, output *OverlayResponse) error {
wwlog.Debug("api.getOverlayByName(Name:%v)", input.Name)
if !overlay.GetOverlay(input.Name).Exists() {
if _, err := overlay.Get(input.Name); err != nil {
return status.Wrap(fmt.Errorf("overlay not found: %v", input.Name), status.NotFound)
} else {
*output = *NewOverlayResponse(input.Name)
@@ -79,11 +82,19 @@ type OverlayFile struct {
}
func (of *OverlayFile) FullPath() string {
return path.Join(overlay.GetOverlay(of.Overlay).Rootfs(), of.Path)
if myOverlay, err := overlay.Get(of.Overlay); err != nil {
return ""
} else {
return myOverlay.File(of.Path)
}
}
func (of *OverlayFile) Exists() bool {
return overlay.GetOverlay(of.Overlay).Exists() && util.IsFile(of.FullPath())
if myOverlay, err := overlay.Get(of.Overlay); err != nil {
return false
} else {
return myOverlay.Exists() && util.IsFile(of.FullPath())
}
}
func (of *OverlayFile) readContents() (string, error) {
@@ -202,10 +213,13 @@ func createOverlay() usecase.Interactor {
u := usecase.NewInteractor(func(ctx context.Context, input createOverlayInput, output *OverlayResponse) error {
wwlog.Debug("api.createOverlay(Name:%v)", input.Name)
newOverlay := overlay.GetSiteOverlay(input.Name)
if err := newOverlay.Create(); err != nil {
newOverlay, err := overlay.Get(input.Name)
if err != nil {
newOverlay, err = overlay.Create(input.Name)
if err != nil {
return err
}
}
*output = *NewOverlayResponse(newOverlay.Name())
return nil
})
@@ -218,17 +232,21 @@ func createOverlay() usecase.Interactor {
func deleteOverlay() usecase.Interactor {
type deleteOverlayInput struct {
Name string `path:"name" required:"true" description:"Name of overlay to delete"`
Force bool `query:"force" default:"false" description:"Whether to delete a non-empty overlay, default:'false'"`
Force bool `query:"force" default:"false" description:"Whether to delete a non-empty or in-use overlay, default:'false'"`
}
u := usecase.NewInteractor(func(ctx context.Context, input deleteOverlayInput, output *OverlayResponse) error {
wwlog.Debug("api.deleteOverlay(Name:%v, Force:%v)", input.Name, input.Force)
overlay_ := overlay.GetSiteOverlay(input.Name)
if input.Force {
if err := os.RemoveAll(overlay_.Path()); err != nil {
overlay_, err := overlay.Get(input.Name)
if err != nil {
return err
}
} else {
if overlay_.IsDistributionOverlay() {
return status.Wrap(fmt.Errorf("distribution overlay: %s", overlay_.Name()), status.InvalidArgument)
}
if !input.Force {
if registry, err := node.New(); err != nil {
return err
} else {
@@ -240,15 +258,12 @@ func deleteOverlay() usecase.Interactor {
status.InvalidArgument)
}
}
if err := os.Remove(overlay_.Rootfs()); err != nil {
return status.Wrap(fmt.Errorf("failed to remove overlay rootfs: %v", err), status.Code(409))
} else {
if err := os.Remove(overlay_.Path()); err != nil {
}
if err := overlay_.Delete(input.Force); err != nil {
return status.Wrap(fmt.Errorf("failed to remove overlay: %v", err), status.Code(409))
}
}
}
*output = *NewOverlayResponse(input.Name)
output.Site = overlay_.IsSiteOverlay()
return nil
})
u.SetTitle("Delete an overlay")
@@ -274,12 +289,14 @@ func deleteOverlayFile() usecase.Interactor {
if relPath, err := url.QueryUnescape(input.Path); err != nil {
return fmt.Errorf("failed to decode path: %v: %w", input.Path, err)
} else {
overlay_ := overlay.GetOverlay(input.Name)
err := overlay_.DeleteFile(relPath, input.Force, input.Cleanup)
if err != nil {
if overlay_, err := overlay.Get(input.Name); err != nil {
return err
} else {
if err := overlay_.DeleteFile(relPath, input.Force, input.Cleanup); err != nil {
return fmt.Errorf("unable to delete overlay file %v: %v: %w", input.Name, relPath, err)
}
}
}
*output = *NewOverlayResponse(input.Name)
return nil
})
@@ -305,7 +322,12 @@ func addOverlayFile() usecase.Interactor {
if relPath, err := url.QueryUnescape(input.Path); err != nil {
return fmt.Errorf("failed to decode path: %v: %w", input.Path, err)
} else {
overlay_ := overlay.GetOverlay(input.Name)
overlay_, err := overlay.Get(input.Name)
if err != nil {
if _, err := overlay.Create(input.Name); err != nil {
return err
}
}
if err := overlay_.AddFile(relPath, []byte(input.Content), true, !(input.IfNoneMatch == "*")); err != nil {
return fmt.Errorf("unable to add overlay file %v: %v: %w", input.Name, relPath, err)
}

View File

@@ -108,7 +108,7 @@ var overlayTests = map[string]struct {
response: `{"files":null, "site":true}`,
},
"delete overlay": {
"force delete site overlay": {
initFiles: map[string]string{
"/var/lib/warewulf/overlays/test/": "",
},
@@ -117,6 +117,16 @@ var overlayTests = map[string]struct {
},
response: `{"files":[], "site":true}`,
},
"force delete distribution overlay": {
initFiles: map[string]string{
"/usr/share/warewulf/overlays/test/email.ww": sampleTemplate,
},
request: func(serverURL string) (*http.Request, error) {
return http.NewRequest(http.MethodDelete, serverURL+"/api/overlays/test?force=true", nil)
},
status: 400,
},
}
func TestOverlayAPI(t *testing.T) {
@@ -160,8 +170,10 @@ func TestOverlayAPI(t *testing.T) {
assert.NoError(t, err)
assert.NoError(t, resp.Body.Close())
if tt.response != "" {
ja := jsonassert.New(t)
ja.Assertf(string(body), tt.response) //nolint:govet // tt.response is used as a format string with special tokens
}
for _, fileName := range tt.resultFiles {
assert.DirExists(t, env.GetPath(fileName))

View File

@@ -81,12 +81,12 @@ func addProfile() usecase.Interactor {
return status.Wrap(fmt.Errorf("image '%s' does not exist", input.Profile.ImageName), status.InvalidArgument)
}
for _, overlay_ := range input.Profile.SystemOverlay {
if !overlay.GetOverlay(overlay_).Exists() {
if _, err = overlay.Get(overlay_); err != nil {
return status.Wrap(fmt.Errorf("overlay '%s' does not exist", overlay_), status.InvalidArgument)
}
}
for _, overlay_ := range input.Profile.RuntimeOverlay {
if !overlay.GetOverlay(overlay_).Exists() {
if _, err = overlay.Get(overlay_); err != nil {
return status.Wrap(fmt.Errorf("overlay '%s' does not exist", overlay_), status.InvalidArgument)
}
}
@@ -126,12 +126,12 @@ func updateProfile() usecase.Interactor {
return status.Wrap(fmt.Errorf("image '%s' does not exist", input.Profile.ImageName), status.InvalidArgument)
}
for _, overlay_ := range input.Profile.SystemOverlay {
if !overlay.GetOverlay(overlay_).Exists() {
if _, err = overlay.Get(overlay_); err != nil {
return status.Wrap(fmt.Errorf("overlay '%s' does not exist", overlay_), status.InvalidArgument)
}
}
for _, overlay_ := range input.Profile.RuntimeOverlay {
if !overlay.GetOverlay(overlay_).Exists() {
if _, err = overlay.Get(overlay_); err != nil {
return status.Wrap(fmt.Errorf("overlay '%s' does not exist", overlay_), status.InvalidArgument)
}
}

View File

@@ -99,10 +99,10 @@ func GetNodeOrSetDiscoverable(hwaddr string, autobuildOverlays bool) (node.Node,
return nodeFound, fmt.Errorf("%s (failed to reload configuration) %w", hwaddr, err)
}
if autobuildOverlays {
if err := overlay.ClearOverlayImage(nodeFound.Id(), "system", []string{}); err != nil {
if err := overlay.RemoveImage(nodeFound.Id(), "system", []string{}); err != nil {
wwlog.Warn("Failed to clear system overlay image: %s: %s", nodeFound.Id(), err)
}
if err := overlay.ClearOverlayImage(nodeFound.Id(), "runtime", []string{}); err != nil {
if err := overlay.RemoveImage(nodeFound.Id(), "runtime", []string{}); err != nil {
wwlog.Warn("Failed to clear runtime overlay image: %s: %s", nodeFound.Id(), err)
}
}

View File

@@ -25,8 +25,8 @@ func OverlaySend(w http.ResponseWriter, req *http.Request) {
return
}
o := overlay.GetOverlay(rinfo.overlay)
if !o.Exists() {
myOverlay, err := overlay.Get(rinfo.overlay)
if err != nil {
message := "overlay not found: %s"
wwlog.Error(message, rinfo.overlay)
http.Error(w, fmt.Sprintf(message, rinfo.overlay), http.StatusNoContent)
@@ -41,7 +41,7 @@ func OverlaySend(w http.ResponseWriter, req *http.Request) {
return
}
overlayFile := o.File(rinfo.path)
overlayFile := myOverlay.File(rinfo.path)
if !path.IsAbs(overlayFile) {
message := "Path %s isn't absolute"
wwlog.Denied(message, overlayFile)

View File

@@ -45,15 +45,16 @@ func sendFile(
}
func getOverlayFile(n node.Node, context string, stage_overlays []string, autobuild bool) (stage_file string, err error) {
stage_file = overlay.OverlayImage(n.Id(), context, stage_overlays)
stage_file = overlay.Image(n.Id(), context, stage_overlays)
build := !util.IsFile(stage_file)
wwlog.Verbose("stage file: %s", stage_file)
if !build && autobuild {
build = util.PathIsNewer(stage_file, config.Get().Paths.NodesConf())
for _, overlayname := range stage_overlays {
overlayDir := overlay.GetOverlay(overlayname).Rootfs()
build = build || util.PathIsNewer(stage_file, overlayDir)
if myOverlay, err := overlay.Get(overlayname); err == nil {
build = build || util.PathIsNewer(stage_file, myOverlay.Rootfs())
}
}
}