Files
warewulf/internal/pkg/warewulfd/helpers.go
Jonathon Anderson 939f4290f4 Move stage display formatting to wwctl
Signed-off-by: Jonathon Anderson <janderson@ciq.com>
2026-03-10 16:11:09 -06:00

156 lines
4.5 KiB
Go

package warewulfd
import (
"bytes"
"fmt"
"net/http"
"net/netip"
"path"
"path/filepath"
"strconv"
"strings"
"text/template"
"github.com/Masterminds/sprig/v3"
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
"github.com/warewulf/warewulf/internal/pkg/kernel"
"github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/util"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
// buildTemplateVars constructs the templateVars struct with all necessary
// fields, including handling IPv6 authority formatting and kernel version resolution.
func buildTemplateVars(conf *warewulfconf.WarewulfYaml, rinfo parsedRequest, remoteNode node.Node) *templateVars {
kernelArgs := ""
kernelVersion := ""
if remoteNode.Kernel != nil {
kernelArgs = strings.Join(remoteNode.Kernel.Args, " ")
kernelVersion = remoteNode.Kernel.Version
}
if kernelVersion == "" {
if kernel_ := kernel.FromNode(&remoteNode); kernel_ != nil {
kernelVersion = kernel_.Version()
}
}
authority := fmt.Sprintf("%s:%d", conf.Ipaddr, conf.Warewulf.Port)
ipaddr6 := ""
if confIpaddr6, err := netip.ParseAddr(conf.Ipaddr6); err == nil {
ipaddr6 = confIpaddr6.String()
}
if rinfoIpaddr, err := netip.ParseAddr(rinfo.ipaddr); err == nil {
if rinfoIpaddr.Is6() {
if ipaddr6 != "" {
authority = fmt.Sprintf("[%s]:%d", ipaddr6, conf.Warewulf.Port)
} else {
wwlog.Error("No valid IPv6 address configured, but request is IPv6")
}
}
} else {
wwlog.Error("Could not parse request IP address: %s", rinfo.ipaddr)
}
return &templateVars{
Id: remoteNode.Id(),
Cluster: remoteNode.ClusterName,
Fqdn: remoteNode.Id(),
Ipaddr: conf.Ipaddr,
Ipaddr6: ipaddr6,
Port: strconv.Itoa(conf.Warewulf.Port),
TLS: conf.Warewulf.TLSEnabled(),
Authority: authority,
Hostname: remoteNode.Id(),
Hwaddr: rinfo.hwaddr,
ImageName: remoteNode.ImageName,
Ipxe: remoteNode.Ipxe,
KernelArgs: kernelArgs,
KernelVersion: kernelVersion,
Root: remoteNode.Root,
NetDevs: remoteNode.NetDevs,
Tags: remoteNode.Tags}
}
// sendResponse handles the common response logic for provision handlers.
// If tmplData is non-nil, it renders the stageFile as a template. Otherwise, it
// sends stageFile as a raw file (with optional .gz compression).
func sendResponse(w http.ResponseWriter, req *http.Request, stageFile string, tmplData *templateVars, ctx *requestContext) {
wwlog.Serv("stage_file '%s'", stageFile)
if util.IsFile(stageFile) {
if tmplData != nil {
if ctx.rinfo.compress != "" {
wwlog.Error("Unsupported %s compressed version for file: %s",
ctx.rinfo.compress, stageFile)
w.WriteHeader(http.StatusNotFound)
return
}
// Create a template with the Sprig functions.
tmpl := template.New(filepath.Base(stageFile)).Funcs(sprig.TxtFuncMap())
// Parse the template.
parsedTmpl, err := tmpl.ParseFiles(stageFile)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
wwlog.ErrorExc(err, "")
return
}
// template engine writes file to buffer in case rendering fails
var buf bytes.Buffer
err = parsedTmpl.Execute(&buf, tmplData)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
wwlog.ErrorExc(err, "")
return
}
w.Header().Set("Content-Type", "text")
w.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
_, err = buf.WriteTo(w)
if err != nil {
wwlog.ErrorExc(err, "")
}
wwlog.Info("send %s -> %s", stageFile, ctx.remoteNode.Id())
} else {
if ctx.rinfo.compress == "gz" {
stageFile += ".gz"
if !util.IsFile(stageFile) {
wwlog.Error("unprepared for compressed version of file %s",
stageFile)
w.WriteHeader(http.StatusNotFound)
return
}
} else if ctx.rinfo.compress != "" {
wwlog.Error("unsupported %s compressed version of file %s",
ctx.rinfo.compress, stageFile)
w.WriteHeader(http.StatusNotFound)
}
err := sendFile(w, req, stageFile, ctx.remoteNode.Id())
if err != nil {
wwlog.ErrorExc(err, "")
return
}
}
updateStatus(ctx.remoteNode.Id(), ctx.rinfo.stage, path.Base(stageFile), ctx.rinfo.ipaddr)
} else if stageFile == "" {
w.WriteHeader(http.StatusBadRequest)
wwlog.Error("No resource selected")
updateStatus(ctx.remoteNode.Id(), ctx.rinfo.stage, "BAD_REQUEST", ctx.rinfo.ipaddr)
} else {
w.WriteHeader(http.StatusNotFound)
wwlog.Error("Not found: %s", stageFile)
updateStatus(ctx.remoteNode.Id(), ctx.rinfo.stage, "NOT_FOUND", ctx.rinfo.ipaddr)
}
}