refactored overlay class
overlay.GetOverlay(name) returns now an error if the overlay doesn't exist. This is the most canonical way to act if there is no overlay.
This commit is contained in:
committed by
Jonathon Anderson
parent
c17fe8d512
commit
6f4fd60d8f
@@ -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.GetOverlay(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.GetOverlay(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.GetOverlay(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,20 @@
|
||||
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")
|
||||
myOverlay, err := overlay.GetOverlay(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !overlay_.Exists() {
|
||||
return fmt.Errorf("overlay does not exist: %s", overlayName)
|
||||
}
|
||||
|
||||
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])
|
||||
|
||||
} 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
|
||||
return myOverlay.DeleteFile(fileName, Force, Parents)
|
||||
}
|
||||
|
||||
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.GetOverlay(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,14 +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.CloneSiteOverlay()
|
||||
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 {
|
||||
|
||||
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.GetOverlay(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !overlay_.IsSiteOverlay() {
|
||||
overlay_, err = overlay_.CloneSiteOverlay()
|
||||
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.GetOverlay(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.GetOverlay(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.GetOverlay(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.GetOverlay("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")
|
||||
|
||||
@@ -15,58 +15,53 @@ import (
|
||||
)
|
||||
|
||||
// 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)
|
||||
func GetOverlay(name string) (overlay Overlay, err error) {
|
||||
overlay = getSiteOverlayName(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 {
|
||||
func Create(name string) (overlay Overlay, err error) {
|
||||
overlay = getSiteOverlayName(name)
|
||||
if util.IsDir(overlay.Path()) {
|
||||
return fmt.Errorf("overlay already exists: %s", overlay)
|
||||
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)
|
||||
}
|
||||
|
||||
// GetDistributionOverlay returns the filesystem path of a distribution overlay
|
||||
// identified by the given name.
|
||||
func getDistributionOverlay(name string) Overlay {
|
||||
return Overlay(path.Join(config.Get().Paths.DistributionOverlaydir(), name))
|
||||
}
|
||||
|
||||
// GetSiteOverlay returns the filesystem path of a site-specific overlay
|
||||
// identified by the given name.
|
||||
func getSiteOverlayName(name string) (overlay Overlay) {
|
||||
return Overlay(path.Join(config.Get().Paths.SiteOverlaydir(), name))
|
||||
}
|
||||
|
||||
// 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())
|
||||
wwlog.Verbose("Creating site overlay: %s", overlay.Name())
|
||||
siteOverlay = getSiteOverlayName(overlay.Name())
|
||||
if !util.IsDir(overlay.Path()) {
|
||||
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())
|
||||
return siteOverlay, nil
|
||||
}
|
||||
if !util.IsDir(filepath.Dir(siteOverlay.Path())) {
|
||||
if err := os.MkdirAll(filepath.Dir(siteOverlay.Path()), 0o755); err != nil {
|
||||
|
||||
@@ -100,7 +100,7 @@ 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()
|
||||
return strings.Contains(overlay.Path(), config.Get().Paths.SiteOverlaydir())
|
||||
}
|
||||
|
||||
// IsDistributionOverlay determines whether the overlay is a distribution overlay.
|
||||
@@ -111,7 +111,7 @@ 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()
|
||||
return strings.Contains(overlay.Path(), config.Get().Paths.DistributionOverlaydir())
|
||||
}
|
||||
|
||||
func (overlay Overlay) AddFile(filePath string, content []byte, parents bool, force bool) error {
|
||||
@@ -125,7 +125,9 @@ func (overlay Overlay) AddFile(filePath string, content []byte, parents bool, fo
|
||||
// replace the overlay with newly created siteOverlay
|
||||
overlay = siteOverlay
|
||||
}
|
||||
|
||||
if !overlay.IsSiteOverlay() {
|
||||
return fmt.Errorf("cloning of site overlay failed")
|
||||
}
|
||||
fullPath := overlay.File(filePath)
|
||||
// create necessary parent directories
|
||||
if parents {
|
||||
@@ -146,23 +148,34 @@ func (overlay Overlay) AddFile(filePath string, content []byte, parents bool, fo
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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)
|
||||
if filePath == "" {
|
||||
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 {
|
||||
// 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()
|
||||
if err != nil {
|
||||
@@ -171,12 +184,7 @@ func (overlay Overlay) DeleteFile(filePath string, force, cleanup bool) error {
|
||||
// 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)
|
||||
@@ -208,6 +216,51 @@ func (overlay Overlay) DeleteFile(filePath string, force, cleanup bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// chmod for the given oppath in the overlay
|
||||
func (overlay Overlay) Chmod(path string, mode uint64) (err error) {
|
||||
if !overlay.IsSiteOverlay() {
|
||||
overlay, err = overlay.CloneSiteOverlay()
|
||||
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.CloneSiteOverlay()
|
||||
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.CloneSiteOverlay()
|
||||
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 +350,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 := GetOverlay("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 {
|
||||
@@ -424,19 +480,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 := GetOverlay(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 +725,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 := GetOverlay(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 := GetOverlay(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)
|
||||
|
||||
@@ -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.GetOverlay(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.GetOverlay(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.GetOverlay(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.GetOverlay(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.GetOverlay(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.GetOverlay(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,13 @@ type OverlayFile struct {
|
||||
}
|
||||
|
||||
func (of *OverlayFile) FullPath() string {
|
||||
return path.Join(overlay.GetOverlay(of.Overlay).Rootfs(), of.Path)
|
||||
myOverlay, _ := overlay.GetOverlay(of.Overlay)
|
||||
return myOverlay.File(of.Path)
|
||||
}
|
||||
|
||||
func (of *OverlayFile) Exists() bool {
|
||||
return overlay.GetOverlay(of.Overlay).Exists() && util.IsFile(of.FullPath())
|
||||
myOverlay, _ := overlay.GetOverlay(of.Overlay)
|
||||
return myOverlay.Exists() && util.IsFile(of.FullPath())
|
||||
}
|
||||
|
||||
func (of *OverlayFile) readContents() (string, error) {
|
||||
@@ -202,9 +207,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.GetOverlay(input.Name)
|
||||
if err != nil {
|
||||
newOverlay, err = overlay.Create(input.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*output = *NewOverlayResponse(newOverlay.Name())
|
||||
return nil
|
||||
@@ -223,12 +231,9 @@ func deleteOverlay() usecase.Interactor {
|
||||
|
||||
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_, _ := overlay.GetOverlay(input.Name)
|
||||
isSiteOverlay := overlay_.IsSiteOverlay()
|
||||
if !input.Force {
|
||||
if registry, err := node.New(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
@@ -240,15 +245,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 := os.RemoveAll(overlay_.Path()); err != nil {
|
||||
return status.Wrap(fmt.Errorf("failed to remove overlay: %v", err), status.Code(409))
|
||||
}
|
||||
*output = *NewOverlayResponse(input.Name)
|
||||
output.Site = isSiteOverlay
|
||||
return nil
|
||||
})
|
||||
u.SetTitle("Delete an overlay")
|
||||
@@ -274,7 +276,7 @@ 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)
|
||||
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)
|
||||
@@ -305,7 +307,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.GetOverlay(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)
|
||||
}
|
||||
|
||||
@@ -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.GetOverlay(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.GetOverlay(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.GetOverlay(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.GetOverlay(overlay_); err != nil {
|
||||
return status.Wrap(fmt.Errorf("overlay '%s' does not exist", overlay_), status.InvalidArgument)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ func OverlaySend(w http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
o := overlay.GetOverlay(rinfo.overlay)
|
||||
if !o.Exists() {
|
||||
myOverlay, err := overlay.GetOverlay(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)
|
||||
|
||||
@@ -52,8 +52,8 @@ func getOverlayFile(n node.Node, context string, stage_overlays []string, autobu
|
||||
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)
|
||||
myOverlay, _ := overlay.GetOverlay(overlayname)
|
||||
build = build || util.PathIsNewer(stage_file, myOverlay.Rootfs())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user