Refactor server to separate handlers
Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
package configure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
||||
"github.com/warewulf/warewulf/internal/pkg/image"
|
||||
"github.com/warewulf/warewulf/internal/pkg/util"
|
||||
"github.com/warewulf/warewulf/internal/pkg/warewulfd"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
@@ -35,7 +36,7 @@ func TFTP() (err error) {
|
||||
}
|
||||
|
||||
if controller.Warewulf.GrubBoot() {
|
||||
err := warewulfd.CopyShimGrub()
|
||||
err := CopyShimGrub()
|
||||
if err != nil {
|
||||
wwlog.Warn("error when copying shim/grub binaries: %s", err)
|
||||
}
|
||||
@@ -70,3 +71,30 @@ func TFTP() (err error) {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CopyShimGrub() (err error) {
|
||||
conf := warewulfconf.Get()
|
||||
wwlog.Debug("copy shim and grub binaries from host")
|
||||
shimPath := image.ShimFind("")
|
||||
if shimPath == "" {
|
||||
return fmt.Errorf("no shim found on the host os")
|
||||
}
|
||||
err = util.CopyFile(shimPath, path.Join(conf.TFTP.TftpRoot, "warewulf", "shim.efi"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = os.Chmod(path.Join(conf.TFTP.TftpRoot, "warewulf", "shim.efi"), 0o755)
|
||||
grubPath := image.GrubFind("")
|
||||
if grubPath == "" {
|
||||
return fmt.Errorf("no grub found on host os")
|
||||
}
|
||||
err = util.CopyFile(grubPath, path.Join(conf.TFTP.TftpRoot, "warewulf", "grub.efi"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = os.Chmod(path.Join(conf.TFTP.TftpRoot, "warewulf", "grub.efi"), 0o755)
|
||||
err = util.CopyFile(grubPath, path.Join(conf.TFTP.TftpRoot, "warewulf", "grubx64.efi"))
|
||||
_ = os.Chmod(path.Join(conf.TFTP.TftpRoot, "warewulf", "grubx64.efi"), 0o755)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package warewulfd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
|
||||
"github.com/warewulf/warewulf/internal/pkg/image"
|
||||
"github.com/warewulf/warewulf/internal/pkg/util"
|
||||
)
|
||||
|
||||
/*
|
||||
Copies the default shim, which is the shim located on host
|
||||
to the tftp directory
|
||||
*/
|
||||
|
||||
func CopyShimGrub() (err error) {
|
||||
conf := warewulfconf.Get()
|
||||
wwlog.Debug("copy shim and grub binaries from host")
|
||||
shimPath := image.ShimFind("")
|
||||
if shimPath == "" {
|
||||
return fmt.Errorf("no shim found on the host os")
|
||||
}
|
||||
err = util.CopyFile(shimPath, path.Join(conf.TFTP.TftpRoot, "warewulf", "shim.efi"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = os.Chmod(path.Join(conf.TFTP.TftpRoot, "warewulf", "shim.efi"), 0o755)
|
||||
grubPath := image.GrubFind("")
|
||||
if grubPath == "" {
|
||||
return fmt.Errorf("no grub found on host os")
|
||||
}
|
||||
err = util.CopyFile(grubPath, path.Join(conf.TFTP.TftpRoot, "warewulf", "grub.efi"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = os.Chmod(path.Join(conf.TFTP.TftpRoot, "warewulf", "grub.efi"), 0o755)
|
||||
err = util.CopyFile(grubPath, path.Join(conf.TFTP.TftpRoot, "warewulf", "grubx64.efi"))
|
||||
_ = os.Chmod(path.Join(conf.TFTP.TftpRoot, "warewulf", "grubx64.efi"), 0o755)
|
||||
|
||||
return
|
||||
}
|
||||
60
internal/pkg/warewulfd/efi.go
Normal file
60
internal/pkg/warewulfd/efi.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package warewulfd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/warewulf/warewulf/internal/pkg/image"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
// HandleEfiBoot handles EFI boot file requests (shim, grub, grub.cfg)
|
||||
func HandleEfiBoot(w http.ResponseWriter, req *http.Request) {
|
||||
ctx, err := initHandleRequest(w, req)
|
||||
if err != nil {
|
||||
return // response already written
|
||||
}
|
||||
|
||||
if !ctx.remoteNode.Valid() {
|
||||
wwlog.Error("%s (unknown/unconfigured node)", ctx.rinfo.hwaddr)
|
||||
sendResponse(w, req, "", nil, ctx)
|
||||
return
|
||||
}
|
||||
|
||||
wwlog.Debug("requested method: %s", req.Method)
|
||||
imageName := ctx.remoteNode.ImageName
|
||||
var stageFile string
|
||||
var tmplData *templateVars
|
||||
|
||||
switch ctx.rinfo.efifile {
|
||||
case "shim.efi":
|
||||
stageFile = image.ShimFind(imageName)
|
||||
if stageFile == "" {
|
||||
wwlog.Error("couldn't find shim.efi for %s", imageName)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
case "grub.efi", "grub-tpm.efi", "grubx64.efi", "grubia32.efi", "grubaa64.efi", "grubarm.efi":
|
||||
stageFile = image.GrubFind(imageName)
|
||||
if stageFile == "" {
|
||||
wwlog.Error("could't find grub*.efi for %s", imageName)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
case "grub.cfg":
|
||||
stageFile = path.Join(ctx.conf.Paths.Sysconfdir, "warewulf/grub/grub.cfg.ww")
|
||||
tmplData = buildTemplateVars(ctx.conf, ctx.rinfo, ctx.remoteNode)
|
||||
if stageFile == "" {
|
||||
wwlog.Error("could't find grub.cfg template for %s", imageName)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
default:
|
||||
wwlog.ErrorExc(fmt.Errorf("could't find efiboot file: %s", ctx.rinfo.efifile), "")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
sendResponse(w, req, stageFile, tmplData, ctx)
|
||||
}
|
||||
33
internal/pkg/warewulfd/grub.go
Normal file
33
internal/pkg/warewulfd/grub.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package warewulfd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/warewulf/warewulf/internal/pkg/image"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
// HandleGrub handles direct GRUB binary requests
|
||||
func HandleGrub(w http.ResponseWriter, req *http.Request) {
|
||||
ctx, err := initHandleRequest(w, req)
|
||||
if err != nil {
|
||||
return // response already written
|
||||
}
|
||||
|
||||
var stageFile string
|
||||
|
||||
if !ctx.remoteNode.Valid() {
|
||||
wwlog.Error("%s (unknown/unconfigured node)", ctx.rinfo.hwaddr)
|
||||
} else {
|
||||
if ctx.remoteNode.ImageName != "" {
|
||||
stageFile = image.GrubFind(ctx.remoteNode.ImageName)
|
||||
if stageFile == "" {
|
||||
wwlog.Error("No grub found for image %s", ctx.remoteNode.ImageName)
|
||||
}
|
||||
} else {
|
||||
wwlog.Warn("No conainer set for node %s", ctx.remoteNode.Id())
|
||||
}
|
||||
}
|
||||
|
||||
sendResponse(w, req, stageFile, nil, ctx)
|
||||
}
|
||||
155
internal/pkg/warewulfd/helpers.go
Normal file
155
internal/pkg/warewulfd/helpers.go
Normal file
@@ -0,0 +1,155 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
30
internal/pkg/warewulfd/image.go
Normal file
30
internal/pkg/warewulfd/image.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package warewulfd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/warewulf/warewulf/internal/pkg/image"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
// HandleImage handles container/image requests
|
||||
func HandleImage(w http.ResponseWriter, req *http.Request) {
|
||||
ctx, err := initHandleRequest(w, req)
|
||||
if err != nil {
|
||||
return // response already written
|
||||
}
|
||||
|
||||
var stageFile string
|
||||
|
||||
if !ctx.remoteNode.Valid() {
|
||||
wwlog.Error("%s (unknown/unconfigured node)", ctx.rinfo.hwaddr)
|
||||
} else {
|
||||
if ctx.remoteNode.ImageName != "" {
|
||||
stageFile = image.ImageFile(ctx.remoteNode.ImageName)
|
||||
} else {
|
||||
wwlog.Warn("No image set for node %s", ctx.remoteNode.Id())
|
||||
}
|
||||
}
|
||||
|
||||
sendResponse(w, req, stageFile, nil, ctx)
|
||||
}
|
||||
39
internal/pkg/warewulfd/initramfs.go
Normal file
39
internal/pkg/warewulfd/initramfs.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package warewulfd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/warewulf/warewulf/internal/pkg/image"
|
||||
"github.com/warewulf/warewulf/internal/pkg/kernel"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
// HandleInitramfs handles initramfs requests
|
||||
func HandleInitramfs(w http.ResponseWriter, req *http.Request) {
|
||||
ctx, err := initHandleRequest(w, req)
|
||||
if err != nil {
|
||||
return // response already written
|
||||
}
|
||||
|
||||
var stageFile string
|
||||
|
||||
if !ctx.remoteNode.Valid() {
|
||||
wwlog.Error("%s (unknown/unconfigured node)", ctx.rinfo.hwaddr)
|
||||
} else {
|
||||
if kernel_ := kernel.FromNode(&ctx.remoteNode); kernel_ != nil {
|
||||
if kver := kernel_.Version(); kver != "" {
|
||||
if initramfs := image.FindInitramfs(ctx.remoteNode.ImageName, kver); initramfs != nil {
|
||||
stageFile = initramfs.FullPath()
|
||||
} else {
|
||||
wwlog.Error("No initramfs found for kernel %s in image %s", kver, ctx.remoteNode.ImageName)
|
||||
}
|
||||
} else {
|
||||
wwlog.Error("No initramfs found: unable to determine kernel version for node %s", ctx.remoteNode.Id())
|
||||
}
|
||||
} else {
|
||||
wwlog.Error("No initramfs found: unable to find kernel for node %s", ctx.remoteNode.Id())
|
||||
}
|
||||
}
|
||||
|
||||
sendResponse(w, req, stageFile, nil, ctx)
|
||||
}
|
||||
35
internal/pkg/warewulfd/ipxe.go
Normal file
35
internal/pkg/warewulfd/ipxe.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package warewulfd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
// HandleIpxe handles iPXE boot script requests
|
||||
func HandleIpxe(w http.ResponseWriter, req *http.Request) {
|
||||
ctx, err := initHandleRequest(w, req)
|
||||
if err != nil {
|
||||
return // response already written
|
||||
}
|
||||
|
||||
var stageFile string
|
||||
var tmplData *templateVars
|
||||
|
||||
if !ctx.remoteNode.Valid() {
|
||||
wwlog.Error("%s (unknown/unconfigured node)", ctx.rinfo.hwaddr)
|
||||
stageFile = path.Join(ctx.conf.Paths.Sysconfdir, "/warewulf/ipxe/unconfigured.ipxe")
|
||||
tmplData = &templateVars{
|
||||
Hwaddr: ctx.rinfo.hwaddr}
|
||||
} else {
|
||||
template := ctx.remoteNode.Ipxe
|
||||
if template == "" {
|
||||
template = "default"
|
||||
}
|
||||
stageFile = path.Join(ctx.conf.Paths.Sysconfdir, "warewulf/ipxe", template+".ipxe")
|
||||
tmplData = buildTemplateVars(ctx.conf, ctx.rinfo, ctx.remoteNode)
|
||||
}
|
||||
|
||||
sendResponse(w, req, stageFile, tmplData, ctx)
|
||||
}
|
||||
34
internal/pkg/warewulfd/kernel.go
Normal file
34
internal/pkg/warewulfd/kernel.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package warewulfd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/warewulf/warewulf/internal/pkg/kernel"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
// HandleKernel handles kernel binary requests
|
||||
func HandleKernel(w http.ResponseWriter, req *http.Request) {
|
||||
ctx, err := initHandleRequest(w, req)
|
||||
if err != nil {
|
||||
return // response already written
|
||||
}
|
||||
|
||||
var stageFile string
|
||||
|
||||
if !ctx.remoteNode.Valid() {
|
||||
wwlog.Error("%s (unknown/unconfigured node)", ctx.rinfo.hwaddr)
|
||||
} else {
|
||||
kernel_ := kernel.FromNode(&ctx.remoteNode)
|
||||
if kernel_ == nil {
|
||||
wwlog.Error("No kernel found for node %s", ctx.remoteNode.Id())
|
||||
} else {
|
||||
stageFile = kernel_.FullPath()
|
||||
if stageFile == "" {
|
||||
wwlog.Error("No kernel path found for node %s", ctx.remoteNode.Id())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendResponse(w, req, stageFile, nil, ctx)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package warewulfd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -16,7 +17,49 @@ import (
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
func OverlaySend(w http.ResponseWriter, req *http.Request) {
|
||||
// HandleOverlay handles system and runtime overlay requests
|
||||
func HandleOverlay(w http.ResponseWriter, req *http.Request) {
|
||||
ctx, err := initHandleRequest(w, req)
|
||||
if err != nil {
|
||||
return // response already written
|
||||
}
|
||||
|
||||
if !ctx.remoteNode.Valid() {
|
||||
wwlog.Error("%s (unknown/unconfigured node)", ctx.rinfo.hwaddr)
|
||||
sendResponse(w, req, "", nil, ctx)
|
||||
return
|
||||
}
|
||||
|
||||
var context string
|
||||
var request_overlays []string
|
||||
|
||||
if len(ctx.rinfo.overlay) > 0 {
|
||||
request_overlays = strings.Split(ctx.rinfo.overlay, ",")
|
||||
} else {
|
||||
context = ctx.rinfo.stage
|
||||
}
|
||||
|
||||
stageFile, err := getOverlayFile(
|
||||
ctx.remoteNode,
|
||||
context,
|
||||
request_overlays,
|
||||
ctx.conf.Warewulf.AutobuildOverlays())
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, overlay.ErrDoesNotExist) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
wwlog.ErrorExc(err, "")
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
wwlog.ErrorExc(err, "")
|
||||
return
|
||||
}
|
||||
|
||||
sendResponse(w, req, stageFile, nil, ctx)
|
||||
}
|
||||
|
||||
func HandleOverlayFile(w http.ResponseWriter, req *http.Request) {
|
||||
rinfo, err := parseReqRender(req)
|
||||
if err != nil {
|
||||
message := "error parsing request: %s"
|
||||
|
||||
@@ -95,7 +95,7 @@ nodes:
|
||||
t.Run(description, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, tt.url, nil)
|
||||
w := httptest.NewRecorder()
|
||||
OverlaySend(w, req)
|
||||
HandleOverlayFile(w, req)
|
||||
res := w.Result()
|
||||
defer res.Body.Close()
|
||||
|
||||
|
||||
@@ -1,15 +1,83 @@
|
||||
package warewulfd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
type parserInfo struct {
|
||||
// requestContext holds the validated results of the parsed request
|
||||
type requestContext struct {
|
||||
conf *warewulfconf.WarewulfYaml
|
||||
rinfo parsedRequest
|
||||
remoteNode node.Node
|
||||
statusStage string
|
||||
}
|
||||
|
||||
// initHandleRequest performs common initial request parsing, security checks,
|
||||
// node lookup, and asset key validation. On error, it writes the HTTP error
|
||||
// response and returns a non-nil error so the caller can simply return.
|
||||
func initHandleRequest(w http.ResponseWriter, req *http.Request) (*requestContext, error) {
|
||||
wwlog.Debug("Requested URL: %s", req.URL.String())
|
||||
conf := warewulfconf.Get()
|
||||
rinfo, err := parseRequest(req)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
wwlog.ErrorExc(err, "Bad status")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wwlog.Debug("stage: %s", rinfo.stage)
|
||||
|
||||
wwlog.Info("request from hwaddr:%s ipaddr:%s | stage:%s", rinfo.hwaddr, req.RemoteAddr, rinfo.stage)
|
||||
|
||||
if (rinfo.stage == "runtime" || len(rinfo.overlay) > 0) && conf.Warewulf.Secure() {
|
||||
if rinfo.remoteport >= 1024 {
|
||||
wwlog.Denied("Non-privileged port: %s", req.RemoteAddr)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return nil, fmt.Errorf("non-privileged port")
|
||||
}
|
||||
}
|
||||
|
||||
status_stages := map[string]string{
|
||||
"efiboot": "EFI",
|
||||
"ipxe": "IPXE",
|
||||
"kernel": "KERNEL",
|
||||
"system": "SYSTEM_OVERLAY",
|
||||
"runtime": "RUNTIME_OVERLAY",
|
||||
"initramfs": "INITRAMFS"}
|
||||
|
||||
statusStage := status_stages[rinfo.stage]
|
||||
|
||||
remoteNode, err := GetNodeOrSetDiscoverable(rinfo.hwaddr, conf.Warewulf.AutobuildOverlays())
|
||||
if err != nil && err != node.ErrNoUnconfigured {
|
||||
wwlog.ErrorExc(err, "")
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if remoteNode.AssetKey != "" && remoteNode.AssetKey != rinfo.assetkey {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
wwlog.Denied("incorrect asset key: node %s: %s", remoteNode.Id(), rinfo.assetkey)
|
||||
updateStatus(remoteNode.Id(), statusStage, "BAD_ASSET", rinfo.ipaddr)
|
||||
return nil, fmt.Errorf("incorrect asset key")
|
||||
}
|
||||
|
||||
return &requestContext{
|
||||
conf: conf,
|
||||
rinfo: rinfo,
|
||||
remoteNode: remoteNode,
|
||||
statusStage: statusStage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type parsedRequest struct {
|
||||
hwaddr string
|
||||
ipaddr string
|
||||
remoteport int
|
||||
@@ -21,8 +89,8 @@ type parserInfo struct {
|
||||
compress string
|
||||
}
|
||||
|
||||
func parseReq(req *http.Request) (parserInfo, error) {
|
||||
var ret parserInfo
|
||||
func parseRequest(req *http.Request) (parsedRequest, error) {
|
||||
var ret parsedRequest
|
||||
|
||||
url := strings.Split(req.URL.Path, "?")[0]
|
||||
path_parts := strings.Split(url, "/")
|
||||
@@ -62,19 +130,20 @@ func parseReq(req *http.Request) (parserInfo, error) {
|
||||
ret.stage = req.URL.Query()["stage"][0]
|
||||
} else {
|
||||
|
||||
if stage == "ipxe" || stage == "provision" {
|
||||
switch stage {
|
||||
case "ipxe", "provision":
|
||||
ret.stage = "ipxe"
|
||||
} else if stage == "kernel" {
|
||||
case "kernel":
|
||||
ret.stage = "kernel"
|
||||
} else if stage == "image" {
|
||||
case "image", "container":
|
||||
ret.stage = "image"
|
||||
} else if stage == "overlay-system" {
|
||||
case "overlay-system":
|
||||
ret.stage = "system"
|
||||
} else if stage == "overlay-runtime" {
|
||||
case "overlay-runtime":
|
||||
ret.stage = "runtime"
|
||||
} else if stage == "efiboot" {
|
||||
case "efiboot":
|
||||
ret.stage = "efiboot"
|
||||
} else if stage == "initramfs" {
|
||||
case "initramfs":
|
||||
ret.stage = "initramfs"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,13 +12,13 @@ var parseReqTests = []struct {
|
||||
description string
|
||||
url string
|
||||
remoteAddr string
|
||||
result parserInfo
|
||||
result parsedRequest
|
||||
}{
|
||||
{
|
||||
description: "basic ipv4 request",
|
||||
url: "/provision/00:00:00:ff:ff:ff",
|
||||
remoteAddr: "10.5.1.1:9873",
|
||||
result: parserInfo{
|
||||
result: parsedRequest{
|
||||
hwaddr: "00:00:00:ff:ff:ff",
|
||||
ipaddr: "10.5.1.1",
|
||||
remoteport: 9873,
|
||||
@@ -29,7 +29,7 @@ var parseReqTests = []struct {
|
||||
description: "basic ipv6 request",
|
||||
url: "/provision/00:00:00:ff:ff:ff",
|
||||
remoteAddr: "[fd00:5::1:1]:9873",
|
||||
result: parserInfo{
|
||||
result: parsedRequest{
|
||||
hwaddr: "00:00:00:ff:ff:ff",
|
||||
ipaddr: "fd00:5::1:1",
|
||||
remoteport: 9873,
|
||||
@@ -38,14 +38,14 @@ var parseReqTests = []struct {
|
||||
},
|
||||
}
|
||||
|
||||
func Test_ParseReq(t *testing.T) {
|
||||
func Test_ParseRequest(t *testing.T) {
|
||||
for _, tt := range parseReqTests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
req := &http.Request{
|
||||
URL: &url.URL{Path: tt.url},
|
||||
RemoteAddr: tt.remoteAddr,
|
||||
}
|
||||
result, err := parseReq(req)
|
||||
result, err := parseRequest(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.result, result)
|
||||
})
|
||||
|
||||
@@ -1,24 +1,9 @@
|
||||
package warewulfd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"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/image"
|
||||
"github.com/warewulf/warewulf/internal/pkg/kernel"
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
"github.com/warewulf/warewulf/internal/pkg/overlay"
|
||||
"github.com/warewulf/warewulf/internal/pkg/util"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
@@ -44,341 +29,38 @@ type templateVars struct {
|
||||
NetDevs map[string]*node.NetDev
|
||||
}
|
||||
|
||||
func ProvisionSend(w http.ResponseWriter, req *http.Request) {
|
||||
wwlog.Debug("Requested URL: %s", req.URL.String())
|
||||
conf := warewulfconf.Get()
|
||||
rinfo, err := parseReq(req)
|
||||
func HandleProvision(w http.ResponseWriter, req *http.Request) {
|
||||
// Parse just enough to determine the stage
|
||||
rinfo, err := parseRequest(req)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
wwlog.ErrorExc(err, "Bad status")
|
||||
return
|
||||
}
|
||||
|
||||
wwlog.Debug("stage: %s", rinfo.stage)
|
||||
|
||||
wwlog.Info("request from hwaddr:%s ipaddr:%s | stage:%s", rinfo.hwaddr, req.RemoteAddr, rinfo.stage)
|
||||
|
||||
if (rinfo.stage == "runtime" || len(rinfo.overlay) > 0) && conf.Warewulf.Secure() {
|
||||
if rinfo.remoteport >= 1024 {
|
||||
wwlog.Denied("Non-privileged port: %s", req.RemoteAddr)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
status_stages := map[string]string{
|
||||
"efiboot": "EFI",
|
||||
"ipxe": "IPXE",
|
||||
"kernel": "KERNEL",
|
||||
"system": "SYSTEM_OVERLAY",
|
||||
"runtime": "RUNTIME_OVERLAY",
|
||||
"initramfs": "INITRAMFS"}
|
||||
|
||||
status_stage := status_stages[rinfo.stage]
|
||||
var stage_file string
|
||||
|
||||
// TODO: when module version is upgraded to go1.18, should be 'any' type
|
||||
var tmpl_data *templateVars
|
||||
|
||||
remoteNode, err := GetNodeOrSetDiscoverable(rinfo.hwaddr, conf.Warewulf.AutobuildOverlays())
|
||||
if err != nil && err != node.ErrNoUnconfigured {
|
||||
wwlog.ErrorExc(err, "")
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
if remoteNode.AssetKey != "" && remoteNode.AssetKey != rinfo.assetkey {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
wwlog.Denied("incorrect asset key: node %s: %s", remoteNode.Id(), rinfo.assetkey)
|
||||
updateStatus(remoteNode.Id(), status_stage, "BAD_ASSET", rinfo.ipaddr)
|
||||
return
|
||||
}
|
||||
|
||||
if !remoteNode.Valid() {
|
||||
wwlog.Error("%s (unknown/unconfigured node)", rinfo.hwaddr)
|
||||
if rinfo.stage == "ipxe" {
|
||||
stage_file = path.Join(conf.Paths.Sysconfdir, "/warewulf/ipxe/unconfigured.ipxe")
|
||||
tmpl_data = &templateVars{
|
||||
Hwaddr: rinfo.hwaddr}
|
||||
}
|
||||
|
||||
} else if rinfo.stage == "ipxe" {
|
||||
template := remoteNode.Ipxe
|
||||
if template == "" {
|
||||
template = "default"
|
||||
}
|
||||
stage_file = path.Join(conf.Paths.Sysconfdir, "warewulf/ipxe", template+".ipxe")
|
||||
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)
|
||||
}
|
||||
tmpl_data = &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,
|
||||
KernelArgs: kernelArgs,
|
||||
KernelVersion: kernelVersion,
|
||||
Root: remoteNode.Root,
|
||||
NetDevs: remoteNode.NetDevs,
|
||||
Tags: remoteNode.Tags}
|
||||
} else if rinfo.stage == "kernel" {
|
||||
kernel_ := kernel.FromNode(&remoteNode)
|
||||
if kernel_ == nil {
|
||||
wwlog.Error("No kernel found for node %s", remoteNode.Id())
|
||||
} else {
|
||||
stage_file = kernel_.FullPath()
|
||||
if stage_file == "" {
|
||||
wwlog.Error("No kernel path found for node %s", remoteNode.Id())
|
||||
}
|
||||
}
|
||||
|
||||
} else if rinfo.stage == "image" {
|
||||
if remoteNode.ImageName != "" {
|
||||
stage_file = image.ImageFile(remoteNode.ImageName)
|
||||
} else {
|
||||
wwlog.Warn("No image set for node %s", remoteNode.Id())
|
||||
}
|
||||
|
||||
} else if rinfo.stage == "system" || rinfo.stage == "runtime" {
|
||||
var context string
|
||||
var request_overlays []string
|
||||
|
||||
if len(rinfo.overlay) > 0 {
|
||||
request_overlays = strings.Split(rinfo.overlay, ",")
|
||||
} else {
|
||||
context = rinfo.stage
|
||||
}
|
||||
stage_file, err = getOverlayFile(
|
||||
remoteNode,
|
||||
context,
|
||||
request_overlays,
|
||||
conf.Warewulf.AutobuildOverlays())
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, overlay.ErrDoesNotExist) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
wwlog.ErrorExc(err, "")
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
wwlog.ErrorExc(err, "")
|
||||
return
|
||||
}
|
||||
} else if rinfo.stage == "efiboot" {
|
||||
wwlog.Debug("requested method: %s", req.Method)
|
||||
imageName := remoteNode.ImageName
|
||||
switch rinfo.efifile {
|
||||
case "shim.efi":
|
||||
stage_file = image.ShimFind(imageName)
|
||||
if stage_file == "" {
|
||||
wwlog.Error("couldn't find shim.efi for %s", imageName)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
case "grub.efi", "grub-tpm.efi", "grubx64.efi", "grubia32.efi", "grubaa64.efi", "grubarm.efi":
|
||||
stage_file = image.GrubFind(imageName)
|
||||
if stage_file == "" {
|
||||
wwlog.Error("could't find grub*.efi for %s", imageName)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
case "grub.cfg":
|
||||
stage_file = path.Join(conf.Paths.Sysconfdir, "warewulf/grub/grub.cfg.ww")
|
||||
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)
|
||||
}
|
||||
tmpl_data = &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}
|
||||
if stage_file == "" {
|
||||
wwlog.Error("could't find grub.cfg template for %s", imageName)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
default:
|
||||
wwlog.ErrorExc(fmt.Errorf("could't find efiboot file: %s", rinfo.efifile), "")
|
||||
}
|
||||
} else if rinfo.stage == "shim" {
|
||||
if remoteNode.ImageName != "" {
|
||||
stage_file = image.ShimFind(remoteNode.ImageName)
|
||||
|
||||
if stage_file == "" {
|
||||
wwlog.Error("No kernel found for image %s", remoteNode.ImageName)
|
||||
}
|
||||
} else {
|
||||
wwlog.Warn("No image set for this %s", remoteNode.Id())
|
||||
}
|
||||
} else if rinfo.stage == "grub" {
|
||||
if remoteNode.ImageName != "" {
|
||||
stage_file = image.GrubFind(remoteNode.ImageName)
|
||||
if stage_file == "" {
|
||||
wwlog.Error("No grub found for image %s", remoteNode.ImageName)
|
||||
}
|
||||
} else {
|
||||
wwlog.Warn("No conainer set for node %s", remoteNode.Id())
|
||||
}
|
||||
} else if rinfo.stage == "initramfs" {
|
||||
if kernel_ := kernel.FromNode(&remoteNode); kernel_ != nil {
|
||||
if kver := kernel_.Version(); kver != "" {
|
||||
if initramfs := image.FindInitramfs(remoteNode.ImageName, kver); initramfs != nil {
|
||||
stage_file = initramfs.FullPath()
|
||||
} else {
|
||||
wwlog.Error("No initramfs found for kernel %s in image %s", kver, remoteNode.ImageName)
|
||||
}
|
||||
} else {
|
||||
wwlog.Error("No initramfs found: unable to determine kernel version for node %s", remoteNode.Id())
|
||||
}
|
||||
} else {
|
||||
wwlog.Error("No initramfs found: unable to find kernel for node %s", remoteNode.Id())
|
||||
}
|
||||
}
|
||||
|
||||
wwlog.Serv("stage_file '%s'", stage_file)
|
||||
|
||||
if util.IsFile(stage_file) {
|
||||
|
||||
if tmpl_data != nil {
|
||||
if rinfo.compress != "" {
|
||||
wwlog.Error("Unsupported %s compressed version for file: %s",
|
||||
rinfo.compress, stage_file)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Create a template with the Sprig functions.
|
||||
tmpl := template.New(filepath.Base(stage_file)).Funcs(sprig.TxtFuncMap())
|
||||
|
||||
// Parse the template.
|
||||
parsedTmpl, err := tmpl.ParseFiles(stage_file)
|
||||
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, tmpl_data)
|
||||
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", stage_file, remoteNode.Id())
|
||||
|
||||
} else {
|
||||
if rinfo.compress == "gz" {
|
||||
stage_file += ".gz"
|
||||
|
||||
if !util.IsFile(stage_file) {
|
||||
wwlog.Error("unprepared for compressed version of file %s",
|
||||
stage_file)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
} else if rinfo.compress != "" {
|
||||
wwlog.Error("unsupported %s compressed version of file %s",
|
||||
rinfo.compress, stage_file)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
|
||||
err = sendFile(w, req, stage_file, remoteNode.Id())
|
||||
if err != nil {
|
||||
wwlog.ErrorExc(err, "")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
updateStatus(remoteNode.Id(), status_stage, path.Base(stage_file), rinfo.ipaddr)
|
||||
|
||||
} else if stage_file == "" {
|
||||
// Dispatch to the appropriate stage handler
|
||||
var handler http.HandlerFunc
|
||||
switch rinfo.stage {
|
||||
case "ipxe":
|
||||
handler = HandleIpxe
|
||||
case "kernel":
|
||||
handler = HandleKernel
|
||||
case "image":
|
||||
handler = HandleImage
|
||||
case "system", "runtime":
|
||||
handler = HandleOverlay
|
||||
case "efiboot":
|
||||
handler = HandleEfiBoot
|
||||
case "shim":
|
||||
handler = HandleShim
|
||||
case "grub":
|
||||
handler = HandleGrub
|
||||
case "initramfs":
|
||||
handler = HandleInitramfs
|
||||
default:
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
wwlog.Error("No resource selected")
|
||||
updateStatus(remoteNode.Id(), status_stage, "BAD_REQUEST", rinfo.ipaddr)
|
||||
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
wwlog.Error("Not found: %s", stage_file)
|
||||
updateStatus(remoteNode.Id(), status_stage, "NOT_FOUND", rinfo.ipaddr)
|
||||
wwlog.Error("Unknown stage: %s", rinfo.stage)
|
||||
return
|
||||
}
|
||||
|
||||
handler(w, req)
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ nodes:
|
||||
req := httptest.NewRequest(http.MethodGet, tt.url, nil)
|
||||
req.RemoteAddr = tt.ip
|
||||
w := httptest.NewRecorder()
|
||||
ProvisionSend(w, req)
|
||||
HandleProvision(w, req)
|
||||
res := w.Result()
|
||||
defer res.Body.Close()
|
||||
|
||||
|
||||
@@ -37,19 +37,20 @@ func (h *slashFix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.mux.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func configureHandler(includeRuntime bool, apiHandler http.Handler) *slashFix {
|
||||
func configureRootHandler(apiHandler http.Handler, includeRuntime bool) *slashFix {
|
||||
var wwHandler http.ServeMux
|
||||
wwHandler.HandleFunc("/provision/", warewulfd.ProvisionSend)
|
||||
wwHandler.HandleFunc("/ipxe/", warewulfd.ProvisionSend)
|
||||
wwHandler.HandleFunc("/efiboot/", warewulfd.ProvisionSend)
|
||||
wwHandler.HandleFunc("/kernel/", warewulfd.ProvisionSend)
|
||||
wwHandler.HandleFunc("/container/", warewulfd.ProvisionSend)
|
||||
wwHandler.HandleFunc("/overlay-system/", warewulfd.ProvisionSend)
|
||||
wwHandler.HandleFunc("/provision/", warewulfd.HandleProvision)
|
||||
wwHandler.HandleFunc("/ipxe/", warewulfd.HandleIpxe)
|
||||
wwHandler.HandleFunc("/efiboot/", warewulfd.HandleEfiBoot)
|
||||
wwHandler.HandleFunc("/kernel/", warewulfd.HandleKernel)
|
||||
wwHandler.HandleFunc("/image/", warewulfd.HandleImage)
|
||||
wwHandler.HandleFunc("/container/", warewulfd.HandleImage)
|
||||
wwHandler.HandleFunc("/overlay-system/", warewulfd.HandleOverlay)
|
||||
if includeRuntime {
|
||||
wwHandler.HandleFunc("/overlay-runtime/", warewulfd.ProvisionSend)
|
||||
wwHandler.HandleFunc("/overlay-runtime/", warewulfd.HandleOverlay)
|
||||
}
|
||||
wwHandler.HandleFunc("/overlay-file/", warewulfd.OverlaySend)
|
||||
wwHandler.HandleFunc("/status", warewulfd.StatusSend)
|
||||
wwHandler.HandleFunc("/overlay-file/", warewulfd.HandleOverlayFile)
|
||||
wwHandler.HandleFunc("/status", warewulfd.HandleStatus)
|
||||
|
||||
if apiHandler != nil {
|
||||
wwHandler.Handle("/api/", apiHandler)
|
||||
@@ -86,7 +87,7 @@ func RunServer() error {
|
||||
apiHandler = api.Handler(auth, conf.API.AllowedIPNets())
|
||||
}
|
||||
|
||||
httpHandler := configureHandler(!conf.Warewulf.EnableTLS(), apiHandler)
|
||||
httpHandler := configureRootHandler(apiHandler, !conf.Warewulf.EnableTLS())
|
||||
|
||||
errChan := make(chan error, 2)
|
||||
|
||||
@@ -97,7 +98,7 @@ func RunServer() error {
|
||||
if !util.IsFile(key) || !util.IsFile(crt) {
|
||||
return fmt.Errorf("TLS enabled but keys not found in %s, run 'wwctl configure tls --create' to generate keys", path.Join(conf.Paths.Sysconfdir, "warewulf", "tls"))
|
||||
}
|
||||
httpsHandler := configureHandler(true, apiHandler)
|
||||
httpsHandler := configureRootHandler(apiHandler, true)
|
||||
go func() {
|
||||
wwlog.Info("Starting HTTPS service on port %d", conf.Warewulf.SecurePort)
|
||||
if err := http.ListenAndServeTLS(":"+strconv.Itoa(conf.Warewulf.SecurePort), crt, key, httpsHandler); err != nil {
|
||||
|
||||
34
internal/pkg/warewulfd/shim.go
Normal file
34
internal/pkg/warewulfd/shim.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package warewulfd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/warewulf/warewulf/internal/pkg/image"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
// HandleShim handles direct shim binary requests
|
||||
func HandleShim(w http.ResponseWriter, req *http.Request) {
|
||||
ctx, err := initHandleRequest(w, req)
|
||||
if err != nil {
|
||||
return // response already written
|
||||
}
|
||||
|
||||
var stageFile string
|
||||
|
||||
if !ctx.remoteNode.Valid() {
|
||||
wwlog.Error("%s (unknown/unconfigured node)", ctx.rinfo.hwaddr)
|
||||
} else {
|
||||
if ctx.remoteNode.ImageName != "" {
|
||||
stageFile = image.ShimFind(ctx.remoteNode.ImageName)
|
||||
|
||||
if stageFile == "" {
|
||||
wwlog.Error("No kernel found for image %s", ctx.remoteNode.ImageName)
|
||||
}
|
||||
} else {
|
||||
wwlog.Warn("No image set for this %s", ctx.remoteNode.Id())
|
||||
}
|
||||
}
|
||||
|
||||
sendResponse(w, req, stageFile, nil, ctx)
|
||||
}
|
||||
@@ -92,7 +92,7 @@ func statusJSON() ([]byte, error) {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func StatusSend(w http.ResponseWriter, req *http.Request) {
|
||||
func HandleStatus(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
status, err := statusJSON()
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user