Expand nodes when rendering overlay templates

Before #1628, all default constructors for nodes and profiles attempted to
construct objects with pre-populated empty fields (e.g., for pointers to member
structs and for collections that default to nil). Changes to MergeNode in #1629
changed this behavior intentionally to simplify the generation of minimal json;
but this had the unintended side-effect of breaking existing templates that may
now encounter a nil pointer, particularly when accessing member structs.

Introduced here, the Expand() methods on Node and Profile are now responsible
for populating such fields, and Node.Expand() is now called during template
processing.

Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
Jonathon Anderson
2025-01-19 04:26:01 -07:00
parent 96396ed47e
commit 57d28c5bc9
5 changed files with 119 additions and 13 deletions

View File

@@ -99,11 +99,7 @@ func NewProfile(id string) (profileconf Profile) {
}
func EmptyNode() (nodeconf Node) {
nodeconf.Ipmi = new(IpmiConf)
nodeconf.Ipmi.Tags = map[string]string{}
nodeconf.Kernel = new(KernelConf)
nodeconf.NetDevs = make(map[string]*NetDev)
nodeconf.Tags = map[string]string{}
nodeconf.Expand()
return nodeconf
}
@@ -111,14 +107,37 @@ func EmptyNode() (nodeconf Node) {
Creates a ProfileConf but doesn't add it to the database.
*/
func EmptyProfile() (profileconf Profile) {
profileconf.Ipmi = new(IpmiConf)
profileconf.Ipmi.Tags = map[string]string{}
profileconf.Kernel = new(KernelConf)
profileconf.NetDevs = make(map[string]*NetDev)
profileconf.Tags = map[string]string{}
profileconf.Expand()
return profileconf
}
func (nodeconf *Node) Expand() {
nodeconf.Profile.Expand()
}
func (profile *Profile) Expand() {
if profile.Ipmi == nil {
profile.Ipmi = new(IpmiConf)
}
if profile.Ipmi.Tags == nil {
profile.Ipmi.Tags = make(map[string]string)
}
if profile.Kernel == nil {
profile.Kernel = new(KernelConf)
}
if profile.NetDevs == nil {
profile.NetDevs = make(map[string]*NetDev)
}
for i := range profile.NetDevs {
if profile.NetDevs[i].Tags == nil {
profile.NetDevs[i].Tags = make(map[string]string)
}
}
if profile.Tags == nil {
profile.Tags = make(map[string]string)
}
}
/*
Flattens out a NodeConf, which means if there are no explicit values in *IpmiConf
or *KernelConf, these pointer will set to nil. This will remove something like

View File

@@ -3,6 +3,8 @@ package node
import (
"net"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_Empty(t *testing.T) {
@@ -33,3 +35,55 @@ func Test_Empty(t *testing.T) {
}
})
}
func Test_Node_Expand_Flatten(t *testing.T) {
node := new(Node)
assert.Nil(t, node.Ipmi)
assert.Nil(t, node.Kernel)
assert.Nil(t, node.NetDevs)
assert.Nil(t, node.Tags)
t.Run("test expand", func(t *testing.T) {
node.Expand()
assert.Equal(t, map[string]string{}, node.Tags)
assert.Equal(t, map[string]string{}, node.Ipmi.Tags)
assert.Equal(t, "", node.Kernel.Version)
assert.Equal(t, "", node.Kernel.Args)
assert.Equal(t, map[string]*NetDev{}, node.NetDevs)
})
t.Run("test flatten", func(t *testing.T) {
node.Flatten()
assert.Nil(t, node.Ipmi)
assert.Nil(t, node.Kernel)
assert.Nil(t, node.NetDevs)
assert.Nil(t, node.Tags)
})
}
func Test_Profile_Expand_Flatten(t *testing.T) {
profile := new(Profile)
assert.Nil(t, profile.Ipmi)
assert.Nil(t, profile.Kernel)
assert.Nil(t, profile.NetDevs)
assert.Nil(t, profile.Tags)
t.Run("test expand", func(t *testing.T) {
profile.Expand()
assert.Equal(t, map[string]string{}, profile.Tags)
assert.Equal(t, map[string]string{}, profile.Ipmi.Tags)
assert.Equal(t, "", profile.Kernel.Version)
assert.Equal(t, "", profile.Kernel.Args)
assert.Equal(t, map[string]*NetDev{}, profile.NetDevs)
})
t.Run("test flatten", func(t *testing.T) {
profile.Flatten()
assert.Nil(t, profile.Ipmi)
assert.Nil(t, profile.Kernel)
assert.Nil(t, profile.NetDevs)
assert.Nil(t, profile.Tags)
})
}

View File

@@ -53,9 +53,7 @@ func InitStruct(overlayName string, nodeData node.Node, allNodes []node.Node) (T
tstruct.BuildHost = hostname
controller := warewulfconf.Get()
tstruct.ThisNode = &nodeData
if tstruct.ThisNode.Kernel == nil {
tstruct.ThisNode.Kernel = new(node.KernelConf)
}
tstruct.ThisNode.Expand()
if tstruct.ThisNode.Kernel.Version == "" {
if kernel_ := kernel.FromNode(tstruct.ThisNode); kernel_ != nil {
tstruct.ThisNode.Kernel.Version = kernel_.Version()
@@ -77,6 +75,9 @@ func InitStruct(overlayName string, nodeData node.Node, allNodes []node.Node) (T
tstruct.Container = nodeData.ContainerName
// Backwards compatibility for templates using "Keys"
tstruct.AllNodes = allNodes
for i := range tstruct.AllNodes {
tstruct.AllNodes[i].Expand()
}
dt := time.Now()
tstruct.BuildTime = dt.Format("01-02-2006 15:04:05 MST")
tstruct.BuildTimeUnix = strconv.FormatInt(dt.Unix(), 10)

View File

@@ -210,6 +210,28 @@ T3
"test-link": "/test-target",
},
},
"expansion of nodes": {
overlays: []string{"o1"},
overlayFiles: map[string]string{
"/var/lib/warewulf/overlays/o1/rootfs/node.txt.ww": `
IPMI user:{{ .Ipmi.UserName}}
Kernel Version:{{.Kernel.Version}}
Kernel Args:{{.Kernel.Args}}
NetDevs:{{.NetDevs}}
Tags:{{.Tags}}
`,
},
outputDir: "/image",
outputFiles: map[string]string{
"node.txt": `
IPMI user:
Kernel Version:
Kernel Args:
NetDevs:map[]
Tags:map[]
`,
},
},
}
for name, tt := range tests {