Fix newline handling in file, softlink, and ImportLink template functions
Use state-based routing instead of sentinel strings, so whitespace-trimming
syntax (e.g. `{{- file "name" -}}`) correctly creates all named files and
symlinks.
Fixes: #2118
Signed-off-by: Jonathon Anderson <janderson@ciq.com>
Co-authored-by: Christian Goll <cgoll@suse.com>
Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/swaggest/usecase"
|
||||
@@ -148,12 +149,46 @@ func (of *OverlayFile) renderContents(nodeName string) (string, error) {
|
||||
}
|
||||
tstruct.BuildSource = of.Path
|
||||
|
||||
buffer, _, _, renderErr := overlay.RenderTemplateFile(of.FullPath(), tstruct)
|
||||
rendered, renderErr := overlay.RenderTemplate(of.FullPath(), tstruct)
|
||||
if renderErr != nil {
|
||||
return "", renderErr
|
||||
}
|
||||
|
||||
return buffer.String(), nil
|
||||
if !rendered.WriteFile {
|
||||
// abort() was called in the template: nothing would be written to disk.
|
||||
// Include any content rendered before the abort() call for debugging.
|
||||
return rendered.Files[0].Buffer.String() + "Aborted\n", nil
|
||||
}
|
||||
|
||||
// Single output file with no file() calls: return content as-is for backward
|
||||
// compatibility. The default slot has Name == ""; named entries always have a
|
||||
// non-empty Name regardless of how many there are.
|
||||
if len(rendered.Files) == 1 && rendered.Files[0].Name == "" {
|
||||
if rendered.Files[0].IsSymlink {
|
||||
return "Symlink: " + rendered.Files[0].Target + "\n", nil
|
||||
}
|
||||
return rendered.Files[0].Buffer.String(), nil
|
||||
}
|
||||
// One or more file() calls: emit Filename: / Symlink: headers for each named
|
||||
// entry. Skip the default slot (Name == ""); pre-file() content in it is
|
||||
// for RenderTemplateFile callers, not serialized here.
|
||||
var combined strings.Builder
|
||||
for _, f := range rendered.Files {
|
||||
if f.Name == "" {
|
||||
continue
|
||||
}
|
||||
combined.WriteString("Filename: ")
|
||||
combined.WriteString(f.Name)
|
||||
combined.WriteString("\n")
|
||||
if f.IsSymlink {
|
||||
combined.WriteString("Symlink: ")
|
||||
combined.WriteString(f.Target)
|
||||
combined.WriteString("\n")
|
||||
} else {
|
||||
combined.WriteString(f.Buffer.String())
|
||||
}
|
||||
}
|
||||
return combined.String(), nil
|
||||
}
|
||||
|
||||
func NewOverlayFile(name string, path string, renderNodeName string) (*OverlayFile, error) {
|
||||
|
||||
@@ -18,6 +18,29 @@ import (
|
||||
const sampleTemplate = `{{ if .Tags.email }}eMail: {{ .Tags.email }}{{else}} noMail{{- end }}
|
||||
`
|
||||
|
||||
const multiFileTemplate = `{{- range $name := list "alpha" "beta" }}
|
||||
{{- file $name -}}
|
||||
content of {{ $name }}
|
||||
{{ end -}}`
|
||||
|
||||
const abortTemplate = `{{- abort -}}`
|
||||
|
||||
const abortWithContentTemplate = `some content
|
||||
{{- abort -}}`
|
||||
|
||||
const symlinkTemplate = `{{- softlink "/usr/share/zoneinfo/UTC" -}}`
|
||||
|
||||
const multiSymlinkTemplate = `{{- file "link1" -}}
|
||||
{{- softlink "/target1" -}}
|
||||
{{- file "link2" -}}
|
||||
{{- softlink "/target2" -}}`
|
||||
|
||||
const sampleNodesConf = `
|
||||
nodeprofiles: {}
|
||||
nodes:
|
||||
node1: {}
|
||||
`
|
||||
|
||||
var overlayTests = []struct {
|
||||
name string
|
||||
initFiles map[string]string
|
||||
@@ -106,6 +129,96 @@ var overlayTests = []struct {
|
||||
},
|
||||
response: `{"test":{"files":null, "site":true},"testoverlay":{"files":["/email.ww"], "site":true}}`,
|
||||
},
|
||||
{
|
||||
name: "render multi-file overlay template",
|
||||
initFiles: map[string]string{
|
||||
"/usr/share/warewulf/overlays/multioverlay/multi.ww": multiFileTemplate,
|
||||
"/etc/warewulf/nodes.conf": sampleNodesConf,
|
||||
},
|
||||
request: func(serverURL string) (*http.Request, error) {
|
||||
return http.NewRequest(http.MethodGet, serverURL+"/api/overlays/multioverlay/file?path=multi.ww&render=node1", nil)
|
||||
},
|
||||
response: `{
|
||||
"overlay": "multioverlay",
|
||||
"path": "multi.ww",
|
||||
"contents": "Filename: alpha\ncontent of alpha\nFilename: beta\ncontent of beta\n",
|
||||
"perms": "<<PRESENCE>>",
|
||||
"uid": "<<PRESENCE>>",
|
||||
"gid": "<<PRESENCE>>"
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "render aborted template",
|
||||
initFiles: map[string]string{
|
||||
"/usr/share/warewulf/overlays/multioverlay/abort.ww": abortTemplate,
|
||||
"/etc/warewulf/nodes.conf": sampleNodesConf,
|
||||
},
|
||||
request: func(serverURL string) (*http.Request, error) {
|
||||
return http.NewRequest(http.MethodGet, serverURL+"/api/overlays/multioverlay/file?path=abort.ww&render=node1", nil)
|
||||
},
|
||||
response: `{
|
||||
"overlay": "multioverlay",
|
||||
"path": "abort.ww",
|
||||
"contents": "Aborted\n",
|
||||
"perms": "<<PRESENCE>>",
|
||||
"uid": "<<PRESENCE>>",
|
||||
"gid": "<<PRESENCE>>"
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "render aborted template with pre-abort content",
|
||||
initFiles: map[string]string{
|
||||
"/usr/share/warewulf/overlays/multioverlay/abort-content.ww": abortWithContentTemplate,
|
||||
"/etc/warewulf/nodes.conf": sampleNodesConf,
|
||||
},
|
||||
request: func(serverURL string) (*http.Request, error) {
|
||||
return http.NewRequest(http.MethodGet, serverURL+"/api/overlays/multioverlay/file?path=abort-content.ww&render=node1", nil)
|
||||
},
|
||||
response: `{
|
||||
"overlay": "multioverlay",
|
||||
"path": "abort-content.ww",
|
||||
"contents": "some contentAborted\n",
|
||||
"perms": "<<PRESENCE>>",
|
||||
"uid": "<<PRESENCE>>",
|
||||
"gid": "<<PRESENCE>>"
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "render single-file symlink template",
|
||||
initFiles: map[string]string{
|
||||
"/usr/share/warewulf/overlays/multioverlay/symlink.ww": symlinkTemplate,
|
||||
"/etc/warewulf/nodes.conf": sampleNodesConf,
|
||||
},
|
||||
request: func(serverURL string) (*http.Request, error) {
|
||||
return http.NewRequest(http.MethodGet, serverURL+"/api/overlays/multioverlay/file?path=symlink.ww&render=node1", nil)
|
||||
},
|
||||
response: `{
|
||||
"overlay": "multioverlay",
|
||||
"path": "symlink.ww",
|
||||
"contents": "Symlink: /usr/share/zoneinfo/UTC\n",
|
||||
"perms": "<<PRESENCE>>",
|
||||
"uid": "<<PRESENCE>>",
|
||||
"gid": "<<PRESENCE>>"
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "render multi-file symlink template",
|
||||
initFiles: map[string]string{
|
||||
"/usr/share/warewulf/overlays/multioverlay/multi-symlink.ww": multiSymlinkTemplate,
|
||||
"/etc/warewulf/nodes.conf": sampleNodesConf,
|
||||
},
|
||||
request: func(serverURL string) (*http.Request, error) {
|
||||
return http.NewRequest(http.MethodGet, serverURL+"/api/overlays/multioverlay/file?path=multi-symlink.ww&render=node1", nil)
|
||||
},
|
||||
response: `{
|
||||
"overlay": "multioverlay",
|
||||
"path": "multi-symlink.ww",
|
||||
"contents": "Filename: link1\nSymlink: /target1\nFilename: link2\nSymlink: /target2\n",
|
||||
"perms": "<<PRESENCE>>",
|
||||
"uid": "<<PRESENCE>>",
|
||||
"gid": "<<PRESENCE>>"
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "delete overlay file",
|
||||
initFiles: map[string]string{
|
||||
|
||||
Reference in New Issue
Block a user