package clean import ( "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/warewulf/warewulf/internal/pkg/testenv" ) func Test_CleanOverlays(t *testing.T) { tests := map[string]struct { nodesConf string // nodes.conf content overlayDirs []string // directories to create under overlays/ overlayFiles []string // files to create under overlays/ (should be skipped) skipOverlaySetup bool // skip creating overlay directory structure wantPreserved []string // overlay dirs that should exist after cleanup wantDeleted []string // overlay dirs that should not exist after cleanup wantErr bool // expect error from CleanOverlays }{ "preserves valid node, deletes orphaned node": { nodesConf: `nodeprofiles: {} nodes: node1: {}`, overlayDirs: []string{"node1", "node2"}, wantPreserved: []string{"node1"}, wantDeleted: []string{"node2"}, wantErr: false, }, "empty overlay directory": { nodesConf: `nodeprofiles: {} nodes: node1: {}`, overlayDirs: []string{}, wantPreserved: []string{}, wantDeleted: []string{}, wantErr: false, }, "skips files in overlay directory": { nodesConf: `nodeprofiles: {} nodes: node1: {}`, overlayDirs: []string{"node1"}, overlayFiles: []string{"somefile.txt", "another.img"}, wantPreserved: []string{"node1"}, wantDeleted: []string{}, wantErr: false, }, "multiple orphaned nodes deleted": { nodesConf: `nodeprofiles: {} nodes: node1: {}`, overlayDirs: []string{"node1", "node2", "node3", "node4"}, wantPreserved: []string{"node1"}, wantDeleted: []string{"node2", "node3", "node4"}, wantErr: false, }, "all nodes preserved when all exist in database": { nodesConf: `nodeprofiles: {} nodes: node1: {} node2: {} node3: {}`, overlayDirs: []string{"node1", "node2", "node3"}, wantPreserved: []string{"node1", "node2", "node3"}, wantDeleted: []string{}, wantErr: false, }, "empty node database deletes all overlays": { nodesConf: `nodeprofiles: {} nodes: {}`, overlayDirs: []string{"node1", "node2"}, wantPreserved: []string{}, wantDeleted: []string{"node1", "node2"}, wantErr: false, }, "error when nodes.conf is invalid": { nodesConf: `this is not valid YAML: [[[`, overlayDirs: []string{"node1"}, wantErr: true, }, "error when overlay directory is missing": { nodesConf: `nodeprofiles: {} nodes: node1: {}`, skipOverlaySetup: true, wantErr: true, }, "cleans directories with names starting with .. (not actual traversal)": { nodesConf: `nodeprofiles: {} nodes: node1: {}`, overlayDirs: []string{"node1", "..suspicious", "..hidden"}, // These are just oddly-named directories, not actual path traversal // They remain inside baseDir and should be cleaned up normally wantPreserved: []string{"node1"}, wantDeleted: []string{"..suspicious", "..hidden"}, wantErr: false, }, "handles directory names with dots correctly": { nodesConf: `nodeprofiles: {} nodes: node1: {} node.valid: {}`, overlayDirs: []string{"node1", "node.valid", "node.orphaned"}, wantPreserved: []string{"node1", "node.valid"}, wantDeleted: []string{"node.orphaned"}, wantErr: false, }, "handles edge case directory names with multiple dots": { nodesConf: `nodeprofiles: {} nodes: validnode: {}`, // Directory names like "..test" and "...triple" are just unusual names, // not actual path traversal - they stay within baseDir and can be safely deleted overlayDirs: []string{"validnode", "..test", "...triple"}, wantPreserved: []string{"validnode"}, wantDeleted: []string{"..test", "...triple"}, wantErr: false, }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { env := testenv.New(t) defer env.RemoveAll() // Setup nodes.conf if tt.nodesConf != "" { env.WriteFile("etc/warewulf/nodes.conf", tt.nodesConf) } // Setup overlay directory structure (unless explicitly skipped) if !tt.skipOverlaySetup { // Ensure the overlay directory exists (even if empty) env.MkdirAll("srv/warewulf/overlays") // Setup overlay directories for _, dir := range tt.overlayDirs { // Create a file inside each directory to make it a proper overlay dir env.WriteFile(filepath.Join("srv/warewulf/overlays", dir, "__SYSTEM__.img"), "Fake System") } // Setup overlay files (non-directories) for _, file := range tt.overlayFiles { env.WriteFile(filepath.Join("srv/warewulf/overlays", file), "test content") } } else { // For tests that skip setup, remove the overlay directory overlayPath := env.GetPath("srv/warewulf/overlays") assert.NoError(t, os.RemoveAll(overlayPath)) } // Execute err := CleanOverlays() // Assert if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) // Verify preserved directories exist for _, dir := range tt.wantPreserved { dirPath := env.GetPath(filepath.Join("srv/warewulf/overlays", dir)) assert.DirExists(t, dirPath, "expected directory %s to be preserved", dir) } // Verify deleted directories do not exist for _, dir := range tt.wantDeleted { dirPath := env.GetPath(filepath.Join("srv/warewulf/overlays", dir)) assert.NoDirExists(t, dirPath, "expected directory %s to be deleted", dir) } // Verify files were not deleted (when specified) for _, file := range tt.overlayFiles { filePath := env.GetPath(filepath.Join("srv/warewulf/overlays", file)) assert.FileExists(t, filePath, "expected file %s to be skipped", file) } } }) } }