Refactor ignition support during dracut

- Control root device using the node/profile root field
- Add support for `/warewulf/wwinit.d` to run scripts during first stage
- Move first-stage ignition support to `/warewulf/wwinit.d/`
- Add `wwctl <node|profile> set --parttype`
- Add overlay template functions `SystemdEscape` and `SystemdEscapePath`
- Support configuring ignition with resources

Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
Jonathon Anderson
2025-06-30 23:09:49 -06:00
parent 608ef89d40
commit b98eae4b01
29 changed files with 787 additions and 354 deletions

View File

@@ -194,3 +194,16 @@ func (config *NodesYaml) FindDiscoverableNode() (Node, string, error) {
return EmptyNode(), "", ErrNoUnconfigured
}
func (node *Node) SetIds(id string) {
node.id = id
for diskId, disk := range node.Disks {
disk.id = diskId
for partitionId, partition := range disk.Partitions {
partition.id = partitionId
}
}
for fsId, fs := range node.FileSystems {
fs.id = fsId
}
}

View File

@@ -92,6 +92,7 @@ type NetDev struct {
Holds the disks of a node
*/
type Disk struct {
id string `yaml:"-" json:"-"`
WipeTable bool `yaml:"wipe_table,omitempty" json:"wipe_table,omitempty" lopt:"diskwipe" comment:"whether or not the partition tables shall be wiped"`
Partitions map[string]*Partition `yaml:"partitions,omitempty" json:"partitions,omitempty"`
}
@@ -101,13 +102,14 @@ partition definition, the label must be uniq so its used as the key in the
Partitions map
*/
type Partition struct {
Number string `yaml:"number,omitempty" json:"number,omitempty" lopt:"partnumber" comment:"set the partition number, if not set next free slot is used" type:"uint"`
SizeMiB string `yaml:"size_mib,omitempty" json:"size_mib,omitempty" lopt:"partsize" comment:"set the size of the partition, if not set maximal possible size is used" type:"uint"`
id string `yaml:"-" json:"-"`
Number string `yaml:"number,omitempty" json:"number,omitempty" lopt:"partnumber" comment:"Set the partition number, if not set next free slot is used" type:"uint"`
SizeMiB string `yaml:"size_mib,omitempty" json:"size_mib,omitempty" lopt:"partsize" comment:"Set the size of the partition, if not set maximal possible size is used" type:"uint"`
StartMiB string `yaml:"start_mib,omitempty" json:"start_mib,omitempty" comment:"the start of the partition" type:"uint"`
TypeGuid string `yaml:"type_guid,omitempty" json:"type_guid,omitempty" comment:"Linux filesystem data will be used if empty"`
TypeGuid string `yaml:"type_guid,omitempty" json:"type_guid,omitempty" lopt:"parttype" comment:"Set the partition type GUID"`
Guid string `yaml:"guid,omitempty" json:"guid,omitempty" comment:"the GPT unique partition GUID"`
WipePartitionEntry bool `yaml:"wipe_partition_entry,omitempty" json:"wipe_partition_entry,omitempty" comment:"if true, Ignition will clobber an existing partition if it does not match the config"`
ShouldExist bool `yaml:"should_exist,omitempty" json:"should_exist,omitempty" lopt:"partcreate" comment:"create partition if not exist"`
ShouldExist bool `yaml:"should_exist,omitempty" json:"should_exist,omitempty" lopt:"partcreate" comment:"Create partition if it does not exist"`
Resize bool `yaml:"resize,omitempty" json:"resize,omitempty" comment:"whether or not the existing partition should be resize"`
}
@@ -115,6 +117,7 @@ type Partition struct {
Definition of a filesystem. The device is uniq so its used as key
*/
type FileSystem struct {
id string `yaml:"-" json:"-"`
Format string `yaml:"format,omitempty" json:"format,omitempty" lopt:"fsformat" comment:"format of the file system"`
Path string `yaml:"path,omitempty" json:"path,omitempty" lopt:"fspath" comment:"the mount point of the file system"`
WipeFileSystem bool `yaml:"wipe_filesystem,omitempty" json:"wipe_filesystem,omitempty" lopt:"fswipe" comment:"wipe file system at boot"`

View File

@@ -157,7 +157,7 @@ func (config *NodesYaml) MergeNode(id string) (node Node, fields fieldMap, err e
delete(fields, "Profiles")
}
node.id = id
node.SetIds(id)
node.valid = true
node.updatePrimaryNetDev()
node.Profile.cleanLists()

View File

@@ -10,10 +10,97 @@ import (
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func (node *Node) DiskList() (disks []*Disk) {
names := make([]string, 0, len(node.Disks))
for name := range node.Disks {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
disk := node.Disks[name]
if disk != nil {
disks = append(disks, disk)
}
}
return disks
}
func (node *Node) FileSystemList() (fs []*FileSystem) {
names := make([]string, 0, len(node.FileSystems))
for name := range node.FileSystems {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
fsys := node.FileSystems[name]
if fsys != nil {
fs = append(fs, fsys)
}
}
return fs
}
func (disk *Disk) Id() string {
return disk.id
}
func (disk *Disk) PartitionList() (partitions []*Partition) {
names := make([]string, 0, len(disk.Partitions))
for name := range disk.Partitions {
names = append(names, name)
}
sort.SliceStable(names, func(i, j int) bool {
pi := disk.Partitions[names[i]]
pj := disk.Partitions[names[j]]
// Try to compare by Number (as int)
ni, erri := strconv.Atoi(pi.Number)
nj, errj := strconv.Atoi(pj.Number)
if erri == nil && errj == nil {
if ni != nj {
return ni < nj
}
} else if erri == nil {
return true // i has a number, j does not
} else if errj == nil {
return false // j has a number, i does not
}
// Next, compare by id if set
if pi.id != "" && pj.id != "" {
if pi.id != pj.id {
return pi.id < pj.id
}
} else if pi.id != "" {
return true // i has id, j does not
} else if pj.id != "" {
return false // j has id, i does not
}
// Fallback: compare by name (map key)
return names[i] < names[j]
})
for _, name := range names {
part := disk.Partitions[name]
if part != nil {
partitions = append(partitions, part)
}
}
return partitions
}
func (partition *Partition) Id() string {
return partition.id
}
func (fs *FileSystem) Id() string {
return fs.id
}
/*
Create a ignition struct class for ignition
*/
func (node *Node) GetStorage() (stor types_3_4.Storage, rep string, err error) {
func (node *Node) GetIgnitionStorage() (stor types_3_4.Storage, rep string, err error) {
var fileSystems []types_3_4.Filesystem
for fsdevice, fs := range node.FileSystems {
var mountOptions []types_3_4.MountOption
@@ -141,8 +228,8 @@ type SimpleIgnitionConfig struct {
/*
Get a simple config which can be marshalled to json
*/
func (node *Node) GetConfig() (conf SimpleIgnitionConfig, rep string, err error) {
conf.Storage, rep, err = node.GetStorage()
func (node *Node) GetIgnitionConfig() (conf SimpleIgnitionConfig, rep string, err error) {
conf.Storage, rep, err = node.GetIgnitionStorage()
conf.Ignition.Version = "3.1.0"
return
}