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
|
## Unreleased
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Build support for EL10, EL10 depends on `dnsmasq` and no longer `dhcpd-server` (EOL). #1974
|
- Build support for EL10, EL10 depends on `dnsmasq` and no longer `dhcpd-server` (EOL). #1974
|
||||||
- `make rpm` added for local development rpm builds. #1974
|
- `make rpm` added for local development rpm builds. #1974
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- `make dist` now uses `mktemp` instead of `.dist`. #1974
|
- `make dist` now uses `mktemp` instead of `.dist`. #1974
|
||||||
- Fixed dnsmasq template file to use basename for ipxe files. #1974
|
- Fixed dnsmasq template file to use basename for ipxe files. #1974
|
||||||
- For EL10 default to dnsmasq for dhcpd and tftp. #1974
|
- For EL10 default to dnsmasq for dhcpd and tftp. #1974
|
||||||
|
- Refactored overlay class. #1968
|
||||||
|
|
||||||
## v4.6.3, 2025-08-01
|
## 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) {
|
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
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/overlay"
|
"github.com/warewulf/warewulf/internal/pkg/overlay"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||||
overlayName := args[0]
|
myOverlay, err := overlay.Get(args[0])
|
||||||
fileName := args[1]
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
path := args[1]
|
||||||
|
|
||||||
permissionMode, err := strconv.ParseUint(args[2], 8, 32)
|
permissionMode, err := strconv.ParseUint(args[2], 8, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not convert requested mode: %s", err)
|
return fmt.Errorf("could not convert requested mode: %s", err)
|
||||||
}
|
}
|
||||||
overlay_ := overlay.GetOverlay(overlayName)
|
return myOverlay.Chmod(path, permissionMode)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/overlay"
|
|
||||||
"github.com/warewulf/warewulf/internal/pkg/util"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/warewulf/warewulf/internal/pkg/overlay"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||||
var uid int
|
fileName := args[1]
|
||||||
var gid int
|
chownSpec := args[2]
|
||||||
|
|
||||||
|
var uid, gid = -1, -1
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
overlayName := args[0]
|
if strings.Contains(chownSpec, ":") {
|
||||||
fileName := args[1]
|
parts := strings.SplitN(chownSpec, ":", 2)
|
||||||
|
if parts[0] != "" {
|
||||||
uid, err = strconv.Atoi(args[2])
|
uid, err = strconv.Atoi(parts[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("UID is not an integer: %s", args[2])
|
return fmt.Errorf("UID is not an integer: %s", parts[0])
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if len(args) > 3 {
|
if parts[1] != "" {
|
||||||
gid, err = strconv.Atoi(args[3])
|
gid, err = strconv.Atoi(parts[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("GID is not an integer: %s", args[3])
|
return fmt.Errorf("GID is not an integer: %s", parts[1])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
gid = -1
|
uid, err = strconv.Atoi(chownSpec)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("UID is not an integer: %s", chownSpec)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
overlay_ := overlay.GetOverlay(overlayName)
|
myOverlay, err := overlay.Get(args[0])
|
||||||
if !overlay_.Exists() {
|
|
||||||
return fmt.Errorf("overlay does not exist: %s", overlayName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !overlay_.IsSiteOverlay() {
|
|
||||||
overlay_, err = overlay_.CloneSiteOverlay()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
return myOverlay.Chown(fileName, uid, gid)
|
||||||
|
|
||||||
overlayFile := overlay_.File(fileName)
|
|
||||||
if !(util.IsFile(overlayFile) || util.IsDir(overlayFile)) {
|
|
||||||
return fmt.Errorf("file does not exist within overlay: %s:%s", overlayName, fileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.Chown(overlayFile, uid, gid)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not set ownership: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
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) {
|
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
|
package delete
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/overlay"
|
"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 {
|
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||||
var fileName string
|
|
||||||
|
|
||||||
overlayName := args[0]
|
|
||||||
|
|
||||||
|
fileName := ""
|
||||||
if len(args) == 2 {
|
if len(args) == 2 {
|
||||||
fileName = args[1]
|
fileName = args[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
overlay_ := overlay.GetOverlay(overlayName)
|
myOverlay, err := overlay.Get(args[0])
|
||||||
if overlay_.IsDistributionOverlay() {
|
if err != nil {
|
||||||
return fmt.Errorf("distribution overlay can't deleted")
|
return err
|
||||||
}
|
|
||||||
if !overlay_.Exists() {
|
|
||||||
return fmt.Errorf("overlay does not exist: %s", overlayName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if fileName == "" {
|
if fileName == "" {
|
||||||
if Force {
|
return myOverlay.Delete(Force)
|
||||||
err := os.RemoveAll(overlay_.Path())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed deleting overlay: %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
err := os.Remove(overlay_.Path())
|
return myOverlay.DeleteFile(fileName, Force, Parents)
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|||||||
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]
|
overlayName := args[0]
|
||||||
fileName := args[1]
|
fileName := args[1]
|
||||||
|
|
||||||
overlay_ := overlay.GetOverlay(overlayName)
|
myOverlay, err := overlay.Get(overlayName)
|
||||||
if !overlay_.Exists() {
|
if err != nil {
|
||||||
return fmt.Errorf("overlay does not exist: %s", overlayName)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
overlayFile := overlay_.File(fileName)
|
overlayFile := myOverlay.File(fileName)
|
||||||
wwlog.Debug("Will edit overlay file: %s", overlayFile)
|
wwlog.Debug("Will edit overlay file: %s", overlayFile)
|
||||||
overlayFileDir := path.Dir(overlayFile)
|
overlayFileDir := path.Dir(overlayFile)
|
||||||
if !(util.IsDir(overlayFileDir) || CreateDirs) {
|
if !(util.IsDir(overlayFileDir) || CreateDirs) {
|
||||||
return fmt.Errorf("%s does not exist. Use '--parents' option to create automatically", overlayFileDir)
|
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 {
|
if tempFileErr != nil {
|
||||||
return fmt.Errorf("unable to create temporary file for editing: %s", tempFileErr)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !overlay_.IsSiteOverlay() {
|
if !myOverlay.IsSiteOverlay() {
|
||||||
overlay_, err = overlay_.CloneSiteOverlay()
|
myOverlay, err = myOverlay.CloneToSite()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
overlayFile = myOverlay.File(fileName)
|
||||||
// re-generate because overlay_ may have changed
|
|
||||||
overlayFile = overlay_.File(fileName)
|
|
||||||
overlayFileDir = path.Dir(overlayFile)
|
overlayFileDir = path.Dir(overlayFile)
|
||||||
|
}
|
||||||
|
|
||||||
if CreateDirs {
|
if CreateDirs {
|
||||||
if err := os.MkdirAll(overlayFileDir, 0755); err != nil {
|
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"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
|
||||||
"github.com/warewulf/warewulf/internal/pkg/overlay"
|
"github.com/warewulf/warewulf/internal/pkg/overlay"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/util"
|
"github.com/warewulf/warewulf/internal/pkg/util"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||||
@@ -17,7 +15,6 @@ import (
|
|||||||
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
|
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
|
||||||
var dest string
|
var dest string
|
||||||
|
|
||||||
overlayName := args[0]
|
|
||||||
source := args[1]
|
source := args[1]
|
||||||
|
|
||||||
if len(args) == 3 {
|
if len(args) == 3 {
|
||||||
@@ -25,17 +22,15 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
|
|||||||
} else {
|
} else {
|
||||||
dest = source
|
dest = source
|
||||||
}
|
}
|
||||||
|
overlay_, err := overlay.Get(args[0])
|
||||||
wwlog.Verbose("Copying '%s' into overlay '%s:%s'", source, overlayName, dest)
|
|
||||||
overlay_ := overlay.GetOverlay(overlayName)
|
|
||||||
if !overlay_.IsSiteOverlay() {
|
|
||||||
overlay_, err = overlay_.CloneSiteOverlay()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if !overlay_.IsSiteOverlay() {
|
||||||
|
overlay_, err = overlay_.CloneToSite()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
if !overlay_.Exists() {
|
|
||||||
return fmt.Errorf("overlay does not exist: %s", overlayName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if util.IsDir(overlay_.File(dest)) {
|
if util.IsDir(overlay_.File(dest)) {
|
||||||
@@ -43,7 +38,7 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !OverwriteFile && util.IsFile(overlay_.File(dest)) {
|
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 {
|
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)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ nodes: {}
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
baseCmd.SetArgs([]string{"-p", "-n", "test", file.Name()})
|
baseCmd.SetArgs([]string{"-p", "test", file.Name()})
|
||||||
baseCmd.SetOut(nil)
|
baseCmd.SetOut(nil)
|
||||||
baseCmd.SetErr(nil)
|
baseCmd.SetErr(nil)
|
||||||
err = baseCmd.Execute()
|
err = baseCmd.Execute()
|
||||||
|
|||||||
@@ -25,14 +25,12 @@ var (
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
OverwriteFile bool
|
OverwriteFile bool
|
||||||
NoOverlayUpdate bool
|
|
||||||
CreateDirs bool
|
CreateDirs bool
|
||||||
Workers int
|
Workers int
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
baseCmd.PersistentFlags().BoolVarP(&OverwriteFile, "overwrite", "o", false, "Overwrite file if exists")
|
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().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)")
|
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 (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -11,7 +12,13 @@ import (
|
|||||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
/*
|
||||||
|
RunE needs a function of type func(*cobraCommand,[]string) err, but
|
||||||
|
in order to avoid global variables which mess up testing a function of
|
||||||
|
the required type is returned
|
||||||
|
*/
|
||||||
|
func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) error {
|
||||||
|
return func(cmd *cobra.Command, args []string) error {
|
||||||
var overlays []string
|
var overlays []string
|
||||||
|
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
@@ -21,52 +28,64 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t := table.New(cmd.OutOrStdout())
|
t := table.New(cmd.OutOrStdout())
|
||||||
if ListLong {
|
locationStr := "SITE"
|
||||||
t.AddHeader("PERM MODE", "UID", "GID", "OVERLAY", "FILE PATH", "SITE")
|
if vars.ShowPath {
|
||||||
|
locationStr = "PATH"
|
||||||
|
}
|
||||||
|
if vars.ListLong {
|
||||||
|
t.AddHeader("PERM MODE", "UID", "GID", "OVERLAY", "FILE PATH", locationStr)
|
||||||
} else {
|
} else {
|
||||||
t.AddHeader("OVERLAY NAME", "FILES/DIRS", "SITE")
|
t.AddHeader("OVERLAY NAME", "FILES/DIRS", locationStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range overlays {
|
for _, name := range overlays {
|
||||||
overlay_ := overlay.GetOverlay(name)
|
overlay_, err := overlay.Get(name)
|
||||||
|
|
||||||
if !overlay_.Exists() {
|
if err != nil {
|
||||||
wwlog.Error("system/%s (path not found:%s)", name, overlay_.Rootfs())
|
wwlog.Error("%s:%s", name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
files := util.FindFiles(overlay_.Rootfs())
|
files := util.FindFiles(overlay_.Rootfs())
|
||||||
|
|
||||||
wwlog.Debug("Iterating overlay rootfs: %s", overlay_.Rootfs())
|
wwlog.Debug("Iterating overlay rootfs: %s", overlay_.Rootfs())
|
||||||
if ListLong {
|
if vars.ListLong {
|
||||||
for file := range files {
|
for file := range files {
|
||||||
s, err := os.Stat(overlay_.File(files[file]))
|
s, err := os.Stat(overlay_.File(files[file]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wwlog.Warn("%s: %s: %s", name, files[file], err)
|
wwlog.Warn("%s: %s: %s", name, files[file], err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fileMode := s.Mode()
|
fileMode := s.Mode()
|
||||||
perms := fileMode & os.ModePerm
|
perms := fileMode & os.ModePerm
|
||||||
|
|
||||||
sys := s.Sys()
|
sys := s.Sys()
|
||||||
|
locLine := strconv.FormatBool(overlay_.IsSiteOverlay())
|
||||||
t.AddLine(perms, sys.(*syscall.Stat_t).Uid, sys.(*syscall.Stat_t).Gid, name, files[file], overlay_.IsSiteOverlay())
|
if vars.ShowPath {
|
||||||
|
locLine = overlay_.Path()
|
||||||
}
|
}
|
||||||
} else if ListContents {
|
t.AddLine(perms, sys.(*syscall.Stat_t).Uid, sys.(*syscall.Stat_t).Gid, name, files[file], locLine)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
locLine := strconv.FormatBool(overlay_.IsSiteOverlay())
|
||||||
|
if vars.ShowPath {
|
||||||
|
locLine = overlay_.Path()
|
||||||
|
}
|
||||||
|
if vars.ListContents {
|
||||||
var fileCount int
|
var fileCount int
|
||||||
for file := range files {
|
for file := range files {
|
||||||
t.AddLine(name, files[file], overlay_.IsSiteOverlay())
|
t.AddLine(name, files[file], locLine)
|
||||||
fileCount++
|
fileCount++
|
||||||
}
|
}
|
||||||
if fileCount == 0 {
|
if fileCount == 0 {
|
||||||
t.AddLine(name, 0, overlay_.IsSiteOverlay())
|
t.AddLine(name, 0, locLine)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.AddLine(name, len(files), overlay_.IsSiteOverlay())
|
t.AddLine(name, len(files), locLine)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.Print()
|
t.Print()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ func Test_Overlay_List(t *testing.T) {
|
|||||||
assert.Contains(t, buf.String(), "testoverlay")
|
assert.Contains(t, buf.String(), "testoverlay")
|
||||||
})
|
})
|
||||||
t.Run("overlay list all", func(t *testing.T) {
|
t.Run("overlay list all", func(t *testing.T) {
|
||||||
baseCmd.SetArgs([]string{"-a"})
|
|
||||||
baseCmd := GetCommand()
|
baseCmd := GetCommand()
|
||||||
|
baseCmd.SetArgs([]string{"-a"})
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
baseCmd.SetOut(buf)
|
baseCmd.SetOut(buf)
|
||||||
baseCmd.SetErr(buf)
|
baseCmd.SetErr(buf)
|
||||||
@@ -40,8 +40,8 @@ func Test_Overlay_List(t *testing.T) {
|
|||||||
assert.Contains(t, buf.String(), "email.ww")
|
assert.Contains(t, buf.String(), "email.ww")
|
||||||
})
|
})
|
||||||
t.Run("overlay list long", func(t *testing.T) {
|
t.Run("overlay list long", func(t *testing.T) {
|
||||||
baseCmd.SetArgs([]string{"--long"})
|
|
||||||
baseCmd := GetCommand()
|
baseCmd := GetCommand()
|
||||||
|
baseCmd.SetArgs([]string{"--long"})
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
baseCmd.SetOut(buf)
|
baseCmd.SetOut(buf)
|
||||||
baseCmd.SetErr(buf)
|
baseCmd.SetErr(buf)
|
||||||
@@ -50,4 +50,16 @@ func Test_Overlay_List(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Contains(t, buf.String(), "email.ww")
|
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"
|
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// Holds the variables which are needed in CobraRunE
|
||||||
baseCmd = &cobra.Command{
|
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,
|
DisableFlagsInUseLine: true,
|
||||||
Use: "list [OPTIONS] OVERLAY_NAME",
|
Use: "list [OPTIONS] OVERLAY_NAME",
|
||||||
Short: "List Warewulf Overlays and files",
|
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.",
|
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"},
|
Aliases: []string{"ls"},
|
||||||
ValidArgsFunction: completions.Overlays,
|
ValidArgsFunction: completions.Overlays,
|
||||||
Args: cobra.ArbitraryArgs,
|
Args: cobra.ArbitraryArgs,
|
||||||
}
|
}
|
||||||
ListContents bool
|
baseCmd.PersistentFlags().BoolVarP(&vars.ListContents, "all", "a", false, "List the contents of overlays")
|
||||||
ListLong bool
|
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
|
return baseCmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,15 @@
|
|||||||
package mkdir
|
package mkdir
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/overlay"
|
"github.com/warewulf/warewulf/internal/pkg/overlay"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
|
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
|
||||||
overlayName := args[0]
|
|
||||||
dirName := args[1]
|
|
||||||
|
|
||||||
overlay_ := overlay.GetOverlay(overlayName)
|
myOverlay, err := overlay.Get(args[0])
|
||||||
if !overlay_.IsSiteOverlay() {
|
|
||||||
overlay_, err = overlay_.CloneSiteOverlay()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
return myOverlay.Mkdir(args[1], PermMode)
|
||||||
|
|
||||||
if !overlay_.Exists() {
|
|
||||||
return fmt.Errorf("overlay does not exist: %s", overlayName)
|
|
||||||
}
|
|
||||||
|
|
||||||
overlayDir := overlay_.File(dirName)
|
|
||||||
wwlog.Debug("Will create directory in overlay: %s:%s", overlayName, dirName)
|
|
||||||
err = os.MkdirAll(overlayDir, os.FileMode(PermMode))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not create directory: %s", overlayDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
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]
|
overlayName := args[0]
|
||||||
fileName := args[1]
|
fileName := args[1]
|
||||||
|
|
||||||
overlay_ := overlay.GetOverlay(overlayName)
|
overlay_, err := overlay.Get(overlayName)
|
||||||
if !overlay_.Exists() {
|
if err != nil {
|
||||||
return fmt.Errorf("overlay does not exist: %s", overlayName)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
overlayFile := overlay_.File(fileName)
|
overlayFile := overlay_.File(fileName)
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ import (
|
|||||||
Creates '/etc/hosts' from the host template.
|
Creates '/etc/hosts' from the host template.
|
||||||
*/
|
*/
|
||||||
func Hostfile() (err error) {
|
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")
|
hostTemplate := path.Join(overlay_.Rootfs(), "/etc/hosts.ww")
|
||||||
if !(util.IsFile(hostTemplate)) {
|
if !(util.IsFile(hostTemplate)) {
|
||||||
return fmt.Errorf("'the overlay template '/etc/hosts.ww' does not exists in 'host' overlay")
|
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"
|
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetOverlay returns the filesystem path of an overlay identified by its name,
|
// Get returns the filesystem path of an overlay identified by its name,
|
||||||
// along with a boolean indicating whether the returned overlayPath corresponds
|
func Get(name string) (overlay Overlay, err error) {
|
||||||
// to a site-specific overlay.
|
overlay = getSiteOverlay(name)
|
||||||
func GetOverlay(name string) (overlay Overlay) {
|
|
||||||
overlay = GetSiteOverlay(name)
|
|
||||||
if overlay.Exists() {
|
if overlay.Exists() {
|
||||||
return overlay
|
return overlay, nil
|
||||||
}
|
}
|
||||||
overlay = GetDistributionOverlay(name)
|
overlay = getDistributionOverlay(name)
|
||||||
if overlay.Exists() {
|
if overlay.Exists() {
|
||||||
return overlay
|
return overlay, nil
|
||||||
}
|
}
|
||||||
return GetSiteOverlay(name)
|
return "", ErrDoesNotExist
|
||||||
}
|
|
||||||
|
|
||||||
// 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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a new overlay directory for the given overlay
|
// Create creates a new overlay directory for the given overlay
|
||||||
//
|
//
|
||||||
// Returns an error if the overlay already exists or if directory creation fails.
|
// 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) {
|
||||||
if util.IsDir(overlay.Path()) {
|
overlay = getSiteOverlay(name)
|
||||||
return fmt.Errorf("overlay already exists: %s", overlay)
|
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.
|
// If the distribution overlay doesn't exist, return an error.
|
||||||
func (overlay Overlay) CloneSiteOverlay() (siteOverlay Overlay, err error) {
|
func (overlay Overlay) CloneToSite() (siteOverlay Overlay, err error) {
|
||||||
siteOverlay = GetSiteOverlay(overlay.Name())
|
wwlog.Verbose("Cloning to site overlay: %s", overlay.Name())
|
||||||
if !util.IsDir(overlay.Path()) {
|
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())
|
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 !util.IsDir(filepath.Dir(siteOverlay.Path())) {
|
||||||
if err := os.MkdirAll(filepath.Dir(siteOverlay.Path()), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(siteOverlay.Path()), 0o755); err != nil {
|
||||||
return siteOverlay, err
|
return siteOverlay, err
|
||||||
@@ -77,7 +73,7 @@ func (overlay Overlay) CloneSiteOverlay() (siteOverlay Overlay, err error) {
|
|||||||
return siteOverlay, err
|
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.
|
// context and the overlays contained in it.
|
||||||
//
|
//
|
||||||
// If a context is provided, the image file name is based on that
|
// 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
|
// If the context is empty and no overlays are specified, the empty
|
||||||
// string is returned.
|
// string is returned.
|
||||||
func OverlayImage(nodeName string, context string, overlayNames []string) string {
|
func Image(nodeName string, context string, overlayNames []string) string {
|
||||||
var name string
|
var name string
|
||||||
if context != "" {
|
if context != "" {
|
||||||
if len(overlayNames) > 0 {
|
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)
|
return path.Join(config.Get().Paths.OverlayProvisiondir(), nodeName, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ClearOverlayImage(nodeName string, context string, overlayNames []string) error {
|
func RemoveImage(nodeName string, context string, overlayNames []string) error {
|
||||||
imagePath := OverlayImage(nodeName, context, overlayNames)
|
imagePath := Image(nodeName, context, overlayNames)
|
||||||
if util.IsFile(imagePath) {
|
if util.IsFile(imagePath) {
|
||||||
if err := os.Remove(imagePath); err != nil {
|
if err := os.Remove(imagePath); err != nil {
|
||||||
return fmt.Errorf("failed to remove overlay image: %w", err)
|
return fmt.Errorf("failed to remove overlay image: %w", err)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func Test_OverlayImage(t *testing.T) {
|
|||||||
conf.Paths.WWProvisiondir = "p"
|
conf.Paths.WWProvisiondir = "p"
|
||||||
for _, tt := range overlayImageTests {
|
for _, tt := range overlayImageTests {
|
||||||
t.Run(tt.description, func(t *testing.T) {
|
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 {
|
if tt.image != out {
|
||||||
t.Errorf("got %q, want %q", out, tt.image)
|
t.Errorf("got %q, want %q", out, tt.image)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,13 @@ func (overlay Overlay) Exists() bool {
|
|||||||
// Returns:
|
// Returns:
|
||||||
// - true if the overlay is a site overlay; false otherwise.
|
// - true if the overlay is a site overlay; false otherwise.
|
||||||
func (overlay Overlay) IsSiteOverlay() bool {
|
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.
|
// IsDistributionOverlay determines whether the overlay is a distribution overlay.
|
||||||
@@ -111,21 +117,26 @@ func (overlay Overlay) IsSiteOverlay() bool {
|
|||||||
// Returns:
|
// Returns:
|
||||||
// - true if the overlay is a distribution overlay; false otherwise.
|
// - true if the overlay is a distribution overlay; false otherwise.
|
||||||
func (overlay Overlay) IsDistributionOverlay() bool {
|
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 {
|
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)
|
wwlog.Info("Creating file %s in overlay %s, force: %v", filePath, overlay.Name(), force)
|
||||||
|
|
||||||
if overlay.IsDistributionOverlay() {
|
if !overlay.IsSiteOverlay() {
|
||||||
siteOverlay, err := overlay.CloneSiteOverlay()
|
siteOverlay, err := overlay.CloneToSite()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to clone distribution overlay '%s' to site overlay: %w", overlay.Name(), err)
|
return fmt.Errorf("failed to clone distribution overlay '%s' to site overlay: %w", overlay.Name(), err)
|
||||||
}
|
}
|
||||||
// replace the overlay with newly created siteOverlay
|
// replace the overlay with newly created siteOverlay
|
||||||
overlay = siteOverlay
|
overlay = siteOverlay
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := overlay.File(filePath)
|
fullPath := overlay.File(filePath)
|
||||||
// create necessary parent directories
|
// create necessary parent directories
|
||||||
if parents {
|
if parents {
|
||||||
@@ -145,38 +156,47 @@ func (overlay Overlay) AddFile(filePath string, content []byte, parents bool, fo
|
|||||||
return os.WriteFile(fullPath, content, 0o644)
|
return os.WriteFile(fullPath, content, 0o644)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteFile deletes a file or the entire overlay directory.
|
func (overlay Overlay) Delete(force bool) (err error) {
|
||||||
// If the file belongs to a distribution overlay, it will be cloned to a site overlay
|
wwlog.Info("Deleting overlay %s, force: %v", overlay.Name(), force)
|
||||||
// before deletion.
|
if overlay.IsDistributionOverlay() {
|
||||||
func (overlay Overlay) DeleteFile(filePath string, force, cleanup bool) error {
|
return fmt.Errorf("cannot delete a distribution overlay: %s", overlay.Name())
|
||||||
wwlog.Info("Deleting file %s from overlay %s, force: %v, cleanup: %v", filePath, overlay.Name(), force, cleanup)
|
}
|
||||||
if filePath == "" {
|
|
||||||
if force {
|
if force {
|
||||||
err := os.RemoveAll(overlay.Path())
|
err := os.RemoveAll(overlay.Path())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete overlay forcely: %w", err)
|
return fmt.Errorf("failed to delete overlay forcely: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err := os.Remove(overlay.Path())
|
// remove rootfs at first
|
||||||
if err != nil {
|
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)
|
return fmt.Errorf("failed to delete overlay: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFile deletes a file or the entire overlay directory.
|
||||||
|
// before deletion.
|
||||||
|
func (overlay Overlay) DeleteFile(filePath string, force, cleanup bool) (err error) {
|
||||||
|
wwlog.Info("Deleting file %s from overlay %s, force: %v, cleanup: %v", filePath, overlay.Name(), force, cleanup)
|
||||||
|
// first check if file exists
|
||||||
|
if !util.IsFile(overlay.File(filePath)) {
|
||||||
|
return fmt.Errorf("file %s does not exist in overlay %s", filePath, overlay.Name())
|
||||||
|
}
|
||||||
if overlay.IsDistributionOverlay() {
|
if overlay.IsDistributionOverlay() {
|
||||||
siteOverlay, err := overlay.CloneSiteOverlay()
|
siteOverlay, err := overlay.CloneToSite()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to clone distribution overlay '%s' to site overlay: %w", overlay.Name(), err)
|
return fmt.Errorf("failed to clone distribution overlay '%s' to site overlay: %w", overlay.Name(), err)
|
||||||
}
|
}
|
||||||
// replace the overlay with newly created siteOverlay
|
// replace the overlay with newly created siteOverlay
|
||||||
overlay = siteOverlay
|
overlay = siteOverlay
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := overlay.File(filePath)
|
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 force {
|
||||||
if err := os.RemoveAll(fullPath); err != nil {
|
if err := os.RemoveAll(fullPath); err != nil {
|
||||||
return fmt.Errorf("failed to delete file %s from overlay %s: %w", filePath, overlay.Name(), err)
|
return fmt.Errorf("failed to delete file %s from overlay %s: %w", filePath, overlay.Name(), err)
|
||||||
@@ -204,10 +224,54 @@ func (overlay Overlay) DeleteFile(filePath string, force, cleanup bool) error {
|
|||||||
i = path.Dir(i)
|
i = path.Dir(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil
|
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 {
|
func BuildAllOverlays(nodes []node.Node, allNodes []node.Node, workerCount int) error {
|
||||||
nodeChan := make(chan node.Node, len(nodes))
|
nodeChan := make(chan node.Node, len(nodes))
|
||||||
errChan := make(chan error, len(nodes)*2)
|
errChan := make(chan error, len(nodes)*2)
|
||||||
@@ -297,13 +361,16 @@ func BuildHostOverlay() error {
|
|||||||
hostname, _ := os.Hostname()
|
hostname, _ := os.Hostname()
|
||||||
hostData := node.NewNode(hostname)
|
hostData := node.NewNode(hostname)
|
||||||
wwlog.Info("Building overlay for %s: host", hostname)
|
wwlog.Info("Building overlay for %s: host", hostname)
|
||||||
hostdir := GetOverlay("host").Rootfs()
|
hostdir, err := Get("host")
|
||||||
stats, err := os.Stat(hostdir)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stats, err := os.Stat(hostdir.Rootfs())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not build host overlay: %w ", err)
|
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)) {
|
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()
|
registry, err := node.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -360,7 +427,7 @@ func BuildOverlay(nodeConf node.Node, allNodes []node.Node, context string, over
|
|||||||
} else {
|
} else {
|
||||||
name = fmt.Sprintf("%s overlay/%v", nodeConf.Id(), overlayNames)
|
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)
|
overlayImageDir := path.Dir(overlayImage)
|
||||||
|
|
||||||
err := os.MkdirAll(overlayImageDir, 0o750)
|
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, ","))
|
wwlog.Verbose("Processing node/overlays: %s/%s", nodeData.Id(), strings.Join(overlayNames, ","))
|
||||||
for _, overlayName := range overlayNames {
|
for _, overlayName := range overlayNames {
|
||||||
wwlog.Verbose("Building overlay %s for node %s in %s", overlayName, nodeData.Id(), outputDir)
|
wwlog.Verbose("Building overlay %s for node %s in %s", overlayName, nodeData.Id(), outputDir)
|
||||||
overlayRootfs := GetOverlay(overlayName).Rootfs()
|
overlayRootfs, err := Get(overlayName)
|
||||||
if !util.IsDir(overlayRootfs) {
|
if err != nil {
|
||||||
return fmt.Errorf("overlay %s: %w", overlayName, ErrDoesNotExist)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
wwlog.Debug("Walking the overlay structure: %s", overlayRootfs)
|
wwlog.Debug("Walking the overlay structure: %s", overlayRootfs.Rootfs())
|
||||||
err := filepath.Walk(overlayRootfs, func(walkPath string, info os.FileInfo, err error) error {
|
err = filepath.Walk(overlayRootfs.Rootfs(), func(walkPath string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error for %s: %w", walkPath, err)
|
return fmt.Errorf("error for %s: %w", walkPath, err)
|
||||||
}
|
}
|
||||||
wwlog.Debug("Found overlay file: %s", walkPath)
|
wwlog.Debug("Found overlay file: %s", walkPath)
|
||||||
|
|
||||||
relPath, relErr := filepath.Rel(overlayRootfs, walkPath)
|
relPath, relErr := filepath.Rel(overlayRootfs.Rootfs(), walkPath)
|
||||||
if relErr != nil {
|
if relErr != nil {
|
||||||
wwlog.Warn("Error computing relative path for %s: %v", walkPath, relErr)
|
wwlog.Warn("Error computing relative path for %s: %v", walkPath, relErr)
|
||||||
return 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
|
// Get all the files as a string slice for a given overlay
|
||||||
func OverlayGetFiles(name string) (files []string, err error) {
|
func (overlay Overlay) GetFiles() (files []string, err error) {
|
||||||
baseDir := GetOverlay(name).Rootfs()
|
err = filepath.Walk(overlay.Rootfs(), func(path string, info fs.FileInfo, err error) error {
|
||||||
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 {
|
|
||||||
if util.IsFile(path) {
|
if util.IsFile(path) {
|
||||||
files = append(files, strings.TrimPrefix(path, baseDir))
|
files = append(files, strings.TrimPrefix(path, overlay.Rootfs()))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -139,7 +139,9 @@ func Test_OverlayMethods(t *testing.T) {
|
|||||||
|
|
||||||
for name, tt := range tests {
|
for name, tt := range tests {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
overlay := GetOverlay(tt.name)
|
overlay, err := Get(tt.name)
|
||||||
|
if tt.exists {
|
||||||
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.name, overlay.Name())
|
assert.Equal(t, tt.name, overlay.Name())
|
||||||
assert.Equal(t, env.GetPath(tt.path), overlay.Path())
|
assert.Equal(t, env.GetPath(tt.path), overlay.Path())
|
||||||
assert.Equal(t, env.GetPath(tt.rootfs), overlay.Rootfs())
|
assert.Equal(t, env.GetPath(tt.rootfs), overlay.Rootfs())
|
||||||
@@ -152,6 +154,7 @@ func Test_OverlayMethods(t *testing.T) {
|
|||||||
assert.Equal(t, tt.exists, overlay.Exists())
|
assert.Equal(t, tt.exists, overlay.Exists())
|
||||||
assert.Equal(t, tt.isSite, overlay.IsSiteOverlay())
|
assert.Equal(t, tt.isSite, overlay.IsSiteOverlay())
|
||||||
assert.Equal(t, tt.isDist, overlay.IsDistributionOverlay())
|
assert.Equal(t, tt.isDist, overlay.IsDistributionOverlay())
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -719,8 +722,8 @@ func Test_CreateOverlayFile(t *testing.T) {
|
|||||||
content []byte
|
content []byte
|
||||||
force bool
|
force bool
|
||||||
}{
|
}{
|
||||||
{"create file", "test", "newfile.ww", []byte("new file"), false},
|
{"create file", "test", "test.ww", []byte("new file"), false},
|
||||||
{"overwrite existing file", "test", "existingfile.ww", []byte("overwrite file"), true},
|
{"overwrite existing file", "test", "test.ww", []byte("overwrite file"), true},
|
||||||
}
|
}
|
||||||
|
|
||||||
conf := warewulfconf.Get()
|
conf := warewulfconf.Get()
|
||||||
@@ -728,11 +731,15 @@ func Test_CreateOverlayFile(t *testing.T) {
|
|||||||
assert.NoError(t, overlayDirErr)
|
assert.NoError(t, overlayDirErr)
|
||||||
defer os.RemoveAll(overlayDir)
|
defer os.RemoveAll(overlayDir)
|
||||||
conf.Paths.WWOverlaydir = overlayDir
|
conf.Paths.WWOverlaydir = overlayDir
|
||||||
|
conf.Paths.Datadir = "/dev/null"
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
newOverlay := GetSiteOverlay(tt.overlayName)
|
newOverlay, err := Get(tt.overlayName)
|
||||||
err := newOverlay.AddFile(tt.filePath, tt.content, true, tt.force)
|
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)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
newFile := newOverlay.File(tt.filePath)
|
newFile := newOverlay.File(tt.filePath)
|
||||||
|
|||||||
@@ -70,13 +70,13 @@ func getNodeOverlayInfo() usecase.Interactor {
|
|||||||
Overlays: node_.RuntimeOverlay,
|
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 {
|
if sysImageStat, err := os.Stat(sysImagePath); err == nil {
|
||||||
mtime := sysImageStat.ModTime()
|
mtime := sysImageStat.ModTime()
|
||||||
out.SystemOverlay.MTime = &mtime
|
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 {
|
if runtimeImageStat, err := os.Stat(runtimeImagePath); err == nil {
|
||||||
mtime := runtimeImageStat.ModTime()
|
mtime := runtimeImageStat.ModTime()
|
||||||
out.RuntimeOverlay.MTime = &mtime
|
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)
|
return status.Wrap(fmt.Errorf("image '%s' does not exist", input.Node.ImageName), status.InvalidArgument)
|
||||||
}
|
}
|
||||||
for _, overlay_ := range input.Node.SystemOverlay {
|
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)
|
return status.Wrap(fmt.Errorf("overlay '%s' does not exist", overlay_), status.InvalidArgument)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, overlay_ := range input.Node.RuntimeOverlay {
|
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)
|
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)
|
return status.Wrap(fmt.Errorf("image '%s' does not exist", input.Node.ImageName), status.InvalidArgument)
|
||||||
}
|
}
|
||||||
for _, overlay_ := range input.Node.SystemOverlay {
|
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)
|
return status.Wrap(fmt.Errorf("overlay '%s' does not exist", overlay_), status.InvalidArgument)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, overlay_ := range input.Node.RuntimeOverlay {
|
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)
|
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 {
|
func NewOverlayResponse(name string) *OverlayResponse {
|
||||||
o := new(OverlayResponse)
|
o := new(OverlayResponse)
|
||||||
o.Files = []string{}
|
o.Files = []string{}
|
||||||
if files, err := overlay.OverlayGetFiles(name); err == nil {
|
myOverlay, err := overlay.Get(name)
|
||||||
|
if err == nil {
|
||||||
|
if files, err := myOverlay.GetFiles(); err == nil {
|
||||||
o.Files = files
|
o.Files = files
|
||||||
}
|
}
|
||||||
o.Site = overlay.GetOverlay(name).IsSiteOverlay()
|
o.Site = myOverlay.IsSiteOverlay()
|
||||||
|
}
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +58,7 @@ func getOverlayByName() usecase.Interactor {
|
|||||||
|
|
||||||
u := usecase.NewInteractor(func(ctx context.Context, input getOverlayByNameInput, output *OverlayResponse) error {
|
u := usecase.NewInteractor(func(ctx context.Context, input getOverlayByNameInput, output *OverlayResponse) error {
|
||||||
wwlog.Debug("api.getOverlayByName(Name:%v)", input.Name)
|
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)
|
return status.Wrap(fmt.Errorf("overlay not found: %v", input.Name), status.NotFound)
|
||||||
} else {
|
} else {
|
||||||
*output = *NewOverlayResponse(input.Name)
|
*output = *NewOverlayResponse(input.Name)
|
||||||
@@ -79,11 +82,19 @@ type OverlayFile struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (of *OverlayFile) FullPath() string {
|
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 {
|
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) {
|
func (of *OverlayFile) readContents() (string, error) {
|
||||||
@@ -202,10 +213,13 @@ func createOverlay() usecase.Interactor {
|
|||||||
|
|
||||||
u := usecase.NewInteractor(func(ctx context.Context, input createOverlayInput, output *OverlayResponse) error {
|
u := usecase.NewInteractor(func(ctx context.Context, input createOverlayInput, output *OverlayResponse) error {
|
||||||
wwlog.Debug("api.createOverlay(Name:%v)", input.Name)
|
wwlog.Debug("api.createOverlay(Name:%v)", input.Name)
|
||||||
newOverlay := overlay.GetSiteOverlay(input.Name)
|
newOverlay, err := overlay.Get(input.Name)
|
||||||
if err := newOverlay.Create(); err != nil {
|
if err != nil {
|
||||||
|
newOverlay, err = overlay.Create(input.Name)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
*output = *NewOverlayResponse(newOverlay.Name())
|
*output = *NewOverlayResponse(newOverlay.Name())
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -218,17 +232,21 @@ func createOverlay() usecase.Interactor {
|
|||||||
func deleteOverlay() usecase.Interactor {
|
func deleteOverlay() usecase.Interactor {
|
||||||
type deleteOverlayInput struct {
|
type deleteOverlayInput struct {
|
||||||
Name string `path:"name" required:"true" description:"Name of overlay to delete"`
|
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 {
|
u := usecase.NewInteractor(func(ctx context.Context, input deleteOverlayInput, output *OverlayResponse) error {
|
||||||
wwlog.Debug("api.deleteOverlay(Name:%v, Force:%v)", input.Name, input.Force)
|
wwlog.Debug("api.deleteOverlay(Name:%v, Force:%v)", input.Name, input.Force)
|
||||||
overlay_ := overlay.GetSiteOverlay(input.Name)
|
overlay_, err := overlay.Get(input.Name)
|
||||||
if input.Force {
|
if err != nil {
|
||||||
if err := os.RemoveAll(overlay_.Path()); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
if overlay_.IsDistributionOverlay() {
|
||||||
|
return status.Wrap(fmt.Errorf("distribution overlay: %s", overlay_.Name()), status.InvalidArgument)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !input.Force {
|
||||||
if registry, err := node.New(); err != nil {
|
if registry, err := node.New(); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
@@ -240,15 +258,12 @@ func deleteOverlay() usecase.Interactor {
|
|||||||
status.InvalidArgument)
|
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))
|
if err := overlay_.Delete(input.Force); err != nil {
|
||||||
} else {
|
|
||||||
if err := os.Remove(overlay_.Path()); err != nil {
|
|
||||||
return status.Wrap(fmt.Errorf("failed to remove overlay: %v", err), status.Code(409))
|
return status.Wrap(fmt.Errorf("failed to remove overlay: %v", err), status.Code(409))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
*output = *NewOverlayResponse(input.Name)
|
*output = *NewOverlayResponse(input.Name)
|
||||||
|
output.Site = overlay_.IsSiteOverlay()
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
u.SetTitle("Delete an overlay")
|
u.SetTitle("Delete an overlay")
|
||||||
@@ -274,12 +289,14 @@ func deleteOverlayFile() usecase.Interactor {
|
|||||||
if relPath, err := url.QueryUnescape(input.Path); err != nil {
|
if relPath, err := url.QueryUnescape(input.Path); err != nil {
|
||||||
return fmt.Errorf("failed to decode path: %v: %w", input.Path, err)
|
return fmt.Errorf("failed to decode path: %v: %w", input.Path, err)
|
||||||
} else {
|
} else {
|
||||||
overlay_ := overlay.GetOverlay(input.Name)
|
if overlay_, err := overlay.Get(input.Name); err != nil {
|
||||||
err := overlay_.DeleteFile(relPath, input.Force, input.Cleanup)
|
return err
|
||||||
if err != nil {
|
} 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)
|
return fmt.Errorf("unable to delete overlay file %v: %v: %w", input.Name, relPath, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
*output = *NewOverlayResponse(input.Name)
|
*output = *NewOverlayResponse(input.Name)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -305,7 +322,12 @@ func addOverlayFile() usecase.Interactor {
|
|||||||
if relPath, err := url.QueryUnescape(input.Path); err != nil {
|
if relPath, err := url.QueryUnescape(input.Path); err != nil {
|
||||||
return fmt.Errorf("failed to decode path: %v: %w", input.Path, err)
|
return fmt.Errorf("failed to decode path: %v: %w", input.Path, err)
|
||||||
} else {
|
} 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 {
|
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)
|
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}`,
|
response: `{"files":null, "site":true}`,
|
||||||
},
|
},
|
||||||
|
|
||||||
"delete overlay": {
|
"force delete site overlay": {
|
||||||
initFiles: map[string]string{
|
initFiles: map[string]string{
|
||||||
"/var/lib/warewulf/overlays/test/": "",
|
"/var/lib/warewulf/overlays/test/": "",
|
||||||
},
|
},
|
||||||
@@ -117,6 +117,16 @@ var overlayTests = map[string]struct {
|
|||||||
},
|
},
|
||||||
response: `{"files":[], "site":true}`,
|
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) {
|
func TestOverlayAPI(t *testing.T) {
|
||||||
@@ -160,8 +170,10 @@ func TestOverlayAPI(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NoError(t, resp.Body.Close())
|
assert.NoError(t, resp.Body.Close())
|
||||||
|
|
||||||
|
if tt.response != "" {
|
||||||
ja := jsonassert.New(t)
|
ja := jsonassert.New(t)
|
||||||
ja.Assertf(string(body), tt.response) //nolint:govet // tt.response is used as a format string with special tokens
|
ja.Assertf(string(body), tt.response) //nolint:govet // tt.response is used as a format string with special tokens
|
||||||
|
}
|
||||||
|
|
||||||
for _, fileName := range tt.resultFiles {
|
for _, fileName := range tt.resultFiles {
|
||||||
assert.DirExists(t, env.GetPath(fileName))
|
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)
|
return status.Wrap(fmt.Errorf("image '%s' does not exist", input.Profile.ImageName), status.InvalidArgument)
|
||||||
}
|
}
|
||||||
for _, overlay_ := range input.Profile.SystemOverlay {
|
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)
|
return status.Wrap(fmt.Errorf("overlay '%s' does not exist", overlay_), status.InvalidArgument)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, overlay_ := range input.Profile.RuntimeOverlay {
|
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)
|
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)
|
return status.Wrap(fmt.Errorf("image '%s' does not exist", input.Profile.ImageName), status.InvalidArgument)
|
||||||
}
|
}
|
||||||
for _, overlay_ := range input.Profile.SystemOverlay {
|
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)
|
return status.Wrap(fmt.Errorf("overlay '%s' does not exist", overlay_), status.InvalidArgument)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, overlay_ := range input.Profile.RuntimeOverlay {
|
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)
|
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)
|
return nodeFound, fmt.Errorf("%s (failed to reload configuration) %w", hwaddr, err)
|
||||||
}
|
}
|
||||||
if autobuildOverlays {
|
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)
|
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)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
o := overlay.GetOverlay(rinfo.overlay)
|
myOverlay, err := overlay.Get(rinfo.overlay)
|
||||||
if !o.Exists() {
|
if err != nil {
|
||||||
message := "overlay not found: %s"
|
message := "overlay not found: %s"
|
||||||
wwlog.Error(message, rinfo.overlay)
|
wwlog.Error(message, rinfo.overlay)
|
||||||
http.Error(w, fmt.Sprintf(message, rinfo.overlay), http.StatusNoContent)
|
http.Error(w, fmt.Sprintf(message, rinfo.overlay), http.StatusNoContent)
|
||||||
@@ -41,7 +41,7 @@ func OverlaySend(w http.ResponseWriter, req *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
overlayFile := o.File(rinfo.path)
|
overlayFile := myOverlay.File(rinfo.path)
|
||||||
if !path.IsAbs(overlayFile) {
|
if !path.IsAbs(overlayFile) {
|
||||||
message := "Path %s isn't absolute"
|
message := "Path %s isn't absolute"
|
||||||
wwlog.Denied(message, overlayFile)
|
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) {
|
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)
|
build := !util.IsFile(stage_file)
|
||||||
wwlog.Verbose("stage file: %s", stage_file)
|
wwlog.Verbose("stage file: %s", stage_file)
|
||||||
if !build && autobuild {
|
if !build && autobuild {
|
||||||
build = util.PathIsNewer(stage_file, config.Get().Paths.NodesConf())
|
build = util.PathIsNewer(stage_file, config.Get().Paths.NodesConf())
|
||||||
|
|
||||||
for _, overlayname := range stage_overlays {
|
for _, overlayname := range stage_overlays {
|
||||||
overlayDir := overlay.GetOverlay(overlayname).Rootfs()
|
if myOverlay, err := overlay.Get(overlayname); err == nil {
|
||||||
build = build || util.PathIsNewer(stage_file, overlayDir)
|
build = build || util.PathIsNewer(stage_file, myOverlay.Rootfs())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user