Remove Chdir from overlay.BuildOverlayIndir

Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
Jonathon Anderson
2024-12-20 12:06:03 -07:00
parent 9dd1ca0d53
commit e3680438a6
4 changed files with 513 additions and 350 deletions

View File

@@ -14,7 +14,6 @@ import (
"text/template"
"github.com/Masterminds/sprig/v3"
"github.com/pkg/errors"
"github.com/warewulf/warewulf/internal/pkg/config"
"github.com/warewulf/warewulf/internal/pkg/node"
@@ -23,7 +22,7 @@ import (
)
var (
ErrDoesNotExist = errors.New("overlay does not exist")
ErrDoesNotExist = fmt.Errorf("overlay does not exist")
)
// Overlay represents an overlay directory path.
@@ -231,155 +230,160 @@ func BuildOverlay(nodeConf node.Node, context string, overlayNames []string) err
return err
}
/*
Build the given overlays for a node in the given directory. If the given does not
exists it will be created.
*/
var regFile *regexp.Regexp
var regLink *regexp.Regexp
func init() {
regFile = regexp.MustCompile(`.*{{\s*/\*\s*file\s*["'](.*)["']\s*\*/\s*}}.*`)
regLink = regexp.MustCompile(`.*{{\s*/\*\s*softlink\s*["'](.*)["']\s*\*/\s*}}.*`)
}
// Build the given overlays for a node in the given directory.
func BuildOverlayIndir(nodeData node.Node, overlayNames []string, outputDir string) error {
if len(overlayNames) == 0 {
return nil
}
if !util.IsDir(outputDir) {
return errors.Errorf("output must a be a directory: %s", outputDir)
return fmt.Errorf("output must a be a directory: %s", outputDir)
}
if !util.ValidString(strings.Join(overlayNames, ""), "^[a-zA-Z0-9-._:]+$") {
return errors.Errorf("overlay names contains illegal characters: %v", overlayNames)
return fmt.Errorf("overlay names contains illegal characters: %v", overlayNames)
}
// Temporarily set umask to 0000, so directories in the overlay retain permissions
defer syscall.Umask(syscall.Umask(0))
wwlog.Verbose("Processing node/overlay: %s/%s", nodeData.Id(), strings.Join(overlayNames, "-"))
wwlog.Verbose("Processing node/overlays: %s/%s", nodeData.Id(), strings.Join(overlayNames, ","))
for _, overlayName := range overlayNames {
wwlog.Verbose("Building overlay %s for node %s in %s", overlayName, nodeData.Id(), outputDir)
overlaySourceDir := GetOverlay(overlayName).Rootfs()
wwlog.Debug("Changing directory to OverlayDir: %s", overlaySourceDir)
err := os.Chdir(overlaySourceDir)
if err != nil {
return fmt.Errorf("directory: %s name: %s err: %w", overlaySourceDir, overlayName, ErrDoesNotExist)
overlayRootfs := GetOverlay(overlayName).Rootfs()
if !util.IsDir(overlayRootfs) {
return fmt.Errorf("overlay %s: %w", overlayName, ErrDoesNotExist)
}
wwlog.Verbose("Walking the overlay structure: %s", overlaySourceDir)
err = filepath.Walk(".", func(location string, info os.FileInfo, err error) error {
wwlog.Debug("Walking the overlay structure: %s", overlayRootfs)
err := filepath.Walk(overlayRootfs, func(walkPath string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("error for %s: %w", location, err)
return fmt.Errorf("error for %s: %w", walkPath, err)
}
wwlog.Debug("Found overlay file: %s", walkPath)
wwlog.Debug("Found overlay file: %s", location)
relPath, relErr := filepath.Rel(overlayRootfs, walkPath)
if relErr != nil {
wwlog.Warn("Error computing relative path for %s: %v", walkPath, relErr)
return relErr
}
outputPath := path.Join(outputDir, relPath)
if info.IsDir() {
wwlog.Debug("Found directory: %s", location)
wwlog.Debug("Found directory: %s", walkPath)
err = os.MkdirAll(path.Join(outputDir, location), info.Mode())
if err != nil {
if err = os.MkdirAll(outputPath, info.Mode()); err != nil {
return fmt.Errorf("could not create directory within overlay: %w", err)
}
err = util.CopyUIDGID(location, path.Join(outputDir, location))
if err != nil {
if err = util.CopyUIDGID(walkPath, outputPath); err != nil {
return fmt.Errorf("failed setting permissions on overlay directory: %w", err)
}
wwlog.Debug("Created directory in overlay: %s", location)
wwlog.Debug("Created directory in overlay: %s", outputPath)
} else if filepath.Ext(location) == ".ww" {
} else if filepath.Ext(walkPath) == ".ww" {
originalOutputPath := outputPath
outputPath := strings.TrimSuffix(outputPath, ".ww")
tstruct, err := InitStruct(overlayName, nodeData)
if err != nil {
return fmt.Errorf("failed to initial data for %s: %w", nodeData.Id(), err)
}
tstruct.BuildSource = path.Join(overlaySourceDir, location)
wwlog.Verbose("Evaluating overlay template file: %s", location)
destFile := strings.TrimSuffix(location, ".ww")
tstruct.BuildSource = walkPath
wwlog.Verbose("Evaluating overlay template file: %s", walkPath)
buffer, backupFile, writeFile, err := RenderTemplateFile(location, tstruct)
buffer, backupFile, writeFile, err := RenderTemplateFile(walkPath, tstruct)
if err != nil {
return fmt.Errorf("failed to render template %s: %w", location, err)
return fmt.Errorf("failed to render template %s: %w", walkPath, err)
}
if writeFile {
destFileName := destFile
var fileBuffer bytes.Buffer
// search for magic file name comment
fileScanner := bufio.NewScanner(bytes.NewReader(buffer.Bytes()))
fileScanner.Split(ScanLines)
regFile := regexp.MustCompile(`.*{{\s*/\*\s*file\s*["'](.*)["']\s*\*/\s*}}.*`)
regLink := regexp.MustCompile(`.*{{\s*/\*\s*softlink\s*["'](.*)["']\s*\*/\s*}}.*`)
foundFileComment := false
for fileScanner.Scan() {
line := fileScanner.Text()
filenameFromTemplate := regFile.FindAllStringSubmatch(line, -1)
softlinkFromTemplate := regLink.FindAllStringSubmatch(line, -1)
if len(softlinkFromTemplate) != 0 {
wwlog.Debug("Creating soft link %s -> %s", destFileName, softlinkFromTemplate[0][1])
return os.Symlink(softlinkFromTemplate[0][1], path.Join(outputDir, destFileName))
} else if len(filenameFromTemplate) != 0 {
wwlog.Debug("Writing file %s", filenameFromTemplate[0][1])
if foundFileComment {
err = CarefulWriteBuffer(path.Join(outputDir, destFileName),
fileBuffer, backupFile, info.Mode())
if err != nil {
return fmt.Errorf("could not write file from template: %w", err)
}
err = util.CopyUIDGID(location, path.Join(outputDir, destFileName))
if err != nil {
return fmt.Errorf("failed setting permissions on template output file: %w", err)
}
fileBuffer.Reset()
if !writeFile {
return nil
}
var fileBuffer bytes.Buffer
// search for magic file name comment
fileScanner := bufio.NewScanner(bytes.NewReader(buffer.Bytes()))
fileScanner.Split(ScanLines)
foundFileComment := false
for fileScanner.Scan() {
line := fileScanner.Text()
filenameFromTemplate := regFile.FindAllStringSubmatch(line, -1)
softlinkFromTemplate := regLink.FindAllStringSubmatch(line, -1)
if len(softlinkFromTemplate) != 0 {
wwlog.Debug("Creating soft link %s -> %s", outputPath, softlinkFromTemplate[0][1])
return os.Symlink(softlinkFromTemplate[0][1], outputPath)
} else if len(filenameFromTemplate) != 0 {
wwlog.Debug("Writing file %s", filenameFromTemplate[0][1])
if foundFileComment {
err = CarefulWriteBuffer(outputPath, fileBuffer, backupFile, info.Mode())
if err != nil {
return fmt.Errorf("could not write file from template: %w", err)
}
destFileName = path.Join(path.Dir(destFile), filenameFromTemplate[0][1])
foundFileComment = true
} else {
_, _ = fileBuffer.WriteString(line)
err = util.CopyUIDGID(walkPath, outputPath)
if err != nil {
return fmt.Errorf("failed setting permissions on template output file: %w", err)
}
fileBuffer.Reset()
}
outputPath = path.Join(path.Dir(originalOutputPath), filenameFromTemplate[0][1])
foundFileComment = true
} else {
if _, err = fileBuffer.WriteString(line); err != nil {
return fmt.Errorf("could not write to template buffer: %w", err)
}
}
err = CarefulWriteBuffer(path.Join(outputDir, destFileName), fileBuffer, backupFile, info.Mode())
if err != nil {
return fmt.Errorf("could not write file from template: %w", err)
}
err = util.CopyUIDGID(location, path.Join(outputDir, destFileName))
if err != nil {
return fmt.Errorf("failed setting permissions on template output file: %w", err)
}
wwlog.Debug("Wrote template file into overlay: %s", destFile)
// } else if b, _ := regexp.MatchString(`\.ww[a-zA-Z0-9\-\._]*$`, location); b {
// wwlog.Debug("Ignoring WW template file: %s", location)
}
err = CarefulWriteBuffer(outputPath, fileBuffer, backupFile, info.Mode())
if err != nil {
return fmt.Errorf("could not write file from template: %w", err)
}
err = util.CopyUIDGID(walkPath, outputPath)
if err != nil {
return fmt.Errorf("failed setting permissions on template output file: %w", err)
}
wwlog.Debug("Wrote template file into overlay: %s", outputPath)
} else if info.Mode()&os.ModeSymlink == os.ModeSymlink {
wwlog.Debug("Found symlink %s", location)
destination, err := os.Readlink(location)
wwlog.Debug("Found symlink %s", walkPath)
target, err := os.Readlink(walkPath)
if err != nil {
wwlog.ErrorExc(err, "")
return fmt.Errorf("failed reading symlink: %w", err)
}
if util.IsFile(path.Join(outputDir, location)) {
if !util.IsFile(path.Join(outputDir, location+".wwbackup")) {
wwlog.Debug("Target exists, creating backup file")
err = os.Rename(path.Join(outputDir, location), path.Join(outputDir, location+".wwbackup"))
if util.IsFile(outputPath) {
backupPath := outputPath + ".wwbackup"
if !util.IsFile(backupPath) {
wwlog.Debug("Output file already exists: moving to backup file")
if err = os.Rename(outputPath, backupPath); err != nil {
return fmt.Errorf("failed renaming to backup file: %w", err)
}
} else {
wwlog.Debug("%s exists, keeping the backup file", path.Join(outputDir, location+".wwbackup"))
err = os.Remove(path.Join(outputDir, location))
}
if err != nil {
wwlog.ErrorExc(err, "")
wwlog.Debug("%s exists, keeping the backup file", backupPath)
if err = os.Remove(outputPath); err != nil {
return fmt.Errorf("failed removing existing file: %w", err)
}
}
}
err = os.Symlink(destination, path.Join(outputDir, location))
if err != nil {
wwlog.ErrorExc(err, "")
if err = os.Symlink(target, outputPath); err != nil {
return fmt.Errorf("failed creating symlink: %w", err)
}
wwlog.Debug("Created symlink file: %s", outputPath)
} else {
err := util.CopyFile(location, path.Join(outputDir, location))
if err == nil {
wwlog.Debug("Copied file into overlay: %s", location)
} else {
if err := util.CopyFile(walkPath, outputPath); err != nil {
return fmt.Errorf("could not copy file into overlay: %w", err)
}
wwlog.Debug("Copied overlay file: %s", outputPath)
}
return nil
})
if err != nil {
return fmt.Errorf("failed to build overlay working directory: %w", err)
return fmt.Errorf("failed to build overlay image directory: %w", err)
}
}

View File

@@ -1,6 +1,7 @@
package overlay
import (
"fmt"
"io"
"os"
"path"
@@ -11,7 +12,6 @@ import (
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
"github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/testenv"
"github.com/warewulf/warewulf/internal/pkg/util"
)
@@ -110,122 +110,263 @@ func Test_OverlayMethods(t *testing.T) {
}
}
var buildOverlayTests = []struct {
description string
nodeName string
context string
overlays []string
image string
contents []string
hasFiles bool
}{
{
description: "if no node, context, or overlays are specified then no overlay image is generated",
nodeName: "",
context: "",
overlays: nil,
image: "",
contents: nil,
},
{
description: "if only node is specified then no overlay image is generated",
nodeName: "node1",
context: "",
overlays: nil,
image: "",
contents: nil,
},
{
description: "if only context is specified then context named image is generated",
nodeName: "",
context: "system",
overlays: nil,
image: "__SYSTEM__.img",
contents: nil,
},
{
description: "if an overlay is specified without a node, then the overlay is built directly in the overlay directory",
nodeName: "",
context: "",
overlays: []string{"o1"},
image: "o1.img",
contents: []string{"o1.txt"},
},
{
description: "if multiple overlays are specified without a node, then the combined overlay is built directly in the overlay directory",
nodeName: "",
context: "",
overlays: []string{"o1", "o2"},
image: "o1-o2.img",
contents: []string{"o1.txt", "o2.txt"},
},
{
description: "if a single node overlay is specified, then the overlay is built in a node overlay directory",
nodeName: "node1",
context: "",
overlays: []string{"o1"},
image: "node1/o1.img",
contents: []string{"o1.txt"},
},
{
description: "if multiple node overlays are specified, then the combined overlay is built in a node overlay directory",
nodeName: "node1",
context: "",
overlays: []string{"o1", "o2"},
image: "node1/o1-o2.img",
contents: []string{"o1.txt", "o2.txt"},
},
{
description: "if no node system overlays are specified, then context pointed overlay is generated",
nodeName: "node1",
context: "system",
overlays: nil,
image: "node1/__SYSTEM__.img",
contents: nil,
},
{
description: "if no node runtime overlays are specified, then context pointed overlay is generated",
nodeName: "node1",
context: "runtime",
overlays: nil,
image: "node1/__RUNTIME__.img",
contents: nil,
},
{
description: "if a single node system overlay is specified, then a system overlay image is generated in a node overlay directory",
nodeName: "node1",
context: "system",
overlays: []string{"o1"},
image: "node1/__SYSTEM__.img",
contents: []string{"o1.txt"},
},
{
description: "if a single node runtime overlay is specified, then a runtime overlay image is generated in a node overlay directory",
nodeName: "node1",
context: "runtime",
overlays: []string{"o1"},
image: "node1/__RUNTIME__.img",
contents: []string{"o1.txt"},
},
{
description: "if multiple node system overlays are specified, then a system overlay image is generated with the contents of both overlays",
nodeName: "node1",
context: "system",
overlays: []string{"o1", "o2"},
image: "node1/__SYSTEM__.img",
contents: []string{"o1.txt", "o2.txt"},
},
{
description: "if multiple node runtime overlays are specified, then a runtime overlay image is generated with the contents of both overlays",
nodeName: "node1",
context: "runtime",
overlays: []string{"o1", "o2"},
image: "node1/__RUNTIME__.img",
contents: []string{"o1.txt", "o2.txt"},
},
func Test_BuildOverlayIndir(t *testing.T) {
var tests = map[string]struct {
node node.Node
overlays []string
overlayFiles map[string]string
overlayDirs []string
overlaySymlinks map[string]string
outputDir string
outputFiles map[string]string
outputDirs []string
outputSymlinks map[string]string
}{
"empty": {
outputDir: "/image",
},
"empty directory": {
overlays: []string{"o1"},
overlayDirs: []string{"/var/lib/warewulf/overlays/o1/rootfs/testdir"},
outputDir: "/image",
outputDirs: []string{"testdir"},
},
"single flat file": {
overlays: []string{"o1"},
overlayFiles: map[string]string{
"/var/lib/warewulf/overlays/o1/rootfs/testfile": "A test file from o1",
},
outputDir: "/image",
outputFiles: map[string]string{
"testfile": "A test file from o1",
},
},
"multiple overlays": {
overlays: []string{"o1", "o2"},
overlayFiles: map[string]string{
"/var/lib/warewulf/overlays/o1/rootfs/t1.txt": "A test file from o1",
"/var/lib/warewulf/overlays/o2/rootfs/t2.txt": "A test file from o2",
},
outputDir: "/image",
outputFiles: map[string]string{
"t1.txt": "A test file from o1",
"t2.txt": "A test file from o2",
},
},
"template": {
node: node.Node{Profile: node.Profile{Comment: "A node comment"}},
overlays: []string{"o1"},
overlayFiles: map[string]string{
"/var/lib/warewulf/overlays/o1/rootfs/t1.txt.ww": "{{ .Comment }}",
"/image/t1.txt": "Previous content",
},
outputDir: "/image",
outputFiles: map[string]string{
"t1.txt": "A node comment",
"t1.txt.wwbackup": "Previous content",
},
},
"multifile": {
overlays: []string{"o1"},
overlayFiles: map[string]string{
"/var/lib/warewulf/overlays/o1/rootfs/file.txt.ww": `
{{- file "t1.txt" }}
T1
{{ file "t2.txt" }}
T2
{{ file "t3.txt" }}
T3
`,
},
outputDir: "/image",
outputFiles: map[string]string{
"t1.txt": "T1\n",
"t2.txt": "T2\n",
"t3.txt": "T3\n",
},
},
"symlink": {
overlays: []string{"o1"},
overlaySymlinks: map[string]string{
"/var/lib/warewulf/overlays/o1/rootfs/test-link": "/test-target",
},
outputDir: "/image",
outputSymlinks: map[string]string{
"test-link": "/test-target",
},
},
"symlink from template": {
overlays: []string{"o1"},
overlayFiles: map[string]string{
"/var/lib/warewulf/overlays/o1/rootfs/test-link.ww": `
{{- softlink "/test-target" }}
`,
},
outputDir: "/image",
outputSymlinks: map[string]string{
"test-link": "/test-target",
},
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
env := testenv.New(t)
defer env.RemoveAll(t)
for fileName, content := range tt.overlayFiles {
env.WriteFile(t, fileName, content)
}
for _, dirName := range tt.overlayDirs {
env.MkdirAll(t, dirName)
}
for linkName, target := range tt.overlaySymlinks {
env.Symlink(t, target, linkName)
}
env.MkdirAll(t, tt.outputDir)
assert.NoError(t, BuildOverlayIndir(tt.node, tt.overlays, env.GetPath(tt.outputDir)))
dirFiles := tt.outputDirs
for outputFile, _ := range tt.outputFiles {
dirFiles = append(dirFiles, outputFile)
}
for outputFile, _ := range tt.outputSymlinks {
dirFiles = append(dirFiles, outputFile)
}
sort.Strings(dirFiles)
assert.Equal(t, dirFiles, env.ReadDir(t, tt.outputDir))
for fileName, content := range tt.outputFiles {
assert.Equal(t, content, env.ReadFile(t, path.Join(tt.outputDir, fileName)))
}
for _, dirName := range tt.outputDirs {
assert.True(t, util.IsDir(env.GetPath(path.Join(tt.outputDir, dirName))), fmt.Sprintf("%s is not a directory", dirName))
}
for linkName, expTarget := range tt.outputSymlinks {
target, err := os.Readlink(env.GetPath(path.Join(tt.outputDir, linkName)))
assert.NoError(t, err)
assert.Equal(t, expTarget, target)
}
})
}
}
func Test_BuildOverlay(t *testing.T) {
var tests = []struct {
description string
nodeName string
context string
overlays []string
image string
contents []string
hasFiles bool
}{
{
description: "if no node, context, or overlays are specified then no overlay image is generated",
nodeName: "",
context: "",
overlays: nil,
image: "",
contents: nil,
},
{
description: "if only node is specified then no overlay image is generated",
nodeName: "node1",
context: "",
overlays: nil,
image: "",
contents: nil,
},
{
description: "if only context is specified then context named image is generated",
nodeName: "",
context: "system",
overlays: nil,
image: "__SYSTEM__.img",
contents: nil,
},
{
description: "if an overlay is specified without a node, then the overlay is built directly in the overlay directory",
nodeName: "",
context: "",
overlays: []string{"o1"},
image: "o1.img",
contents: []string{"o1.txt"},
},
{
description: "if multiple overlays are specified without a node, then the combined overlay is built directly in the overlay directory",
nodeName: "",
context: "",
overlays: []string{"o1", "o2"},
image: "o1-o2.img",
contents: []string{"o1.txt", "o2.txt"},
},
{
description: "if a single node overlay is specified, then the overlay is built in a node overlay directory",
nodeName: "node1",
context: "",
overlays: []string{"o1"},
image: "node1/o1.img",
contents: []string{"o1.txt"},
},
{
description: "if multiple node overlays are specified, then the combined overlay is built in a node overlay directory",
nodeName: "node1",
context: "",
overlays: []string{"o1", "o2"},
image: "node1/o1-o2.img",
contents: []string{"o1.txt", "o2.txt"},
},
{
description: "if no node system overlays are specified, then context pointed overlay is generated",
nodeName: "node1",
context: "system",
overlays: nil,
image: "node1/__SYSTEM__.img",
contents: nil,
},
{
description: "if no node runtime overlays are specified, then context pointed overlay is generated",
nodeName: "node1",
context: "runtime",
overlays: nil,
image: "node1/__RUNTIME__.img",
contents: nil,
},
{
description: "if a single node system overlay is specified, then a system overlay image is generated in a node overlay directory",
nodeName: "node1",
context: "system",
overlays: []string{"o1"},
image: "node1/__SYSTEM__.img",
contents: []string{"o1.txt"},
},
{
description: "if a single node runtime overlay is specified, then a runtime overlay image is generated in a node overlay directory",
nodeName: "node1",
context: "runtime",
overlays: []string{"o1"},
image: "node1/__RUNTIME__.img",
contents: []string{"o1.txt"},
},
{
description: "if multiple node system overlays are specified, then a system overlay image is generated with the contents of both overlays",
nodeName: "node1",
context: "system",
overlays: []string{"o1", "o2"},
image: "node1/__SYSTEM__.img",
contents: []string{"o1.txt", "o2.txt"},
},
{
description: "if multiple node runtime overlays are specified, then a runtime overlay image is generated with the contents of both overlays",
nodeName: "node1",
context: "runtime",
overlays: []string{"o1", "o2"},
image: "node1/__RUNTIME__.img",
contents: []string{"o1.txt", "o2.txt"},
},
}
conf := warewulfconf.Get()
overlayDir, overlayDirErr := os.MkdirTemp(os.TempDir(), "ww-test-overlay-*")
assert.NoError(t, overlayDirErr)
@@ -242,7 +383,7 @@ func Test_BuildOverlay(t *testing.T) {
assert.NoError(t, err)
}
for _, tt := range buildOverlayTests {
for _, tt := range tests {
nodeInfo := node.NewNode(tt.nodeName)
t.Run(tt.description, func(t *testing.T) {
provisionDir, provisionDirErr := os.MkdirTemp(os.TempDir(), "ww-test-provision-*")
@@ -269,83 +410,79 @@ func Test_BuildOverlay(t *testing.T) {
}
}
// Although these tests specify system and runtime overlays for the
// nodes, these overlays define the overlays that are defined in the
// configuration. BuildAllOverlays doesn't receive these as arguments,
// but builds all the overlays that are configured on the given node.
var buildAllOverlaysTests = []struct {
description string
nodes []string
systemOverlays [][]string
runtimeOverlays [][]string
createdOverlays []string
}{
{
description: "empty input creates no overlays",
nodes: nil,
systemOverlays: nil,
runtimeOverlays: nil,
createdOverlays: nil,
},
{
description: "a node with no overlays creates default system/runtime overlays",
nodes: []string{"node1"},
systemOverlays: [][]string{{"o1"}},
runtimeOverlays: [][]string{{"o1"}},
createdOverlays: []string{"node1/__SYSTEM__.img.gz", "node1/__RUNTIME__.img.gz"},
},
{
description: "multiple nodes with no overlays creates default system/runtime overlays",
nodes: []string{"node1", "node2"},
systemOverlays: [][]string{{"o1"}, {"o2"}},
runtimeOverlays: [][]string{{"o1"}, {"o2"}},
createdOverlays: []string{"node1/__SYSTEM__.img.gz", "node1/__RUNTIME__.img.gz", "node2/__SYSTEM__.img.gz", "node2/__RUNTIME__.img.gz"},
},
{
description: "a system overlay for a node generates a system overlay for that node",
nodes: []string{"node1"},
systemOverlays: [][]string{{"o1"}},
runtimeOverlays: nil,
createdOverlays: []string{"node1/__SYSTEM__.img.gz"},
},
{
description: "two nodes with different system overlays generates a system overlay for each node",
nodes: []string{"node1", "node2"},
systemOverlays: [][]string{{"o1"}, {"o1", "o2"}},
runtimeOverlays: nil,
createdOverlays: []string{"node1/__SYSTEM__.img.gz", "node2/__SYSTEM__.img.gz"},
},
{
description: "two nodes with a single runtime overlay generates a runtime overlay for the first node",
nodes: []string{"node1"},
systemOverlays: nil,
runtimeOverlays: [][]string{{"o1"}},
createdOverlays: []string{"node1/__RUNTIME__.img.gz"},
},
{
description: "two nodes with different runtime overlays generates a system overlay for each node",
nodes: []string{"node1", "node2"},
systemOverlays: nil,
runtimeOverlays: [][]string{{"o1"}, {"o1", "o2"}},
createdOverlays: []string{"node1/__RUNTIME__.img.gz", "node2/__RUNTIME__.img.gz"},
},
{
description: "a node with both a runtime and system overlay generates an image for each",
nodes: []string{"node1"},
systemOverlays: [][]string{{"o1"}},
runtimeOverlays: [][]string{{"o2"}},
createdOverlays: []string{"node1/__RUNTIME__.img.gz", "node1/__SYSTEM__.img.gz"},
},
{
description: "two nodes with both runtime and system overlays generates each image for each node",
nodes: []string{"node1", "node2"},
systemOverlays: [][]string{{"o1"}, {"o1", "o2"}},
runtimeOverlays: [][]string{{"o2"}, {"o2"}},
createdOverlays: []string{"node1/__RUNTIME__.img.gz", "node1/__SYSTEM__.img.gz", "node2/__RUNTIME__.img.gz", "node2/__SYSTEM__.img.gz"},
},
}
func Test_BuildAllOverlays(t *testing.T) {
var tests = []struct {
description string
nodes []string
systemOverlays [][]string
runtimeOverlays [][]string
createdOverlays []string
}{
{
description: "empty input creates no overlays",
nodes: nil,
systemOverlays: nil,
runtimeOverlays: nil,
createdOverlays: nil,
},
{
description: "a node with no overlays creates default system/runtime overlays",
nodes: []string{"node1"},
systemOverlays: [][]string{{"o1"}},
runtimeOverlays: [][]string{{"o1"}},
createdOverlays: []string{"node1/__SYSTEM__.img.gz", "node1/__RUNTIME__.img.gz"},
},
{
description: "multiple nodes with no overlays creates default system/runtime overlays",
nodes: []string{"node1", "node2"},
systemOverlays: [][]string{{"o1"}, {"o2"}},
runtimeOverlays: [][]string{{"o1"}, {"o2"}},
createdOverlays: []string{"node1/__SYSTEM__.img.gz", "node1/__RUNTIME__.img.gz", "node2/__SYSTEM__.img.gz", "node2/__RUNTIME__.img.gz"},
},
{
description: "a system overlay for a node generates a system overlay for that node",
nodes: []string{"node1"},
systemOverlays: [][]string{{"o1"}},
runtimeOverlays: nil,
createdOverlays: []string{"node1/__SYSTEM__.img.gz"},
},
{
description: "two nodes with different system overlays generates a system overlay for each node",
nodes: []string{"node1", "node2"},
systemOverlays: [][]string{{"o1"}, {"o1", "o2"}},
runtimeOverlays: nil,
createdOverlays: []string{"node1/__SYSTEM__.img.gz", "node2/__SYSTEM__.img.gz"},
},
{
description: "two nodes with a single runtime overlay generates a runtime overlay for the first node",
nodes: []string{"node1"},
systemOverlays: nil,
runtimeOverlays: [][]string{{"o1"}},
createdOverlays: []string{"node1/__RUNTIME__.img.gz"},
},
{
description: "two nodes with different runtime overlays generates a system overlay for each node",
nodes: []string{"node1", "node2"},
systemOverlays: nil,
runtimeOverlays: [][]string{{"o1"}, {"o1", "o2"}},
createdOverlays: []string{"node1/__RUNTIME__.img.gz", "node2/__RUNTIME__.img.gz"},
},
{
description: "a node with both a runtime and system overlay generates an image for each",
nodes: []string{"node1"},
systemOverlays: [][]string{{"o1"}},
runtimeOverlays: [][]string{{"o2"}},
createdOverlays: []string{"node1/__RUNTIME__.img.gz", "node1/__SYSTEM__.img.gz"},
},
{
description: "two nodes with both runtime and system overlays generates each image for each node",
nodes: []string{"node1", "node2"},
systemOverlays: [][]string{{"o1"}, {"o1", "o2"}},
runtimeOverlays: [][]string{{"o2"}, {"o2"}},
createdOverlays: []string{"node1/__RUNTIME__.img.gz", "node1/__SYSTEM__.img.gz", "node2/__RUNTIME__.img.gz", "node2/__SYSTEM__.img.gz"},
},
}
conf := warewulfconf.Get()
overlayDir, overlayDirErr := os.MkdirTemp(os.TempDir(), "ww-test-overlay-*")
assert.NoError(t, overlayDirErr)
@@ -354,7 +491,7 @@ func Test_BuildAllOverlays(t *testing.T) {
assert.NoError(t, os.Mkdir(path.Join(overlayDir, "o1"), 0700))
assert.NoError(t, os.Mkdir(path.Join(overlayDir, "o2"), 0700))
for _, tt := range buildAllOverlaysTests {
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
provisionDir, provisionDirErr := os.MkdirTemp(os.TempDir(), "ww-test-provision-*")
assert.NoError(t, provisionDirErr)
@@ -385,65 +522,65 @@ func Test_BuildAllOverlays(t *testing.T) {
}
}
var buildSpecificOverlaysTests = []struct {
description string
nodes []string
overlays []string
images []string
succeed bool
}{
{
description: "building no overlays for no nodes generates no error and no images",
nodes: nil,
overlays: nil,
images: nil,
succeed: true,
},
{
description: "building no overlays for a node generates no error and no images",
nodes: []string{"node1"},
overlays: nil,
images: nil,
succeed: true,
},
{
description: "building no overlays for two nodes generates no error and no images",
nodes: []string{"node1", "node2"},
overlays: nil,
images: nil,
succeed: true,
},
{
description: "building an overlay for a node generates an overlay image in that node's overlay directory",
nodes: []string{"node1"},
overlays: []string{"o1"},
images: []string{"node1/o1.img"},
succeed: true,
},
{
description: "building an overlay for two nodes generates an overlay image in each node's overlay directory",
nodes: []string{"node1", "node2"},
overlays: []string{"o1"},
images: []string{"node1/o1.img", "node2/o1.img"},
succeed: true,
},
{
description: "building multiple overlays for a node generates an overlay image for each overlay in the node's overlay directory",
nodes: []string{"node1"},
overlays: []string{"o1", "o2"},
images: []string{"node1/o1.img", "node1/o2.img"},
succeed: true,
},
{
description: "building multiple overlays for two nodes generates an overlay image for each overlay in each node's overlay directory",
nodes: []string{"node1", "node2"},
overlays: []string{"o1", "o2"},
images: []string{"node1/o1.img", "node1/o2.img", "node2/o1.img", "node2/o2.img"},
succeed: true,
},
}
func Test_BuildSpecificOverlays(t *testing.T) {
var tests = []struct {
description string
nodes []string
overlays []string
images []string
succeed bool
}{
{
description: "building no overlays for no nodes generates no error and no images",
nodes: nil,
overlays: nil,
images: nil,
succeed: true,
},
{
description: "building no overlays for a node generates no error and no images",
nodes: []string{"node1"},
overlays: nil,
images: nil,
succeed: true,
},
{
description: "building no overlays for two nodes generates no error and no images",
nodes: []string{"node1", "node2"},
overlays: nil,
images: nil,
succeed: true,
},
{
description: "building an overlay for a node generates an overlay image in that node's overlay directory",
nodes: []string{"node1"},
overlays: []string{"o1"},
images: []string{"node1/o1.img"},
succeed: true,
},
{
description: "building an overlay for two nodes generates an overlay image in each node's overlay directory",
nodes: []string{"node1", "node2"},
overlays: []string{"o1"},
images: []string{"node1/o1.img", "node2/o1.img"},
succeed: true,
},
{
description: "building multiple overlays for a node generates an overlay image for each overlay in the node's overlay directory",
nodes: []string{"node1"},
overlays: []string{"o1", "o2"},
images: []string{"node1/o1.img", "node1/o2.img"},
succeed: true,
},
{
description: "building multiple overlays for two nodes generates an overlay image for each overlay in each node's overlay directory",
nodes: []string{"node1", "node2"},
overlays: []string{"o1", "o2"},
images: []string{"node1/o1.img", "node1/o2.img", "node2/o1.img", "node2/o2.img"},
succeed: true,
},
}
conf := warewulfconf.Get()
overlayDir, overlayDirErr := os.MkdirTemp(os.TempDir(), "ww-test-overlay-*")
assert.NoError(t, overlayDirErr)
@@ -452,7 +589,7 @@ func Test_BuildSpecificOverlays(t *testing.T) {
assert.NoError(t, os.Mkdir(path.Join(overlayDir, "o1"), 0700))
assert.NoError(t, os.Mkdir(path.Join(overlayDir, "o2"), 0700))
for _, tt := range buildSpecificOverlaysTests {
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
provisionDir, provisionDirErr := os.MkdirTemp(os.TempDir(), "ww-test-provision-*")
assert.NoError(t, provisionDirErr)

View File

@@ -140,12 +140,22 @@ func (env *TestEnv) ImportFile(t *testing.T, fileName string, inputFileName stri
env.WriteFile(t, fileName, string(buffer))
}
// CreateFile creates an empty file to fileName, creating any necessary intermediate directories
// CreateFile creates an empty file at fileName, creating any necessary intermediate directories
// relative to the test environment.
func (env *TestEnv) CreateFile(t *testing.T, fileName string) {
env.WriteFile(t, fileName, "")
}
// Symlink creates a symlink at fileName to target, creating any necessary intermediate directories
// relative to the test environment.
func (env *TestEnv) Symlink(t *testing.T, target string, fileName string) {
dirName := filepath.Dir(fileName)
env.MkdirAll(t, dirName)
err := os.Symlink(target, env.GetPath(fileName))
assert.NoError(t, err)
}
// ReadFile returns the content of fileName as converted to a
// string.
//
@@ -156,6 +166,20 @@ func (env *TestEnv) ReadFile(t *testing.T, fileName string) string {
return string(buffer)
}
// ReadDir returns the content of dirName as converted to a
// slice of strings.
//
// Asserts no errors occur.
func (env *TestEnv) ReadDir(t *testing.T, dirName string) []string {
entries, err := os.ReadDir(env.GetPath(dirName))
assert.NoError(t, err)
var entryStrs []string
for _, entry := range entries {
entryStrs = append(entryStrs, entry.Name())
}
return entryStrs
}
// RemoveAll deletes the temporary directory, and all its contents,
// for the test environment.
//

View File

@@ -45,9 +45,7 @@ func sendFile(
}
func getOverlayFile(n node.Node, context string, stage_overlays []string, autobuild bool) (stage_file string, err error) {
stage_file = overlay.OverlayImage(n.Id(), context, stage_overlays)
err = nil
build := !util.IsFile(stage_file)
wwlog.Verbose("stage file: %s", stage_file)
if !build && autobuild {