Recommended changes from review of #1568
- Make Resources an `interface{}` to support arbitrary yaml
- Remove `wwctl resource` as incompatible with arbitrary yaml
- Revert changes to host overlay templates
- Remove NFS mount options from warewulf.conf
- Replace NFS support / resource prefix with "fstab" resource
- Move resources to profiles
- Migrate warewulf.conf mounts to nodes.conf with `wwctl upgrade`
- Add documentation
Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
@@ -21,12 +21,6 @@ func (this NFSConf) Enabled() bool {
|
||||
type NFSExportConf struct {
|
||||
Path string `yaml:"path" default:"/dev/null"`
|
||||
ExportOptions string `yaml:"export options,omitempty" default:"rw,sync,no_subtree_check"`
|
||||
MountOptions string `yaml:"mount options,omitempty" default:"defaults"`
|
||||
MountP *bool `yaml:"mount,omitempty" default:"true"`
|
||||
}
|
||||
|
||||
func (this NFSExportConf) Mount() bool {
|
||||
return BoolP(this.MountP)
|
||||
}
|
||||
|
||||
// Implements the Unmarshal interface for NFSConf to set default
|
||||
|
||||
@@ -94,12 +94,8 @@ nfs:
|
||||
export paths:
|
||||
- path: /home
|
||||
export options: rw,sync
|
||||
mount options: defaults
|
||||
mount: true
|
||||
- path: /opt
|
||||
export options: ro,sync,no_root_squash
|
||||
mount options: defaults
|
||||
mount: false
|
||||
systemd name: nfs-server
|
||||
container mounts:
|
||||
- source: /etc/resolv.conf
|
||||
@@ -138,12 +134,8 @@ container mounts:
|
||||
assert.True(t, conf.NFS.Enabled())
|
||||
assert.Equal(t, "/home", conf.NFS.ExportsExtended[0].Path)
|
||||
assert.Equal(t, "rw,sync", conf.NFS.ExportsExtended[0].ExportOptions)
|
||||
assert.Equal(t, "defaults", conf.NFS.ExportsExtended[0].MountOptions)
|
||||
assert.True(t, conf.NFS.ExportsExtended[0].Mount())
|
||||
assert.Equal(t, "/opt", conf.NFS.ExportsExtended[1].Path)
|
||||
assert.Equal(t, "ro,sync,no_root_squash", conf.NFS.ExportsExtended[1].ExportOptions)
|
||||
assert.Equal(t, "defaults", conf.NFS.ExportsExtended[1].MountOptions)
|
||||
assert.False(t, conf.NFS.ExportsExtended[1].Mount())
|
||||
assert.Equal(t, "nfs-server", conf.NFS.SystemdName)
|
||||
|
||||
assert.Equal(t, "/etc/resolv.conf", conf.MountsContainer[0].Source)
|
||||
|
||||
@@ -31,7 +31,7 @@ func Hostfile() (err error) {
|
||||
}
|
||||
|
||||
hostname, _ := os.Hostname()
|
||||
tstruct, err := overlay.InitStruct(overlay_.Name(), node.NewNode(hostname), allNodes, nil)
|
||||
tstruct, err := overlay.InitStruct(overlay_.Name(), node.NewNode(hostname), allNodes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -183,18 +183,6 @@ func (config *NodesYaml) ListAllProfiles() []string {
|
||||
return nodeList
|
||||
}
|
||||
|
||||
/*
|
||||
Return the names of all available remote resources
|
||||
*/
|
||||
func (config *NodesYaml) ListAllResources() []string {
|
||||
var resList []string
|
||||
for name := range config.Resources {
|
||||
resList = append(resList, name)
|
||||
}
|
||||
sort.Strings(resList)
|
||||
return resList
|
||||
}
|
||||
|
||||
/*
|
||||
FindDiscoverableNode returns the first discoverable node and an
|
||||
interface to associate with the discovered interface. If the nodUNDEFe has
|
||||
@@ -223,13 +211,3 @@ func (config *NodesYaml) FindDiscoverableNode() (Node, string, error) {
|
||||
|
||||
return EmptyNode(), "", ErrNoUnconfigured
|
||||
}
|
||||
|
||||
/*
|
||||
get the given resource
|
||||
*/
|
||||
func (config *NodesYaml) GetResource(id string) (res Resource, err error) {
|
||||
if found, ok := config.Resources[id]; ok {
|
||||
return found, nil
|
||||
}
|
||||
return res, ErrNotFound
|
||||
}
|
||||
|
||||
@@ -17,13 +17,14 @@ nodeprofiles:
|
||||
override:
|
||||
device: ib0
|
||||
type: profile
|
||||
resources:
|
||||
fstab:
|
||||
- file: /home
|
||||
nodes:
|
||||
test_node1:
|
||||
network devices:
|
||||
net0:
|
||||
device: eth0
|
||||
resources:
|
||||
- NFSHOME
|
||||
test_node2:
|
||||
primary network: net1
|
||||
network devices:
|
||||
@@ -53,9 +54,6 @@ nodes:
|
||||
network devices:
|
||||
override:
|
||||
device: ib1
|
||||
resources:
|
||||
NFSHOME:
|
||||
mountpoint: /home
|
||||
`
|
||||
var ret NodesYaml
|
||||
err := yaml.Unmarshal([]byte(data), &ret)
|
||||
@@ -122,12 +120,6 @@ func Test_Primary_Network(t *testing.T) {
|
||||
assert.Equal(t, "ib1", test_node6.NetDevs["override"].Device)
|
||||
assert.Equal(t, "profile", test_node6.NetDevs["override"].Type)
|
||||
})
|
||||
t.Run("resource is defined", func(t *testing.T) {
|
||||
assert.Contains(t, test_node1.Resources, "NFSHOME")
|
||||
res, err := c.GetResource(test_node1.Resources[0])
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, res, "mountpoint")
|
||||
})
|
||||
}
|
||||
|
||||
var findDiscoverableNodeTests = []struct {
|
||||
|
||||
@@ -17,7 +17,6 @@ Structure of which goes to disk
|
||||
type NodesYaml struct {
|
||||
NodeProfiles map[string]*Profile `yaml:"nodeprofiles"`
|
||||
Nodes map[string]*Node `yaml:"nodes"`
|
||||
Resources map[string]Resource `yaml:"resources,omitempty"`
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -54,7 +53,7 @@ type Profile struct {
|
||||
PrimaryNetDev string `yaml:"primary network,omitempty" lopt:"primarynet" sopt:"p" comment:"Set the primary network interface"`
|
||||
Disks map[string]*Disk `yaml:"disks,omitempty"`
|
||||
FileSystems map[string]*FileSystem `yaml:"filesystems,omitempty"`
|
||||
Resources []string `yaml:"resources,omitempty" lopt:"resources" comment:"Set the resources available to the node or profile"`
|
||||
Resources map[string]Resource `yaml:"resources,omitempty"`
|
||||
}
|
||||
|
||||
type IpmiConf struct {
|
||||
@@ -127,4 +126,4 @@ type FileSystem struct {
|
||||
MountOptions string `yaml:"mount_options,omitempty" comment:"any special options to be passed to the mount command"`
|
||||
}
|
||||
|
||||
type Resource map[string]string
|
||||
type Resource interface{}
|
||||
|
||||
@@ -103,6 +103,9 @@ func Test_listFields(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Resources: map[string]Resource{
|
||||
"resource": "resvalue",
|
||||
},
|
||||
},
|
||||
},
|
||||
fields: []string{
|
||||
@@ -142,7 +145,7 @@ func Test_listFields(t *testing.T) {
|
||||
"NetDevs[default].Tags[nettag]",
|
||||
"Tags[tag]",
|
||||
"PrimaryNetDev",
|
||||
"Resources",
|
||||
"Resources[resource]",
|
||||
},
|
||||
},
|
||||
"profile": {
|
||||
@@ -157,6 +160,9 @@ func Test_listFields(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Resources: map[string]Resource{
|
||||
"resource": "resvalue",
|
||||
},
|
||||
},
|
||||
fields: []string{
|
||||
"Profiles",
|
||||
@@ -193,7 +199,7 @@ func Test_listFields(t *testing.T) {
|
||||
"NetDevs[default].Tags[nettag]",
|
||||
"Tags[tag]",
|
||||
"PrimaryNetDev",
|
||||
"Resources",
|
||||
"Resources[resource]",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -49,38 +49,47 @@ func (add *NodeConfAdd) CreateAddFlags(baseCmd *cobra.Command) {
|
||||
}
|
||||
|
||||
func recursiveCreateFlags(obj interface{}, baseCmd *cobra.Command) {
|
||||
// now iterate of every field
|
||||
nodeInfoType := reflect.TypeOf(obj)
|
||||
nodeInfoVal := reflect.ValueOf(obj)
|
||||
for i := 0; i < nodeInfoVal.Elem().NumField(); i++ {
|
||||
if !nodeInfoType.Elem().Field(i).IsExported() {
|
||||
elemType := reflect.TypeOf(obj).Elem()
|
||||
elemVal := reflect.ValueOf(obj).Elem()
|
||||
|
||||
for i := 0; i < elemVal.NumField(); i++ {
|
||||
field := elemType.Field(i)
|
||||
fieldVal := elemVal.Field(i)
|
||||
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
if nodeInfoType.Elem().Field(i).Tag.Get("comment") != "" {
|
||||
field := nodeInfoVal.Elem().Field(i)
|
||||
createFlags(baseCmd, nodeInfoType.Elem().Field(i), &field)
|
||||
|
||||
} else if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.Ptr {
|
||||
recursiveCreateFlags(nodeInfoVal.Elem().Field(i).Interface(), baseCmd)
|
||||
if field.Tag.Get("comment") != "" {
|
||||
createFlags(baseCmd, field, &fieldVal)
|
||||
|
||||
} else if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.Map &&
|
||||
nodeInfoType.Elem().Field(i).Type != reflect.TypeOf(map[string]string{}) {
|
||||
// add a map with key UNDEF so that it can hold values N.B. UNDEF can never be added through command line
|
||||
key := reflect.ValueOf("UNDEF")
|
||||
if nodeInfoVal.Elem().Field(i).Len() == 0 {
|
||||
if nodeInfoVal.Elem().Field(i).IsNil() {
|
||||
nodeInfoVal.Elem().Field(i).Set(reflect.MakeMap(nodeInfoType.Elem().Field(i).Type))
|
||||
} else if field.Anonymous {
|
||||
recursiveCreateFlags(fieldVal.Addr().Interface(), baseCmd)
|
||||
|
||||
} else if field.Type.Kind() == reflect.Ptr {
|
||||
recursiveCreateFlags(fieldVal.Interface(), baseCmd)
|
||||
|
||||
} else if field.Type.Kind() == reflect.Struct {
|
||||
recursiveCreateFlags(fieldVal.Addr().Interface(), baseCmd)
|
||||
|
||||
} else if field.Type.Kind() == reflect.Map {
|
||||
switch field.Type.Elem().Kind() {
|
||||
case reflect.String, reflect.Interface:
|
||||
continue
|
||||
case reflect.Pointer, reflect.Slice, reflect.Map:
|
||||
// add a map with key UNDEF so that it can hold values N.B. UNDEF can never be added through command line
|
||||
key := reflect.ValueOf("UNDEF")
|
||||
if fieldVal.Len() == 0 {
|
||||
if fieldVal.IsNil() {
|
||||
fieldVal.Set(reflect.MakeMap(field.Type))
|
||||
}
|
||||
newPtr := reflect.New(field.Type.Elem().Elem())
|
||||
fieldVal.SetMapIndex(key, newPtr)
|
||||
} else {
|
||||
key = fieldVal.MapKeys()[0]
|
||||
}
|
||||
newPtr := reflect.New(nodeInfoType.Elem().Field(i).Type.Elem().Elem())
|
||||
nodeInfoVal.Elem().Field(i).SetMapIndex(key, newPtr)
|
||||
} else {
|
||||
key = nodeInfoVal.Elem().Field(i).MapKeys()[0]
|
||||
recursiveCreateFlags(fieldVal.MapIndex(key).Interface(), baseCmd)
|
||||
}
|
||||
recursiveCreateFlags(nodeInfoVal.Elem().Field(i).MapIndex(key).Interface(), baseCmd)
|
||||
} else if nodeInfoType.Elem().Field(i).Anonymous {
|
||||
recursiveCreateFlags(nodeInfoVal.Elem().Field(i).Addr().Interface(), baseCmd)
|
||||
} else if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.Struct {
|
||||
recursiveCreateFlags(nodeInfoVal.Elem().Field(i).Addr().Interface(), baseCmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,16 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"dario.cat/mergo"
|
||||
"github.com/mohae/deepcopy"
|
||||
|
||||
"github.com/warewulf/warewulf/internal/pkg/util"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
// copyProfile creates a deep copy of the given Profile object.
|
||||
// It uses encoding/gob to serialize and deserialize the Profile, ensuring
|
||||
// that all nested fields are copied.
|
||||
//
|
||||
// Parameters:
|
||||
// - original: The Profile object to be copied.
|
||||
//
|
||||
// Returns:
|
||||
// - A new Profile object that is a deep copy of the input.
|
||||
// - An error if serialization or deserialization fails.
|
||||
func copyProfile(original Profile) (Profile, error) {
|
||||
var buf bytes.Buffer
|
||||
enc := gob.NewEncoder(&buf)
|
||||
dec := gob.NewDecoder(&buf)
|
||||
profile := Profile{}
|
||||
if err := enc.Encode(original); err != nil {
|
||||
return profile, err
|
||||
} else {
|
||||
if err := dec.Decode(&profile); err != nil {
|
||||
return profile, err
|
||||
} else {
|
||||
return profile, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getNodeProfiles retrieves a list of profile IDs associated with a specific node ID.
|
||||
// It retrives nested profiles and ensures the list is cleaned of duplicates
|
||||
// and negations (denoted with a '~' prefix).
|
||||
@@ -85,6 +58,33 @@ func (config *NodesYaml) appendProfileProfiles(profiles []string, id string) []s
|
||||
return profiles
|
||||
}
|
||||
|
||||
type InterfaceTransformer struct{}
|
||||
|
||||
func (t InterfaceTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
|
||||
if typ.Kind() == reflect.Interface {
|
||||
return func(dst, src reflect.Value) error {
|
||||
if !src.IsValid() || src.IsZero() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handle merging of concrete values
|
||||
switch src.Interface().(type) {
|
||||
case map[string]interface{}:
|
||||
if dst.IsNil() {
|
||||
dst.Set(reflect.New(src.Elem().Type()).Elem())
|
||||
}
|
||||
return mergo.Merge(dst.Interface(), src.Interface(), mergo.WithAppendSlice, mergo.WithOverride, mergo.WithTransformers(t))
|
||||
case []interface{}:
|
||||
dst.Set(src)
|
||||
default:
|
||||
dst.Set(src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MergeNode merges the configuration of a node identified by `id` with all the profiles
|
||||
// associated with it, producing a fully composed `Node` and a `fieldMap` detailing the
|
||||
// sources of various configuration fields.
|
||||
@@ -121,11 +121,9 @@ func (config *NodesYaml) MergeNode(id string) (node Node, fields fieldMap, err e
|
||||
if profile, err := config.GetProfile(profileID); err != nil {
|
||||
wwlog.Warn("profile not found: %s", profileID)
|
||||
continue
|
||||
} else if profile, err := copyProfile(profile); err != nil {
|
||||
wwlog.Warn("error processing profile %s: %v", profileID, err)
|
||||
continue
|
||||
} else {
|
||||
if err = mergo.Merge(&node.Profile, profile, mergo.WithAppendSlice, mergo.WithOverride); err != nil {
|
||||
profile := deepcopy.Copy(profile)
|
||||
if err = mergo.Merge(&node.Profile, profile, mergo.WithAppendSlice, mergo.WithOverride, mergo.WithTransformers(InterfaceTransformer{})); err != nil {
|
||||
return node, fields, err
|
||||
}
|
||||
for _, fieldName := range listFields(profile) {
|
||||
@@ -150,8 +148,15 @@ func (config *NodesYaml) MergeNode(id string) (node Node, fields fieldMap, err e
|
||||
if value, err := getNestedFieldValue(originalNode, fieldName); err == nil && valueStr(value) != "" {
|
||||
source := ""
|
||||
prevSource := fields.Source(fieldName)
|
||||
if value.Kind() == reflect.Slice && prevSource != "" {
|
||||
source = strings.Join([]string{prevSource, id}, ",")
|
||||
if prevSource != "" {
|
||||
switch value.Kind() {
|
||||
case reflect.Slice:
|
||||
source = strings.Join([]string{prevSource, id}, ",")
|
||||
case reflect.Interface:
|
||||
if _, ok := value.Interface().([]interface{}); ok {
|
||||
source = strings.Join([]string{prevSource, id}, ",")
|
||||
}
|
||||
}
|
||||
}
|
||||
if value, err := getNestedFieldString(node, fieldName); err == nil {
|
||||
fields.Set(fieldName, source, value)
|
||||
|
||||
@@ -758,6 +758,30 @@ nodeprofiles:
|
||||
"p2 netdev tag",
|
||||
},
|
||||
},
|
||||
"resources": {
|
||||
nodesConf: `
|
||||
nodeprofiles:
|
||||
p1:
|
||||
resources:
|
||||
fstab:
|
||||
- spec: warewulf:/home
|
||||
file: /home
|
||||
vfstype: nfs
|
||||
nodes:
|
||||
n1:
|
||||
profiles:
|
||||
- p1
|
||||
resources:
|
||||
fstab:
|
||||
- spec: warewulf:/opt
|
||||
file: /opt
|
||||
vfstype: nfs
|
||||
`,
|
||||
nodes: []string{"n1"},
|
||||
fields: []string{"Resources[fstab]"},
|
||||
sources: []string{"p1,n1"},
|
||||
values: []string{"[map[file:/home spec:warewulf:/home vfstype:nfs] map[file:/opt spec:warewulf:/opt vfstype:nfs]]"},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tt := range tests {
|
||||
@@ -781,13 +805,13 @@ nodeprofiles:
|
||||
}
|
||||
|
||||
var nodes []Node
|
||||
for i, _ := range tt.nodes {
|
||||
for i := range tt.nodes {
|
||||
node, _, mergeErr := registry.MergeNode(tt.nodes[i])
|
||||
assert.NoError(t, mergeErr)
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
|
||||
for i, _ := range tt.nodes {
|
||||
for i := range tt.nodes {
|
||||
_, fields, _ := registry.MergeNode(tt.nodes[i])
|
||||
value, valueErr := getNestedFieldString(nodes[i], tt.fields[i])
|
||||
assert.NoError(t, valueErr)
|
||||
|
||||
@@ -154,13 +154,16 @@ func recursiveFlatten(obj interface{}) (hasContent bool) {
|
||||
case reflect.Map:
|
||||
mapIter := valObj.Elem().Field(i).MapRange()
|
||||
for mapIter.Next() {
|
||||
if mapIter.Value().Kind() == reflect.String {
|
||||
if mapIter.Value().String() != "" {
|
||||
hasContent = true
|
||||
switch mapIter.Value().Kind() {
|
||||
case reflect.Map, reflect.Pointer, reflect.Slice:
|
||||
if mapIter.Value().Type().Elem().Kind() == reflect.Struct {
|
||||
ret := recursiveFlatten(mapIter.Value().Interface())
|
||||
hasContent = ret || hasContent
|
||||
} else {
|
||||
hasContent = !mapIter.Value().IsZero() || hasContent
|
||||
}
|
||||
} else {
|
||||
ret := recursiveFlatten(mapIter.Value().Interface())
|
||||
hasContent = ret || hasContent
|
||||
default:
|
||||
hasContent = !mapIter.Value().IsZero() || hasContent
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ type TemplateStruct struct {
|
||||
Tftp warewulfconf.TFTPConf
|
||||
Paths warewulfconf.BuildConfig
|
||||
AllNodes []node.Node
|
||||
Resources map[string]node.Resource
|
||||
node.Node
|
||||
// backward compatiblity
|
||||
Container string
|
||||
@@ -47,7 +46,7 @@ type TemplateStruct struct {
|
||||
/*
|
||||
Initialize an TemplateStruct with the given node.NodeInfo
|
||||
*/
|
||||
func InitStruct(overlayName string, nodeData node.Node, allNodes []node.Node, resources map[string]node.Resource) (TemplateStruct, error) {
|
||||
func InitStruct(overlayName string, nodeData node.Node, allNodes []node.Node) (TemplateStruct, error) {
|
||||
var tstruct TemplateStruct
|
||||
tstruct.Overlay = overlayName
|
||||
hostname, _ := os.Hostname()
|
||||
@@ -82,7 +81,6 @@ func InitStruct(overlayName string, nodeData node.Node, allNodes []node.Node, re
|
||||
tstruct.BuildTime = dt.Format("01-02-2006 15:04:05 MST")
|
||||
tstruct.BuildTimeUnix = strconv.FormatInt(dt.Unix(), 10)
|
||||
// tstruct.Node.Tags = map[string]string{}
|
||||
tstruct.Resources = resources
|
||||
var buf bytes.Buffer
|
||||
enc := gob.NewEncoder(&buf)
|
||||
dec := gob.NewDecoder(&buf)
|
||||
|
||||
@@ -101,7 +101,7 @@ func (this Overlay) IsDistributionOverlay() bool {
|
||||
return path.Dir(this.Path()) == config.Get().Paths.DistributionOverlaydir()
|
||||
}
|
||||
|
||||
func BuildAllOverlays(nodes []node.Node, allNodes []node.Node, resources map[string]node.Resource, workerCount int) error {
|
||||
func BuildAllOverlays(nodes []node.Node, allNodes []node.Node, workerCount int) error {
|
||||
nodeChan := make(chan node.Node, len(nodes))
|
||||
errChan := make(chan error, len(nodes)*2)
|
||||
|
||||
@@ -109,12 +109,12 @@ func BuildAllOverlays(nodes []node.Node, allNodes []node.Node, resources map[str
|
||||
worker := func() {
|
||||
for n := range nodeChan {
|
||||
wwlog.Info("Building system overlays for %s: [%s]", n.Id(), strings.Join(n.SystemOverlay, ", "))
|
||||
if err := BuildOverlay(n, allNodes, resources, "system", n.SystemOverlay); err != nil {
|
||||
if err := BuildOverlay(n, allNodes, "system", n.SystemOverlay); err != nil {
|
||||
errChan <- fmt.Errorf("could not build system overlays %v for node %s: %w", n.SystemOverlay, n.Id(), err)
|
||||
}
|
||||
|
||||
wwlog.Info("Building runtime overlays for %s: [%s]", n.Id(), strings.Join(n.RuntimeOverlay, ", "))
|
||||
if err := BuildOverlay(n, allNodes, resources, "runtime", n.RuntimeOverlay); err != nil {
|
||||
if err := BuildOverlay(n, allNodes, "runtime", n.RuntimeOverlay); err != nil {
|
||||
errChan <- fmt.Errorf("could not build runtime overlays %v for node %s: %w", n.RuntimeOverlay, n.Id(), err)
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ func BuildAllOverlays(nodes []node.Node, allNodes []node.Node, resources map[str
|
||||
return nil
|
||||
}
|
||||
|
||||
func BuildSpecificOverlays(nodes []node.Node, allNodes []node.Node, resources map[string]node.Resource, overlayNames []string, workerCount int) error {
|
||||
func BuildSpecificOverlays(nodes []node.Node, allNodes []node.Node, overlayNames []string, workerCount int) error {
|
||||
nodeChan := make(chan node.Node, len(nodes))
|
||||
errChan := make(chan error, len(nodes))
|
||||
|
||||
@@ -148,7 +148,7 @@ func BuildSpecificOverlays(nodes []node.Node, allNodes []node.Node, resources ma
|
||||
for n := range nodeChan {
|
||||
wwlog.Info("Building overlay for %s: %v", n, overlayNames)
|
||||
for _, overlayName := range overlayNames {
|
||||
err := BuildOverlay(n, allNodes, resources, "", []string{overlayName})
|
||||
err := BuildOverlay(n, allNodes, "", []string{overlayName})
|
||||
if err != nil {
|
||||
errChan <- fmt.Errorf("could not build overlay %s for node %s: %w", overlayName, n.Id(), err)
|
||||
}
|
||||
@@ -199,7 +199,7 @@ func BuildHostOverlay() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return BuildOverlayIndir(hostData, allNodes, registry.Resources, []string{"host"}, "/")
|
||||
return BuildOverlayIndir(hostData, allNodes, []string{"host"}, "/")
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -229,7 +229,7 @@ func FindOverlays() (overlayList []string, err error) {
|
||||
/*
|
||||
Build the given overlays for a node and create a Image for them
|
||||
*/
|
||||
func BuildOverlay(nodeConf node.Node, allNodes []node.Node, resources map[string]node.Resource, context string, overlayNames []string) error {
|
||||
func BuildOverlay(nodeConf node.Node, allNodes []node.Node, context string, overlayNames []string) error {
|
||||
if len(overlayNames) == 0 && context == "" {
|
||||
return nil
|
||||
}
|
||||
@@ -254,7 +254,7 @@ func BuildOverlay(nodeConf node.Node, allNodes []node.Node, resources map[string
|
||||
|
||||
wwlog.Debug("Created temporary directory for %s: %s", name, buildDir)
|
||||
|
||||
err = BuildOverlayIndir(nodeConf, allNodes, resources, overlayNames, buildDir)
|
||||
err = BuildOverlayIndir(nodeConf, allNodes, overlayNames, buildDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate files for %s: %w", name, err)
|
||||
}
|
||||
@@ -283,7 +283,7 @@ func init() {
|
||||
}
|
||||
|
||||
// Build the given overlays for a node in the given directory.
|
||||
func BuildOverlayIndir(nodeData node.Node, allNodes []node.Node, resources map[string]node.Resource, overlayNames []string, outputDir string) error {
|
||||
func BuildOverlayIndir(nodeData node.Node, allNodes []node.Node, overlayNames []string, outputDir string) error {
|
||||
if len(overlayNames) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -332,7 +332,7 @@ func BuildOverlayIndir(nodeData node.Node, allNodes []node.Node, resources map[s
|
||||
} else if filepath.Ext(walkPath) == ".ww" {
|
||||
originalOutputPath := outputPath
|
||||
outputPath := strings.TrimSuffix(outputPath, ".ww")
|
||||
tstruct, err := InitStruct(overlayName, nodeData, allNodes, resources)
|
||||
tstruct, err := InitStruct(overlayName, nodeData, allNodes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initial data for %s: %w", nodeData.Id(), err)
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ T3
|
||||
|
||||
env.MkdirAll(tt.outputDir)
|
||||
|
||||
assert.NoError(t, BuildOverlayIndir(tt.node, []node.Node{tt.node}, nil, tt.overlays, env.GetPath(tt.outputDir)))
|
||||
assert.NoError(t, BuildOverlayIndir(tt.node, []node.Node{tt.node}, tt.overlays, env.GetPath(tt.outputDir)))
|
||||
dirFiles := tt.outputDirs
|
||||
for outputFile, _ := range tt.outputFiles {
|
||||
dirFiles = append(dirFiles, outputFile)
|
||||
@@ -400,7 +400,7 @@ func Test_BuildOverlay(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
nodeInfo := node.NewNode(tt.nodeName)
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
err := BuildOverlay(nodeInfo, []node.Node{nodeInfo}, nil, tt.context, tt.overlays)
|
||||
err := BuildOverlay(nodeInfo, []node.Node{nodeInfo}, tt.context, tt.overlays)
|
||||
assert.NoError(t, err)
|
||||
if tt.image != "" {
|
||||
image := env.GetPath(path.Join("srv/warewulf/overlays", tt.image))
|
||||
@@ -523,7 +523,7 @@ func Test_BuildAllOverlays(t *testing.T) {
|
||||
}
|
||||
nodes = append(nodes, nodeInfo)
|
||||
}
|
||||
err := BuildAllOverlays(nodes, nodes, nil, runtime.NumCPU())
|
||||
err := BuildAllOverlays(nodes, nodes, runtime.NumCPU())
|
||||
assert.NoError(t, err)
|
||||
if tt.createdOverlays == nil {
|
||||
dirName := path.Join(provisionDir, "overlays")
|
||||
@@ -615,7 +615,7 @@ func Test_BuildSpecificOverlays(t *testing.T) {
|
||||
nodeInfo := node.NewNode(nodeName)
|
||||
nodes = append(nodes, nodeInfo)
|
||||
}
|
||||
err := BuildSpecificOverlays(nodes, nodes, nil, tt.overlays, runtime.NumCPU())
|
||||
err := BuildSpecificOverlays(nodes, nodes, tt.overlays, runtime.NumCPU())
|
||||
if !tt.succeed {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/warewulf/warewulf/internal/pkg/config"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
func ParseConfig(data []byte) (warewulfYaml *WarewulfYaml, err error) {
|
||||
@@ -175,8 +176,9 @@ func (this *NFSExportConf) Upgrade() (upgraded *config.NFSExportConf) {
|
||||
upgraded = new(config.NFSExportConf)
|
||||
upgraded.Path = this.Path
|
||||
upgraded.ExportOptions = this.ExportOptions
|
||||
upgraded.MountOptions = this.MountOptions
|
||||
upgraded.MountP = this.Mount
|
||||
if *(this.Mount) {
|
||||
wwlog.Warn("Legacy mount configured for NFS export %s: use `wwctl upgrade nodes --with-warewulfconf=<original file>` to port to nodes.conf", this.Path)
|
||||
}
|
||||
return upgraded
|
||||
}
|
||||
|
||||
|
||||
@@ -231,12 +231,8 @@ nfs:
|
||||
export paths:
|
||||
- path: /home
|
||||
export options: rw,sync
|
||||
mount options: defaults
|
||||
mount: true
|
||||
- path: /opt
|
||||
export options: ro,sync,no_root_squash
|
||||
mount options: defaults
|
||||
mount: false
|
||||
systemd name: nfs-server
|
||||
`,
|
||||
},
|
||||
@@ -299,12 +295,8 @@ nfs:
|
||||
export paths:
|
||||
- path: /home
|
||||
export options: rw,sync
|
||||
mount options: defaults
|
||||
mount: true
|
||||
- path: /opt
|
||||
export options: ro,sync,no_root_squash
|
||||
mount options: defaults
|
||||
mount: false
|
||||
systemd name: nfs-server
|
||||
`,
|
||||
},
|
||||
@@ -387,12 +379,8 @@ nfs:
|
||||
export paths:
|
||||
- path: /home
|
||||
export options: rw,sync
|
||||
mount options: defaults
|
||||
mount: true
|
||||
- path: /opt
|
||||
export options: ro,sync,no_root_squash
|
||||
mount options: defaults
|
||||
mount: false
|
||||
systemd name: nfs-server
|
||||
ssh:
|
||||
key types:
|
||||
@@ -485,12 +473,8 @@ nfs:
|
||||
export paths:
|
||||
- path: /home
|
||||
export options: rw,sync
|
||||
mount options: defaults
|
||||
mount: true
|
||||
- path: /opt
|
||||
export options: ro,sync,no_root_squash
|
||||
mount options: defaults
|
||||
mount: false
|
||||
systemd name: nfs-server
|
||||
ssh:
|
||||
key types:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package upgrade
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -50,7 +51,7 @@ type NodesYaml struct {
|
||||
Nodes map[string]*Node
|
||||
}
|
||||
|
||||
func (this *NodesYaml) Upgrade(addDefaults bool, replaceOverlays bool) (upgraded *node.NodesYaml) {
|
||||
func (this *NodesYaml) Upgrade(addDefaults bool, replaceOverlays bool, warewulfconf *WarewulfYaml) (upgraded *node.NodesYaml) {
|
||||
upgraded = new(node.NodesYaml)
|
||||
upgraded.NodeProfiles = make(map[string]*node.Profile)
|
||||
upgraded.Nodes = make(map[string]*node.Node)
|
||||
@@ -93,6 +94,50 @@ func (this *NodesYaml) Upgrade(addDefaults bool, replaceOverlays bool) (upgraded
|
||||
defaultProfile.Ipxe = "default"
|
||||
}
|
||||
}
|
||||
if warewulfconf != nil && warewulfconf.NFS != nil {
|
||||
var fstab []map[string]string
|
||||
for _, export := range warewulfconf.NFS.Exports {
|
||||
fmt.Printf("PORTING EXPORT: %s\n", export)
|
||||
fstab = append(fstab, map[string]string{
|
||||
"spec": fmt.Sprintf("warewulf:%s", export),
|
||||
"file": export,
|
||||
"vfstype": "nfs",
|
||||
})
|
||||
}
|
||||
for _, export := range warewulfconf.NFS.ExportsExtended {
|
||||
if export.Mount != nil && *(export.Mount) {
|
||||
entry := map[string]string{
|
||||
"spec": fmt.Sprintf("warewulf:%s", export.Path),
|
||||
"file": export.Path,
|
||||
"vfstype": "nfs",
|
||||
}
|
||||
if export.MountOptions != "" {
|
||||
entry["mntops"] = export.MountOptions
|
||||
}
|
||||
fstab = append(fstab, entry)
|
||||
}
|
||||
}
|
||||
fmt.Printf("FSTAB: %+v\n", fstab)
|
||||
if len(fstab) > 0 {
|
||||
if _, ok := upgraded.NodeProfiles["default"]; !ok {
|
||||
upgraded.NodeProfiles["default"] = new(node.Profile)
|
||||
}
|
||||
if upgraded.NodeProfiles["default"].Resources == nil {
|
||||
upgraded.NodeProfiles["default"].Resources = make(map[string]node.Resource)
|
||||
}
|
||||
if _, ok := upgraded.NodeProfiles["default"].Resources["fstab"]; ok {
|
||||
if prevFstab, ok := (upgraded.NodeProfiles["default"].Resources["fstab"]).([]map[string]string); ok {
|
||||
newFstab := append(prevFstab, fstab...)
|
||||
upgraded.NodeProfiles["default"].Resources["fstab"] = newFstab
|
||||
} else {
|
||||
wwlog.Warn("Unable to port NFS mounts from warewulf.conf: incompatible existing fstab resource in default profile")
|
||||
}
|
||||
} else {
|
||||
upgraded.NodeProfiles["default"].Resources["fstab"] = fstab
|
||||
}
|
||||
fmt.Printf("RECORDED FSTAB: %+v\n", upgraded.NodeProfiles["default"].Resources["fstab"])
|
||||
}
|
||||
}
|
||||
return upgraded
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package upgrade
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -16,6 +15,7 @@ var nodesYamlUpgradeTests = []struct {
|
||||
files map[string]string
|
||||
legacyYaml string
|
||||
upgradedYaml string
|
||||
warewulfConf string
|
||||
}{
|
||||
{
|
||||
name: "captured vers42 example",
|
||||
@@ -767,6 +767,64 @@ nodeprofiles:
|
||||
- p2
|
||||
p2: {}
|
||||
nodes: {}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "Legacy export mounts",
|
||||
legacyYaml: `
|
||||
nodeprofiles:
|
||||
default: {}
|
||||
`,
|
||||
upgradedYaml: `
|
||||
nodeprofiles:
|
||||
default:
|
||||
resources:
|
||||
fstab:
|
||||
- spec: warewulf:/home
|
||||
file: /home
|
||||
vfstype: nfs
|
||||
- spec: warewulf:/opt
|
||||
file: /opt
|
||||
vfstype: nfs
|
||||
nodes: {}
|
||||
`,
|
||||
warewulfConf: `
|
||||
nfs:
|
||||
exports:
|
||||
- /home
|
||||
- /opt
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "Legacy extended export mounts",
|
||||
legacyYaml: `
|
||||
nodeprofiles:
|
||||
default: {}
|
||||
`,
|
||||
upgradedYaml: `
|
||||
nodeprofiles:
|
||||
default:
|
||||
resources:
|
||||
fstab:
|
||||
- spec: warewulf:/home
|
||||
file: /home
|
||||
vfstype: nfs
|
||||
- spec: warewulf:/opt
|
||||
file: /opt
|
||||
mntops: defaults,ro
|
||||
vfstype: nfs
|
||||
nodes: {}
|
||||
`,
|
||||
warewulfConf: `
|
||||
nfs:
|
||||
export paths:
|
||||
- path: /home
|
||||
mount: true
|
||||
- path: /opt
|
||||
mount options: defaults,ro
|
||||
mount: true
|
||||
- path: /var
|
||||
mount: false
|
||||
`,
|
||||
},
|
||||
}
|
||||
@@ -781,12 +839,14 @@ func Test_UpgradeNodesYaml(t *testing.T) {
|
||||
env.WriteFile(fileName, content)
|
||||
}
|
||||
}
|
||||
conf, err := ParseConfig([]byte(tt.warewulfConf))
|
||||
assert.NoError(t, err)
|
||||
legacy, err := ParseNodes([]byte(tt.legacyYaml))
|
||||
assert.NoError(t, err)
|
||||
upgraded := legacy.Upgrade(tt.addDefaults, tt.replaceOverlays)
|
||||
upgraded := legacy.Upgrade(tt.addDefaults, tt.replaceOverlays, conf)
|
||||
upgradedYaml, err := upgraded.Dump()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(tt.upgradedYaml), strings.TrimSpace(string(upgradedYaml)))
|
||||
assert.YAMLEq(t, tt.upgradedYaml, string(upgradedYaml))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,9 +72,9 @@ func getOverlayFile(n node.Node, context string, stage_overlays []string, autobu
|
||||
return "", err
|
||||
}
|
||||
if len(stage_overlays) > 0 {
|
||||
err = overlay.BuildSpecificOverlays([]node.Node{n}, allNodes, registry.Resources, stage_overlays, 1)
|
||||
err = overlay.BuildSpecificOverlays([]node.Node{n}, allNodes, stage_overlays, 1)
|
||||
} else {
|
||||
err = overlay.BuildAllOverlays([]node.Node{n}, allNodes, registry.Resources, 1)
|
||||
err = overlay.BuildAllOverlays([]node.Node{n}, allNodes, 1)
|
||||
}
|
||||
if err != nil {
|
||||
wwlog.Error("Failed to build overlay: %s, %s, %s\n%s",
|
||||
|
||||
Reference in New Issue
Block a user