Merge pull request #1968 from mslacken/FixSiteOverlays
refactored overlay class
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
95
internal/app/wwctl/overlay/chmod/main_test.go
Normal file
95
internal/app/wwctl/overlay/chmod/main_test.go
Normal 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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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 err != nil {
|
||||
return fmt.Errorf("UID is not an integer: %s", args[2])
|
||||
}
|
||||
|
||||
if len(args) > 3 {
|
||||
gid, err = strconv.Atoi(args[3])
|
||||
if err != nil {
|
||||
return fmt.Errorf("GID is not an integer: %s", args[3])
|
||||
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", parts[0])
|
||||
}
|
||||
}
|
||||
if parts[1] != "" {
|
||||
gid, err = strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("GID is not an integer: %s", parts[1])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gid = -1
|
||||
}
|
||||
|
||||
overlay_ := overlay.GetOverlay(overlayName)
|
||||
if !overlay_.Exists() {
|
||||
return fmt.Errorf("overlay does not exist: %s", overlayName)
|
||||
}
|
||||
|
||||
if !overlay_.IsSiteOverlay() {
|
||||
overlay_, err = overlay_.CloneSiteOverlay()
|
||||
uid, err = strconv.Atoi(chownSpec)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("UID is not an integer: %s", chownSpec)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
myOverlay, err := overlay.Get(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not set ownership: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return myOverlay.Chown(fileName, uid, gid)
|
||||
}
|
||||
|
||||
156
internal/app/wwctl/overlay/chown/main_test.go
Normal file
156
internal/app/wwctl/overlay/chown/main_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
63
internal/app/wwctl/overlay/create/main_test.go
Normal file
63
internal/app/wwctl/overlay/create/main_test.go
Normal 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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
} else {
|
||||
err := os.Remove(overlay_.Path())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed deleting overlay: %w", err)
|
||||
}
|
||||
}
|
||||
wwlog.Info("Deleted overlay: %s\n", args[0])
|
||||
|
||||
return myOverlay.Delete(Force)
|
||||
} 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 myOverlay.DeleteFile(fileName, Force, Parents)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
141
internal/app/wwctl/overlay/delete/main_test.go
Normal file
141
internal/app/wwctl/overlay/delete/main_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
overlayFile = myOverlay.File(fileName)
|
||||
overlayFileDir = path.Dir(overlayFile)
|
||||
}
|
||||
// re-generate because overlay_ may have changed
|
||||
overlayFile = overlay_.File(fileName)
|
||||
overlayFileDir = path.Dir(overlayFile)
|
||||
|
||||
if CreateDirs {
|
||||
if err := os.MkdirAll(overlayFileDir, 0755); err != nil {
|
||||
|
||||
217
internal/app/wwctl/overlay/edit/main_test.go
Normal file
217
internal/app/wwctl/overlay/edit/main_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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,25 +22,23 @@ 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)
|
||||
overlay_, err := overlay.Get(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !overlay_.IsSiteOverlay() {
|
||||
overlay_, err = overlay_.CloneSiteOverlay()
|
||||
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)) {
|
||||
dest = path.Join(dest, path.Base(source))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -24,15 +24,13 @@ var (
|
||||
}
|
||||
},
|
||||
}
|
||||
OverwriteFile bool
|
||||
NoOverlayUpdate bool
|
||||
CreateDirs bool
|
||||
Workers int
|
||||
OverwriteFile 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)")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package list
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -11,62 +12,80 @@ import (
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
var overlays []string
|
||||
/*
|
||||
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 {
|
||||
overlays = args
|
||||
} else {
|
||||
overlays = overlay.FindOverlays()
|
||||
}
|
||||
|
||||
t := table.New(cmd.OutOrStdout())
|
||||
if ListLong {
|
||||
t.AddHeader("PERM MODE", "UID", "GID", "OVERLAY", "FILE PATH", "SITE")
|
||||
} else {
|
||||
t.AddHeader("OVERLAY NAME", "FILES/DIRS", "SITE")
|
||||
}
|
||||
|
||||
for _, name := range overlays {
|
||||
overlay_ := overlay.GetOverlay(name)
|
||||
|
||||
if !overlay_.Exists() {
|
||||
wwlog.Error("system/%s (path not found:%s)", name, overlay_.Rootfs())
|
||||
continue
|
||||
}
|
||||
|
||||
files := util.FindFiles(overlay_.Rootfs())
|
||||
|
||||
wwlog.Debug("Iterating overlay rootfs: %s", overlay_.Rootfs())
|
||||
if 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())
|
||||
}
|
||||
} else if ListContents {
|
||||
var fileCount int
|
||||
for file := range files {
|
||||
t.AddLine(name, files[file], overlay_.IsSiteOverlay())
|
||||
fileCount++
|
||||
}
|
||||
if fileCount == 0 {
|
||||
t.AddLine(name, 0, overlay_.IsSiteOverlay())
|
||||
}
|
||||
if len(args) > 0 {
|
||||
overlays = args
|
||||
} else {
|
||||
t.AddLine(name, len(files), overlay_.IsSiteOverlay())
|
||||
overlays = overlay.FindOverlays()
|
||||
}
|
||||
}
|
||||
t.Print()
|
||||
|
||||
return nil
|
||||
t := table.New(cmd.OutOrStdout())
|
||||
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", locationStr)
|
||||
}
|
||||
|
||||
for _, name := range overlays {
|
||||
overlay_, err := overlay.Get(name)
|
||||
|
||||
if err != nil {
|
||||
wwlog.Error("%s:%s", name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
files := util.FindFiles(overlay_.Rootfs())
|
||||
|
||||
wwlog.Debug("Iterating overlay rootfs: %s", overlay_.Rootfs())
|
||||
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()
|
||||
locLine := strconv.FormatBool(overlay_.IsSiteOverlay())
|
||||
if vars.ShowPath {
|
||||
locLine = overlay_.Path()
|
||||
}
|
||||
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], locLine)
|
||||
fileCount++
|
||||
}
|
||||
if fileCount == 0 {
|
||||
t.AddLine(name, 0, locLine)
|
||||
}
|
||||
} else {
|
||||
t.AddLine(name, len(files), locLine)
|
||||
}
|
||||
}
|
||||
}
|
||||
t.Print()
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
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))
|
||||
myOverlay, err := overlay.Get(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create directory: %s", overlayDir)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return myOverlay.Mkdir(args[1], PermMode)
|
||||
}
|
||||
|
||||
89
internal/app/wwctl/overlay/mkdir/main_test.go
Normal file
89
internal/app/wwctl/overlay/mkdir/main_test.go
Normal 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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,69 +156,122 @@ 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 == "" {
|
||||
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 {
|
||||
return fmt.Errorf("failed to delete overlay: %w", err)
|
||||
}
|
||||
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 {
|
||||
if overlay.IsDistributionOverlay() {
|
||||
siteOverlay, err := overlay.CloneSiteOverlay()
|
||||
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
|
||||
// remove rootfs at first
|
||||
if err = os.Remove(overlay.Rootfs()); err != nil {
|
||||
return fmt.Errorf("failed to delete overlay: %w", err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
if err := os.Remove(fullPath); err != nil {
|
||||
return fmt.Errorf("failed to delete file %s from overlay %s: %w", filePath, overlay.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
if cleanup {
|
||||
// cleanup the empty parents
|
||||
i := path.Dir(fullPath)
|
||||
for i != overlay.Rootfs() {
|
||||
wwlog.Debug("Evaluating directory to remove: %s", i)
|
||||
err := os.Remove(i)
|
||||
if err != nil {
|
||||
// if the directory is not empty, we stop here
|
||||
if !os.IsNotExist(err) {
|
||||
wwlog.Debug("Could not remove directory %s: %v", i, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
wwlog.Debug("Removed empty directory: %s", i)
|
||||
i = path.Dir(i)
|
||||
if overlay.Exists() {
|
||||
if err = os.Remove(overlay.Path()); err != nil {
|
||||
return fmt.Errorf("failed to delete overlay: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
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.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 force {
|
||||
if err := os.RemoveAll(fullPath); err != nil {
|
||||
return fmt.Errorf("failed to delete file %s from overlay %s: %w", filePath, overlay.Name(), err)
|
||||
}
|
||||
} else {
|
||||
if err := os.Remove(fullPath); err != nil {
|
||||
return fmt.Errorf("failed to delete file %s from overlay %s: %w", filePath, overlay.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
if cleanup {
|
||||
// cleanup the empty parents
|
||||
i := path.Dir(fullPath)
|
||||
for i != overlay.Rootfs() {
|
||||
wwlog.Debug("Evaluating directory to remove: %s", i)
|
||||
err := os.Remove(i)
|
||||
if err != nil {
|
||||
// if the directory is not empty, we stop here
|
||||
if !os.IsNotExist(err) {
|
||||
wwlog.Debug("Could not remove directory %s: %v", i, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
wwlog.Debug("Removed empty directory: %s", i)
|
||||
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
|
||||
})
|
||||
|
||||
@@ -139,19 +139,22 @@ func Test_OverlayMethods(t *testing.T) {
|
||||
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
overlay := GetOverlay(tt.name)
|
||||
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())
|
||||
assert.Equal(t, env.GetPath(tt.file), overlay.File("testfile"))
|
||||
if tt.content != "" {
|
||||
buffer, err := os.ReadFile(overlay.File("testfile"))
|
||||
overlay, err := Get(tt.name)
|
||||
if tt.exists {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.content, string(buffer))
|
||||
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())
|
||||
assert.Equal(t, env.GetPath(tt.file), overlay.File("testfile"))
|
||||
if tt.content != "" {
|
||||
buffer, err := os.ReadFile(overlay.File("testfile"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.content, string(buffer))
|
||||
}
|
||||
assert.Equal(t, tt.exists, overlay.Exists())
|
||||
assert.Equal(t, tt.isSite, overlay.IsSiteOverlay())
|
||||
assert.Equal(t, tt.isDist, overlay.IsDistributionOverlay())
|
||||
}
|
||||
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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
o.Files = files
|
||||
myOverlay, err := overlay.Get(name)
|
||||
if err == nil {
|
||||
if files, err := myOverlay.GetFiles(); err == nil {
|
||||
o.Files = files
|
||||
}
|
||||
o.Site = myOverlay.IsSiteOverlay()
|
||||
}
|
||||
o.Site = overlay.GetOverlay(name).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,9 +213,12 @@ 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 {
|
||||
return err
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
overlay_, err := overlay.Get(input.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 {
|
||||
return status.Wrap(fmt.Errorf("failed to remove overlay: %v", err), status.Code(409))
|
||||
}
|
||||
}
|
||||
}
|
||||
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,10 +289,12 @@ 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 {
|
||||
return fmt.Errorf("unable to delete overlay file %v: %v: %w", input.Name, relPath, err)
|
||||
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)
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
ja := jsonassert.New(t)
|
||||
ja.Assertf(string(body), tt.response) //nolint:govet // tt.response is used as a format string with special tokens
|
||||
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))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user