156 lines
4.5 KiB
Go
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.EnableTLS(),
|
|
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.statusStage, path.Base(stageFile), ctx.rinfo.ipaddr)
|
|
|
|
} else if stageFile == "" {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
wwlog.Error("No resource selected")
|
|
updateStatus(ctx.remoteNode.Id(), ctx.statusStage, "BAD_REQUEST", ctx.rinfo.ipaddr)
|
|
|
|
} else {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
wwlog.Error("Not found: %s", stageFile)
|
|
updateStatus(ctx.remoteNode.Id(), ctx.statusStage, "NOT_FOUND", ctx.rinfo.ipaddr)
|
|
}
|
|
}
|