diff --git a/cmd/warewulfd/main.go b/cmd/warewulfd/main.go new file mode 100644 index 00000000..019de38f --- /dev/null +++ b/cmd/warewulfd/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "github.com/hpcng/warewulf/internal/app/warewulfd" +) + + +func main() { + root := warewulfd.GetRootCommand() + + root.Execute() +} \ No newline at end of file diff --git a/internal/app/warewulfd/root.go b/internal/app/warewulfd/root.go new file mode 100644 index 00000000..a4dd41a1 --- /dev/null +++ b/internal/app/warewulfd/root.go @@ -0,0 +1,40 @@ +package warewulfd + +import ( + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/spf13/cobra" +) + +var ( + WarewulfdCmd = &cobra.Command{ + Use: "warewulfd", + Short: "Warewulf Daemon Service", + Long: "This is the primary Warewulf service for provisioning nodes", + RunE: CobraRunE, + PersistentPreRunE: rootPersistentPreRunE, + } + verboseArg bool + debugArg bool +) + + +func init() { + WarewulfdCmd.PersistentFlags().BoolVarP(&verboseArg, "verbose", "v", false, "Run with increased verbosity.") + WarewulfdCmd.PersistentFlags().BoolVarP(&debugArg, "debug", "d", false, "Run with debugging messages enabled.") +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetRootCommand() *cobra.Command { + return WarewulfdCmd +} + +func rootPersistentPreRunE(cmd *cobra.Command, args []string) error { + if verboseArg == true { + wwlog.SetLevel(wwlog.VERBOSE) + } else if debugArg == true { + wwlog.SetLevel(wwlog.DEBUG) + } else { + wwlog.SetLevel(wwlog.INFO) + } + return nil +} \ No newline at end of file diff --git a/internal/app/warewulfd/warewulfd-reponses/ipxe.go b/internal/app/warewulfd/warewulfd-reponses/ipxe.go new file mode 100644 index 00000000..0cb2b262 --- /dev/null +++ b/internal/app/warewulfd/warewulfd-reponses/ipxe.go @@ -0,0 +1,66 @@ +package warewulfd_responses + +import ( + "bufio" + "fmt" + "github.com/hpcng/warewulf/internal/pkg/assets" + "github.com/hpcng/warewulf/internal/pkg/config" + "log" + "net/http" + "os" + "strconv" + "strings" +) + +func IpxeSend(w http.ResponseWriter, req *http.Request) { + url := strings.Split(req.URL.Path, "/") + + if url[2] == "" { + log.Printf("ERROR: Bad iPXE request from %s\n", req.RemoteAddr) + return + } + + hwaddr := strings.ReplaceAll(url[2], "-", ":") + node, err := assets.FindByHwaddr(hwaddr) + if err != nil { + log.Printf("Could not find HW Addr: %s: %s\n", hwaddr, err) + w.WriteHeader(404) + return + } + + if node.HostName != "" { + log.Printf("IPXE: %15s: %s\n", node.Fqdn, req.URL.Path) + + conf := config.New() + + ipxeTemplate := fmt.Sprintf("/etc/warewulf/ipxe/%s.ipxe", node.Ipxe) + sourceFD, err := os.Open(ipxeTemplate) + if err != nil { + log.Printf("ERROR: Could not open iPXE Template: %s\n", err) + w.WriteHeader(404) + return + } + + scanner := bufio.NewScanner(sourceFD) + + for scanner.Scan() { + newLine := scanner.Text() + + // TODO: Update this to use templates instead of replaces + newLine = strings.ReplaceAll(newLine, "@HWADDR@", url[2]) + newLine = strings.ReplaceAll(newLine, "@IPADDR@", conf.Ipaddr) + newLine = strings.ReplaceAll(newLine, "@HOSTNAME@", node.HostName) + newLine = strings.ReplaceAll(newLine, "@FQDN@", node.Fqdn) + newLine = strings.ReplaceAll(newLine, "@PORT@", strconv.Itoa(conf.Port)) + // TODO: Add KernelArgs to nodes.conf + //newLine = strings.ReplaceAll(newLine, "@KERNELARGS@", node.KernelArgs) + + fmt.Fprintln(w, newLine) + } + log.Printf("SEND: %15s: %s\n", node.Fqdn, ipxeTemplate) + + } else { + log.Printf("ERROR: iPXE request from unknown Node (hwaddr=%s)\n", url[2]) + } + return +} diff --git a/internal/app/warewulfd/warewulfd-reponses/kernel.go b/internal/app/warewulfd/warewulfd-reponses/kernel.go new file mode 100644 index 00000000..018a9d8f --- /dev/null +++ b/internal/app/warewulfd/warewulfd-reponses/kernel.go @@ -0,0 +1,35 @@ +package warewulfd_responses + +import ( + "github.com/hpcng/warewulf/internal/pkg/config" + "log" + "net/http" +) + +func KernelSend(w http.ResponseWriter, req *http.Request) { + config := config.New() + + node, err := getSanity(req) + if err != nil { + w.WriteHeader(404) + log.Println(err) + return + } + + if node.KernelVersion != "" { + fileName := config.KernelImage(node.KernelVersion) + + err := sendFile(w, fileName, node.Fqdn) + if err != nil { + log.Printf("ERROR: %s\n", err) + } else { + log.Printf("SEND: %15s: %s\n", node.Fqdn, fileName) + } + + } else { + w.WriteHeader(503) + log.Printf("ERROR: No 'kernel version' set for node %s\n", node.Fqdn) + } + + return +} diff --git a/internal/app/warewulfd/warewulfd-reponses/kmods.go b/internal/app/warewulfd/warewulfd-reponses/kmods.go new file mode 100644 index 00000000..dff142f1 --- /dev/null +++ b/internal/app/warewulfd/warewulfd-reponses/kmods.go @@ -0,0 +1,34 @@ +package warewulfd_responses + +import ( + "github.com/hpcng/warewulf/internal/pkg/config" + "log" + "net/http" +) + +func KmodsSend(w http.ResponseWriter, req *http.Request) { + config := config.New() + + node, err := getSanity(req) + if err != nil { + w.WriteHeader(404) + log.Println(err) + return + } + if node.KernelVersion != "" { + fileName := config.KmodsImage(node.KernelVersion) + + err := sendFile(w, fileName, node.Fqdn) + if err != nil { + log.Printf("ERROR: %s\n", err) + } else { + log.Printf("SEND: %15s: %s\n", node.Fqdn, fileName) + } + + } else { + w.WriteHeader(503) + log.Printf("ERROR: No 'kernel version' set for node %s\n", node.Fqdn) + } + + return +} diff --git a/internal/app/warewulfd/warewulfd-reponses/runtime.go b/internal/app/warewulfd/warewulfd-reponses/runtime.go new file mode 100644 index 00000000..b173ddc7 --- /dev/null +++ b/internal/app/warewulfd/warewulfd-reponses/runtime.go @@ -0,0 +1,67 @@ +package warewulfd_responses + +import ( + "fmt" + "github.com/hpcng/warewulf/internal/pkg/assets" + "github.com/hpcng/warewulf/internal/pkg/config" + "log" + "net/http" + "strconv" + "strings" +) + +func RuntimeOverlaySend(w http.ResponseWriter, req *http.Request) { + config := config.New() + + remote := strings.Split(req.RemoteAddr, ":") + port, err := strconv.Atoi(remote[1]) + if err != nil { + log.Printf("Could not convert port to integer: %s\n", remote[1]) + w.WriteHeader(503) + return + } + + if err != nil { + fmt.Printf("ERROR: Could not load configuration file: %s\n", err) + return + } + + if config.InsecureRuntime == false { + if port >= 1024 { + log.Panicf("DENIED: Connection coming from non-privledged port: %s\n", req.RemoteAddr) + w.WriteHeader(401) + return + } + } + + node, err := assets.FindByIpaddr(remote[0]) + if err != nil { + fmt.Printf("Could not find node by IP address: %s\n", remote[0]) + w.WriteHeader(404) + return + } + + if node.Fqdn == "" { + log.Printf("UNKNOWN: %15s: %s\n", remote[0], req.URL.Path) + w.WriteHeader(404) + return + } else { + log.Printf("REQ: %15s: %s\n", node.Fqdn, req.URL.Path) + } + + if node.RuntimeOverlay != "" { + fileName := config.RuntimeOverlayImage(node.Fqdn) + + err := sendFile(w, fileName, node.Fqdn) + if err != nil { + log.Printf("ERROR: %s\n", err) + } else { + log.Printf("SEND: %15s: %s\n", node.Fqdn, fileName) + } + } else { + w.WriteHeader(503) + log.Printf("ERROR: No 'runtime system-overlay' set for node %s\n", node.Fqdn) + } + + return +} diff --git a/internal/app/warewulfd/warewulfd-reponses/system.go b/internal/app/warewulfd/warewulfd-reponses/system.go new file mode 100644 index 00000000..ffae0cf9 --- /dev/null +++ b/internal/app/warewulfd/warewulfd-reponses/system.go @@ -0,0 +1,34 @@ +package warewulfd_responses + +import ( + "github.com/hpcng/warewulf/internal/pkg/config" + "log" + "net/http" +) + +func SystemOverlaySend(w http.ResponseWriter, req *http.Request) { + config := config.New() + + node, err := getSanity(req) + if err != nil { + w.WriteHeader(404) + log.Println(err) + return + } + + if node.SystemOverlay != "" { + fileName := config.SystemOverlayImage(node.Fqdn) + + err := sendFile(w, fileName, node.Fqdn) + if err != nil { + log.Printf("ERROR: %s\n", err) + } else { + log.Printf("SEND: %15s: %s\n", node.Fqdn, fileName) + } + } else { + w.WriteHeader(503) + log.Printf("ERROR: No 'system system-overlay' set for node %s\n", node.Fqdn) + } + + return +} diff --git a/internal/app/warewulfd/warewulfd-reponses/util.go b/internal/app/warewulfd/warewulfd-reponses/util.go new file mode 100644 index 00000000..293a6f58 --- /dev/null +++ b/internal/app/warewulfd/warewulfd-reponses/util.go @@ -0,0 +1,55 @@ +package warewulfd_responses + +import ( + "github.com/hpcng/warewulf/internal/pkg/assets" + "github.com/hpcng/warewulf/internal/pkg/errors" + "io" + "log" + "net/http" + "os" + "strconv" + "strings" +) + +func getSanity(req *http.Request) (assets.NodeInfo, error) { + url := strings.Split(req.URL.Path, "/") + + hwaddr := strings.ReplaceAll(url[2], "-", ":") + node, err := assets.FindByHwaddr(hwaddr) + if err != nil { + return node, errors.New("Could not find node by HW address") + } + + if node.Fqdn == "" { + log.Printf("UNKNOWN: %15s: %s\n", hwaddr, req.URL.Path) + return node, errors.New("Unknown node HW address: " + hwaddr) + } else { + log.Printf("REQ: %15s: %s\n", node.Fqdn, req.URL.Path) + } + + return node, nil +} + +func sendFile(w http.ResponseWriter, filename string, sendto string) error { + + fd, err := os.Open(filename) + if err != nil { + return err + } + + FileHeader := make([]byte, 512) + fd.Read(FileHeader) + FileContentType := http.DetectContentType(FileHeader) + FileStat, _ := fd.Stat() + FileSize := strconv.FormatInt(FileStat.Size(), 10) + + w.Header().Set("Content-Disposition", "attachment; filename=kernel") + w.Header().Set("Content-Type", FileContentType) + w.Header().Set("Content-Length", FileSize) + + fd.Seek(0, 0) + io.Copy(w, fd) + + fd.Close() + return nil +} diff --git a/internal/app/warewulfd/warewulfd-reponses/vnfs.go b/internal/app/warewulfd/warewulfd-reponses/vnfs.go new file mode 100644 index 00000000..1967b7ab --- /dev/null +++ b/internal/app/warewulfd/warewulfd-reponses/vnfs.go @@ -0,0 +1,35 @@ +package warewulfd_responses + +import ( + "github.com/hpcng/warewulf/internal/pkg/config" + "github.com/hpcng/warewulf/internal/pkg/vnfs" + "log" + "net/http" +) + +func VnfsSend(w http.ResponseWriter, req *http.Request) { + config := config.New() + + node, err := getSanity(req) + if err != nil { + w.WriteHeader(404) + log.Panicln(err) + return + } + + if node.Vnfs != "" { + v := vnfs.New(node.Vnfs) + + err := sendFile(w, config.VnfsImage(v.NameClean()), node.Fqdn) + if err != nil { + log.Printf("ERROR: %s\n", err) + } else { + log.Printf("SEND: %15s: %s\n", node.Fqdn, config.VnfsImage(v.NameClean())) + } + } else { + w.WriteHeader(503) + log.Printf("ERROR: No VNFS set for node %s\n", node.Fqdn) + } + + return +}