Merge pull request #423 from kcdodd/refac/provision

Refactor of server, logging, building of images, template updates
This commit is contained in:
Gregory M. Kurtzer
2022-05-25 14:49:50 -07:00
committed by GitHub
29 changed files with 1243 additions and 798 deletions

View File

@@ -154,7 +154,7 @@ test-cover: ## Run test coverage and generate html report
rm *.coverprofile
go tool cover -html=coverage/cover.out -o=coverage/cover.html
debian: all
debian: all
files: all
install -d -m 0755 $(DESTDIR)$(BINDIR)
@@ -265,4 +265,3 @@ clean:
install: files install_wwclient
debinstall: files debfiles

View File

@@ -1,31 +0,0 @@
#!ipxe
echo
echo ================================================================================
echo Warewulf v4 now booting: {{.Fqdn}}
echo
echo Container: {{.ContainerName}}
{{if .KernelOverride }}
echo Kernel: {{.KernelOverride}}
{{else}}
echo Kernel: {{.ContainerName}} (container default)
{{end}}
echo KernelArgs: {{.KernelArgs}}
echo
set base http://{{.Ipaddr}}:{{.Port}}
kernel --name kernel ${base}/kernel/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid} || goto reboot
imgextract --name container ${base}/container/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid} || goto reboot
imgextract --name system ${base}/overlay-system/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid} || goto reboot
imgextract --name runtime ${base}/overlay-runtime/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid} || goto reboot
imgextract --name runtime ${base}/overlay-runtime/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid} || goto reboot
boot kernel initrd=container initrd=system initrd=runtime wwid={{.Hwaddr}} {{.KernelArgs}} || goto reboot
:reboot
echo
echo There was an error, rebooting in 15s...
echo
sleep 15
reboot

View File

@@ -2,7 +2,7 @@
echo
echo ================================================================================
echo Warewulf v4 now booting: {{.Fqdn}}
echo Warewulf v4 now booting: {{.Fqdn}} ({{.Hwaddr}})
echo
echo Container: {{.ContainerName}}
{{if .KernelOverride }}
@@ -13,14 +13,56 @@ echo Kernel: {{.ContainerName}} (container default)
echo KernelArgs: {{.KernelArgs}}
echo
set base http://{{.Ipaddr}}:{{.Port}}
set uri_base http://{{.Ipaddr}}:{{.Port}}/provision/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid}
echo Downloading node provisions: ${uri_base}
kernel --name kernel ${base}/kernel/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid} || goto reboot
initrd --name container ${base}/container/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid} || goto reboot
initrd --name system ${base}/overlay-system/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid} || goto reboot
initrd --name runtime ${base}/overlay-runtime/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid} || goto reboot
echo Kernel image:
kernel --name kernel ${uri_base}&stage=kernel || goto reboot
boot kernel initrd=container initrd=system initrd=runtime wwid={{.Hwaddr}} {{.KernelArgs}} || goto reboot
# try extracting compressed images first
# NOTE: system overlay tends to be the smallest, so failure here is the cheapest
echo System image:
imgextract --name system ${uri_base}&stage=system&compress=gz || goto nocompress
echo Container image:
imgextract --name container ${uri_base}&stage=container&compress=gz || goto reboot
{{if ne .KernelOverride "" -}}
echo Kernel modules image:
imgextract --name kmods ${uri_base}&stage=kmods&compress=gz || goto reboot
{{- end}}
goto imoktogo
:nocompress
echo Extraction failed, attempting un-compressed version of images
echo System image:
initrd --name system ${uri_base}&stage=system || goto reboot
echo Container image:
initrd --name container ${uri_base}&stage=container || goto reboot
{{if ne .KernelOverride "" -}}
echo Kernel modules image:
initrd --name kmods ${uri_base}&stage=kmods || goto reboot
{{- end}}
:imoktogo
echo Booting in 3s...
{{if ne .KernelOverride "" -}}
echo boot kernel initrd=container initrd=kmods initrd=system wwid={{.Hwaddr}} {{.KernelArgs}}
sleep 3
boot kernel initrd=container initrd=kmods initrd=system wwid={{.Hwaddr}} {{.KernelArgs}} || goto reboot
{{- else -}}
echo boot kernel initrd=container initrd=system wwid={{.Hwaddr}} {{.KernelArgs}}
sleep 3
boot kernel initrd=container initrd=system wwid={{.Hwaddr}} {{.KernelArgs}} || goto reboot
{{- end}}
:reboot
echo

View File

@@ -178,7 +178,7 @@ func updateSystem(ipaddr string, port int, wwid string, tag string, localUUID uu
counter := 0
for {
var err error
getString := fmt.Sprintf("http://%s:%d/overlay-runtime/%s?assetkey=%s&uuid=%s", ipaddr, port, wwid, tag, localUUID)
getString := fmt.Sprintf("http://%s:%d/provision/%s?assetkey=%s&uuid=%s&stage=runtime&compress=gz", ipaddr, port, wwid, tag, localUUID)
wwlog.Printf(wwlog.DEBUG, "Making request: %s\n", getString)
resp, err = Webclient.Get(getString)
if err == nil {

View File

@@ -75,11 +75,11 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
name = args[1]
} else {
name = path.Base(uri)
fmt.Printf("Setting VNFS name: %s\n", name)
wwlog.Info("Setting VNFS name: %s", name)
}
if !container.ValidName(name) {
wwlog.Printf(wwlog.ERROR, "VNFS name contains illegal characters: %s\n", name)
wwlog.Error("VNFS name contains illegal characters: %s", name)
os.Exit(1)
}
@@ -87,75 +87,75 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
if util.IsDir(fullPath) {
if SetForce {
fmt.Printf("Overwriting existing VNFS\n")
wwlog.Info("Overwriting existing VNFS")
err := os.RemoveAll(fullPath)
if err != nil {
wwlog.Printf(wwlog.ERROR, "%s\n", err)
wwlog.ErrorExc(err, "")
os.Exit(1)
}
} else if SetUpdate {
fmt.Printf("Updating existing VNFS\n")
wwlog.Info("Updating existing VNFS")
} else {
wwlog.Printf(wwlog.ERROR, "VNFS Name exists, specify --force, --update, or choose a different name: %s\n", name)
wwlog.Error("VNFS Name exists, specify --force, --update, or choose a different name: %s", name)
os.Exit(1)
}
} else if strings.HasPrefix(uri, "docker://") || strings.HasPrefix(uri, "docker-daemon://") ||
strings.HasPrefix(uri, "file://") || util.IsFile(uri) {
sCtx, err := getSystemContext()
if err != nil {
wwlog.Printf(wwlog.ERROR, "%s\n", err)
wwlog.ErrorExc(err, "")
}
err = container.ImportDocker(uri, name, sCtx)
if err != nil {
wwlog.Printf(wwlog.ERROR, "Could not import image: %s\n", err)
wwlog.Error("Could not import image: %s", err)
_ = container.DeleteSource(name)
os.Exit(1)
}
} else if util.IsDir(uri) {
err := container.ImportDirectory(uri, name)
if err != nil {
wwlog.Printf(wwlog.ERROR, "Could not import image: %s\n", err)
wwlog.Error("Could not import image: %s", err)
_ = container.DeleteSource(name)
os.Exit(1)
}
} else {
wwlog.Printf(wwlog.ERROR, "Invalid dir or uri: %s\n", uri)
wwlog.Error("Invalid dir or uri: %s", uri)
os.Exit(1)
}
fmt.Printf("Updating the container's /etc/resolv.conf\n")
wwlog.Info("Updating the container's /etc/resolv.conf")
err := util.CopyFile("/etc/resolv.conf", path.Join(container.RootFsDir(name), "/etc/resolv.conf"))
if err != nil {
wwlog.Printf(wwlog.WARN, "Could not copy /etc/resolv.conf into container: %s\n", err)
wwlog.Warn("Could not copy /etc/resolv.conf into container: %s", err)
}
err = container.SyncUids(name, !SyncUser)
if err != nil && !SyncUser {
wwlog.Printf(wwlog.ERROR, "Error in user sync, fix error and run 'syncuser' manually: %s\n", err)
wwlog.Error("Error in user sync, fix error and run 'syncuser' manually: %s", err)
os.Exit(1)
}
fmt.Printf("Building container: %s\n", name)
wwlog.Info("Building container: %s", name)
err = container.Build(name, true)
if err != nil {
wwlog.Printf(wwlog.ERROR, "Could not build container %s: %s\n", name, err)
wwlog.Error("Could not build container %s: %s", name, err)
os.Exit(1)
}
if SetDefault {
nodeDB, err := node.New()
if err != nil {
wwlog.Printf(wwlog.ERROR, "Could not open node configuration: %s\n", err)
wwlog.Error("Could not open node configuration: %s", err)
os.Exit(1)
}
//TODO: Don't loop through profiles, instead have a nodeDB function that goes directly to the map
profiles, _ := nodeDB.FindAllProfiles()
for _, profile := range profiles {
wwlog.Printf(wwlog.DEBUG, "Looking for profile default: %s\n", profile.Id.Get())
wwlog.Printf(wwlog.DEBUG, "Looking for profile default: %s", profile.Id.Get())
if profile.Id.Get() == "default" {
wwlog.Printf(wwlog.DEBUG, "Found profile default, setting container name to: %s\n", name)
wwlog.Printf(wwlog.DEBUG, "Found profile default, setting container name to: %s", name)
profile.ContainerName.Set(name)
err := nodeDB.ProfileUpdate(profile)
if err != nil {
@@ -168,7 +168,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
return errors.Wrap(err, "failed to persist nodedb")
}
fmt.Printf("Set default profile to container: %s\n", name)
wwlog.Info("Set default profile to container: %s", name)
err = warewulfd.DaemonReload()
if err != nil {
return errors.Wrap(err, "failed to reload warewulf daemon")

View File

@@ -1,7 +1,6 @@
package add
import (
"fmt"
"net"
"github.com/hpcng/warewulf/internal/pkg/node"
@@ -135,7 +134,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
}
// just check if address is a valid ipv6 CIDR address
if _, _, err := net.ParseCIDR(SetIpaddr6); err != nil {
return errors.New(fmt.Sprintf("%s is not a valid ipv6 address in CIDR notation\n", SetIpaddr6))
return errors.Errorf("%s is not a valid ipv6 address in CIDR notation\n", SetIpaddr6)
}
}

View File

@@ -58,11 +58,11 @@ func GetRootCommand() *cobra.Command {
func rootPersistentPreRunE(cmd *cobra.Command, args []string) error {
if DebugFlag {
wwlog.SetLevel(wwlog.DEBUG)
wwlog.SetLogLevel(wwlog.DEBUG)
} else if verboseArg {
wwlog.SetLevel(wwlog.VERBOSE)
wwlog.SetLogLevel(wwlog.VERBOSE)
} else {
wwlog.SetLevel(wwlog.INFO)
wwlog.SetLogLevel(wwlog.INFO)
}
return nil
}

View File

@@ -1,10 +1,8 @@
package container
import (
"fmt"
"os"
"os/exec"
"path"
"strings"
"github.com/pkg/errors"
@@ -18,50 +16,42 @@ func Build(name string, buildForce bool) error {
imagePath := ImageFile(name)
if !ValidSource(name) {
return errors.New("Container does not exist")
return errors.Errorf("Container does not exist: %s", name)
}
if !buildForce {
wwlog.Printf(wwlog.DEBUG, "Checking if there have been any updates to the VNFS directory\n")
wwlog.Debug("Checking if there have been any updates to the VNFS directory")
if util.PathIsNewer(rootfsPath, imagePath) {
wwlog.Printf(wwlog.INFO, "Skipping (VNFS is current)\n")
wwlog.Info("Skipping (VNFS is current)")
return nil
}
}
wwlog.Printf(wwlog.DEBUG, "Making parent directory for: %s\n", name)
err := os.MkdirAll(path.Dir(imagePath), 0755)
if err != nil {
return errors.New("Failed creating directory")
excludes_file := path.Join(rootfsPath, "./etc/warewulf/excludes")
ignore := []string{}
if util.IsFile(excludes_file) {
ignore, err := util.ReadFile(excludes_file)
if err != nil {
return errors.Wrapf(err, "Failed creating directory: %s", imagePath)
}
for i, pattern := range ignore {
if ( strings.HasPrefix(pattern, "/") ) {
ignore[i] = pattern[1:]
}
}
}
wwlog.Printf(wwlog.DEBUG, "Making parent directory for: %s\n", rootfsPath)
err = os.MkdirAll(path.Dir(rootfsPath), 0755)
if err != nil {
return errors.New("Failed creating directory")
}
err := util.BuildFsImage(
"VNFS container " + name,
rootfsPath,
imagePath,
[]string{"*"},
ignore,
// ignore cross-device files
true,
"newc")
compressor, err := exec.LookPath("pigz")
if err != nil {
wwlog.Printf(wwlog.VERBOSE, "Could not locate PIGZ, using GZIP\n")
compressor = "gzip"
} else {
wwlog.Printf(wwlog.VERBOSE, "Using PIGZ to compress the container: %s\n", compressor)
}
var cmd string
_, err = os.Stat(path.Join(rootfsPath, "./etc/warewulf/excludes"))
if os.IsNotExist(err) {
wwlog.Printf(wwlog.DEBUG, "Building VNFS image: '%s' -> '%s'\n", rootfsPath, imagePath)
cmd = fmt.Sprintf("cd %s; find . -xdev -xautofs | cpio --quiet -o -H newc | %s -c > \"%s\"", rootfsPath, compressor, imagePath)
} else {
wwlog.Printf(wwlog.DEBUG, "Building VNFS image with excludes: '%s' -> '%s'\n", rootfsPath, imagePath)
cmd = fmt.Sprintf("cd %s; find . -xdev -xautofs | grep -v -f ./etc/warewulf/excludes | cpio --quiet -o -H newc | %s -c > \"%s\"", rootfsPath, compressor, imagePath)
}
wwlog.Printf(wwlog.DEBUG, "RUNNING: %s\n", cmd)
err = exec.Command("/bin/sh", "-c", cmd).Run()
if err != nil {
return errors.New("Failed building VNFS")
}
return nil
return err
}

View File

@@ -23,5 +23,5 @@ func ImageParentDir() string {
}
func ImageFile(name string) string {
return path.Join(ImageParentDir(), name+".img.gz")
return path.Join(ImageParentDir(), name+".img")
}

View File

@@ -13,9 +13,13 @@ var (
kernelNames = []string{
`vmlinux`,
`vmlinuz`,
`vmlinuz.gz`,
}
modulePath = "/lib/modules/"
`vmlinux-*`,
`vmlinuz-*`,
`vmlinuz.gz` }
kernelDirs = []string{
`/lib/modules/*/`,
`/boot/` }
)
func KernelFind(container string) string {
@@ -25,21 +29,28 @@ func KernelFind(container string) string {
return ""
}
for _, kname := range kernelNames {
wwlog.Printf(wwlog.DEBUG, "Checking for kernel name within module path: %s\n", kname)
kernelPaths, err := filepath.Glob(path.Join(container_path, modulePath, "/*/", kname))
if err != nil {
return ""
}
for _, kdir := range kernelDirs {
wwlog.Printf(wwlog.DEBUG, "Checking kernel directory: %s\n", kdir)
for _, kname := range kernelNames {
wwlog.Printf(wwlog.DEBUG, "Checking for kernel name: %s\n", kname)
kernelPaths, err := filepath.Glob(path.Join(container_path, kdir, kname))
if err != nil {
return ""
}
sort.Slice(kernelPaths, func(i, j int) bool {
return kernelPaths[i] > kernelPaths[j]
})
if len(kernelPaths) == 0 {
continue
}
for _, kernelPath := range kernelPaths {
wwlog.Printf(wwlog.DEBUG, "Checking for kernel path: %s\n", kernelPath)
if util.IsFile(kernelPath) {
return kernelPath
sort.Slice(kernelPaths, func(i, j int) bool {
return kernelPaths[i] > kernelPaths[j]
})
for _, kernelPath := range kernelPaths {
wwlog.Printf(wwlog.DEBUG, "Checking for kernel path: %s\n", kernelPath)
if util.IsFile(kernelPath) {
return kernelPath
}
}
}
}

View File

@@ -6,7 +6,6 @@ import (
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
@@ -35,12 +34,12 @@ func KernelImageTopDir() string {
func KernelImage(kernelName string) string {
if kernelName == "" {
wwlog.Printf(wwlog.ERROR, "Kernel Name is not defined\n")
wwlog.Error("Kernel Name is not defined")
return ""
}
if !util.ValidString(kernelName, "^[a-zA-Z0-9-._]+$") {
wwlog.Printf(wwlog.ERROR, "Runtime overlay name contains illegal characters: %s\n", kernelName)
wwlog.Error("Runtime overlay name contains illegal characters: %s", kernelName)
return ""
}
@@ -49,7 +48,7 @@ func KernelImage(kernelName string) string {
func GetKernelVersion(kernelName string) string {
if kernelName == "" {
wwlog.Printf(wwlog.ERROR, "Kernel Name is not defined\n")
wwlog.Error("Kernel Name is not defined")
return ""
}
kernelVersion, err := ioutil.ReadFile(KernelVersionFile(kernelName))
@@ -61,12 +60,12 @@ func GetKernelVersion(kernelName string) string {
func KmodsImage(kernelName string) string {
if kernelName == "" {
wwlog.Printf(wwlog.ERROR, "Kernel Name is not defined\n")
wwlog.Error("Kernel Name is not defined")
return ""
}
if !util.ValidString(kernelName, "^[a-zA-Z0-9-._]+$") {
wwlog.Printf(wwlog.ERROR, "Runtime overlay name contains illegal characters: %s\n", kernelName)
wwlog.Error("Runtime overlay name contains illegal characters: %s", kernelName)
return ""
}
@@ -75,12 +74,12 @@ func KmodsImage(kernelName string) string {
func KernelVersionFile(kernelName string) string {
if kernelName == "" {
wwlog.Printf(wwlog.ERROR, "Kernel Name is not defined\n")
wwlog.Error("Kernel Name is not defined")
return ""
}
if !util.ValidString(kernelName, "^[a-zA-Z0-9-._]+$") {
wwlog.Printf(wwlog.ERROR, "Runtime overlay name contains illegal characters: %s\n", kernelName)
wwlog.Error("Runtime overlay name contains illegal characters: %s", kernelName)
return ""
}
@@ -95,7 +94,7 @@ func ListKernels() ([]string, error) {
return ret, errors.New("Could not create Kernel parent directory: " + KernelImageTopDir())
}
wwlog.Printf(wwlog.DEBUG, "Searching for Kernel image directories: %s\n", KernelImageTopDir())
wwlog.Debug("Searching for Kernel image directories: %s", KernelImageTopDir())
kernels, err := ioutil.ReadDir(KernelImageTopDir())
if err != nil {
@@ -103,7 +102,7 @@ func ListKernels() ([]string, error) {
}
for _, kernel := range kernels {
wwlog.Printf(wwlog.VERBOSE, "Found Kernel: %s\n", kernel.Name())
wwlog.Verbose("Found Kernel: %s", kernel.Name())
ret = append(ret, kernel.Name())
@@ -113,8 +112,8 @@ func ListKernels() ([]string, error) {
}
func Build(kernelVersion, kernelName, root string) (string, error) {
kernelDrivers := path.Join(root, "/lib/modules/", kernelVersion)
kernelDriversRelative := path.Join("/lib/modules/", kernelVersion)
kernelDrivers := path.Join(root, kernelDriversRelative)
kernelDestination := KernelImage(kernelName)
driversDestination := KmodsImage(kernelName)
versionDestination := KernelVersionFile(kernelName)
@@ -138,7 +137,7 @@ func Build(kernelVersion, kernelName, root string) (string, error) {
for _, searchPath := range kernelSearchPaths {
testPath := fmt.Sprintf(path.Join(root, searchPath), kernelVersion)
wwlog.Printf(wwlog.VERBOSE, "Looking for kernel at: %s\n", testPath)
wwlog.Verbose("Looking for kernel at: %s", testPath)
if util.IsFile(testPath) {
kernelSource = testPath
break
@@ -146,17 +145,17 @@ func Build(kernelVersion, kernelName, root string) (string, error) {
}
if kernelSource == "" {
wwlog.Printf(wwlog.ERROR, "Could not locate kernel image\n")
wwlog.Error("Could not locate kernel image")
return "", errors.New("could not locate kernel image")
} else {
wwlog.Printf(wwlog.INFO, "Found kernel at: %s\n", kernelSource)
wwlog.Info("Found kernel at: %s", kernelSource)
}
if !util.IsDir(kernelDrivers) {
return "", errors.New("Could not locate kernel drivers")
}
wwlog.Printf(wwlog.VERBOSE, "Setting up Kernel\n")
wwlog.Verbose("Setting up Kernel")
if _, err := os.Stat(kernelSource); err == nil {
kernel, err := os.Open(kernelSource)
if err != nil {
@@ -189,26 +188,30 @@ func Build(kernelVersion, kernelName, root string) (string, error) {
}
wwlog.Printf(wwlog.VERBOSE, "Building Kernel driver image\n")
if _, err := os.Stat(kernelDrivers); err == nil {
compressor, err := exec.LookPath("pigz")
if err != nil {
wwlog.Printf(wwlog.VERBOSE, "Could not locate PIGZ, using GZIP\n")
compressor = "gzip"
} else {
wwlog.Printf(wwlog.VERBOSE, "Using PIGZ to compress the container: %s\n", compressor)
}
name := kernelName + " drivers"
wwlog.Verbose("Creating image for %s: %s", name, root)
cmd := fmt.Sprintf("cd %s; find .%s ./lib/firmware | cpio --quiet -o -L -H newc | %s -c > \"%s\"", root, kernelDriversRelative, compressor, driversDestination)
err = util.BuildFsImage(
name,
root,
driversDestination,
[]string{
"." + kernelDriversRelative,
"./lib/firmware" },
[]string{},
// ignore cross-device files
true,
"newc",
// dereference symbolic links
"-L")
wwlog.Printf(wwlog.DEBUG, "RUNNING: %s\n", cmd)
err = exec.Command("/bin/sh", "-c", cmd).Run()
if err != nil {
return "", err
}
}
wwlog.Printf(wwlog.VERBOSE, "Creating version file\n")
wwlog.Verbose("Creating version file")
file, err := os.Create(versionDestination)
if err != nil {
return "", errors.Wrap(err, "Failed to create version file")
@@ -228,26 +231,26 @@ func Build(kernelVersion, kernelName, root string) (string, error) {
func DeleteKernel(name string) error {
fullPath := path.Join(KernelImageTopDir(), name)
wwlog.Printf(wwlog.VERBOSE, "Removing path: %s\n", fullPath)
wwlog.Verbose("Removing path: %s", fullPath)
return os.RemoveAll(fullPath)
}
func FindKernelVersion(root string) (string, error) {
for _, searchPath := range kernelSearchPaths {
testPattern := fmt.Sprintf(path.Join(root, searchPath), `*`)
wwlog.Printf(wwlog.VERBOSE, "Looking for kernel version with pattern at: %s\n", testPattern)
wwlog.Verbose("Looking for kernel version with pattern at: %s", testPattern)
potentialKernel, _ := filepath.Glob(testPattern)
if len(potentialKernel) == 0 {
continue
}
for _, foundKernel := range potentialKernel {
wwlog.Printf(wwlog.VERBOSE, "Parsing out kernel version for %s\n", foundKernel)
wwlog.Verbose("Parsing out kernel version for %s", foundKernel)
re := regexp.MustCompile(fmt.Sprintf(path.Join(root, searchPath), `([\w\d-\.]*)`))
version := re.FindAllStringSubmatch(foundKernel, -1)
if version == nil {
return "", fmt.Errorf("could not parse kernel version")
}
wwlog.Printf(wwlog.VERBOSE, "found kernel version %s\n", version)
wwlog.Verbose("found kernel version %s", version)
return version[0][1], nil
}

View File

@@ -55,6 +55,14 @@ func templateFileBlock(inc string, abortStr string) (string, error) {
}
cont += line + "\n"
}
// NOTE: the text originally contains N-1 newlines for N lines, but the above
// loop will always add one at the end
// Avoids adding a blank line that was not present in the original file
// by adding 'abort' string to the end of the included block (without a newline)
// instead of manually in the template
cont += abortStr
return cont, nil
}

View File

@@ -8,7 +8,6 @@ import (
"io/ioutil"
"net"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
@@ -51,16 +50,16 @@ func BuildAllOverlays(nodes []node.NodeInfo) error {
for _, n := range nodes {
sysOverlays := n.SystemOverlay.GetSlice()
wwlog.Printf(wwlog.INFO, "Building system overlays for %s: [%s]\n", n.Id.Get(), strings.Join(sysOverlays, ", "))
wwlog.Info("Building system overlays for %s: [%s]", n.Id.Get(), strings.Join(sysOverlays, ", "))
err := BuildOverlay(n, sysOverlays)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("could not build system overlays %v for nide %s\n", sysOverlays, n.Id.Get()))
return errors.Wrapf(err, "could not build system overlays %v for nide %s", sysOverlays, n.Id.Get())
}
runOverlays := n.RuntimeOverlay.GetSlice()
wwlog.Printf(wwlog.INFO, "Building runtime overlays for %s: [%s]\n", n.Id.Get(), strings.Join(runOverlays, ", "))
wwlog.Info("Building runtime overlays for %s: [%s]", n.Id.Get(), strings.Join(runOverlays, ", "))
err = BuildOverlay(n, runOverlays)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("could not build runtime overlays %v for nide %s\n", runOverlays, n.Id.Get()))
return errors.Wrapf(err, "could not build runtime overlays %v for nide %s", runOverlays, n.Id.Get())
}
}
@@ -72,7 +71,7 @@ func BuildAllOverlays(nodes []node.NodeInfo) error {
func BuildSpecificOverlays(nodes []node.NodeInfo, overlayName string) error {
for _, n := range nodes {
wwlog.Printf(wwlog.INFO, "Building overlay for %s: %s\n", n.Id.Get(), overlayName)
wwlog.Info("Building overlay for %s: %s", n.Id.Get(), overlayName)
err := BuildOverlay(n, []string{overlayName})
if err != nil {
return errors.Wrap(err, "could not build overlay "+n.Id.Get()+"/"+overlayName+".img")
@@ -91,7 +90,7 @@ func BuildHostOverlay() error {
host.Ipmi = new(node.IpmiEntry)
var idEntry node.Entry
hostname, _ := os.Hostname()
wwlog.Printf(wwlog.INFO, "Building overlay for %s: host\n", hostname)
wwlog.Info("Building overlay for %s: host", hostname)
idEntry.Set(hostname)
host.Id = idEntry
hostdir := OverlaySourceDir("host")
@@ -100,7 +99,7 @@ func BuildHostOverlay() error {
return errors.Wrap(err, "could not build host overlay ")
}
if !(stats.Mode() == os.FileMode(0750|os.ModeDir) || stats.Mode() == os.FileMode(0700|os.ModeDir)) {
wwlog.Printf(wwlog.SECWARN, "Permissions of host overlay dir %s are %s (750 is considered as secure)\n", hostdir, stats.Mode())
wwlog.SecWarn("Permissions of host overlay dir %s are %s (750 is considered as secure)", hostdir, stats.Mode())
}
return BuildOverlayIndir(host, []string{"host"}, "/")
}
@@ -119,7 +118,7 @@ func FindOverlays() ([]string, error) {
}
for _, file := range files {
wwlog.Printf(wwlog.DEBUG, "Evaluating overlay source: %s\n", file.Name())
wwlog.Debug("Evaluating overlay source: %s", file.Name())
isdotfile := dotfilecheck.MatchString(file.Name())
if (file.IsDir()) && !(isdotfile) {
@@ -150,47 +149,43 @@ Build the given overlays for a node and create a Image for them
*/
func BuildOverlay(nodeInfo node.NodeInfo, overlayNames []string) error {
// create the dir where the overlay images will reside
name := fmt.Sprintf("overlay %s/%v", nodeInfo.Id.Get(), overlayNames)
overlayImage := OverlayImage(nodeInfo.Id.Get(), overlayNames)
overlayImageDir := path.Dir(overlayImage)
err := os.MkdirAll(overlayImageDir, 0755)
if err == nil {
wwlog.Printf(wwlog.DEBUG, "Created parent directory for Overlay Images: %s\n", overlayImageDir)
} else {
return errors.Wrap(err, "could not create overlay image directory")
}
outputDir, err := ioutil.TempDir(os.TempDir(), ".wwctl-overlay-")
if err == nil {
wwlog.Printf(wwlog.DEBUG, "Creating temporary directory for overlay files: %s\n", outputDir)
} else {
return errors.Wrap(err, "could not create overlay temporary directory")
}
err = BuildOverlayIndir(nodeInfo, overlayNames, outputDir)
if err != nil {
wwlog.Printf(wwlog.WARN, "Got following error when building overlay: %s\n", err)
return errors.Wrapf(err, "Failed to create directory for %s: %s", name, overlayImageDir)
}
wwlog.Printf(wwlog.DEBUG, "Finished generating overlay working directory for: %s/%v\n", nodeInfo.Id.Get(), overlayNames)
compressor, err := exec.LookPath("pigz")
wwlog.Debug("Created directory for %s: %s", name, overlayImageDir)
buildDir, err := ioutil.TempDir(os.TempDir(), ".wwctl-overlay-")
if err != nil {
wwlog.Printf(wwlog.DEBUG, "Could not locate PIGZ, using GZIP\n")
compressor = "gzip"
} else {
wwlog.Printf(wwlog.DEBUG, "Using PIGZ to compress the overlay: %s\n", compressor)
return errors.Wrapf(err, "Failed to create temporary directory for %s", name )
}
defer os.RemoveAll(buildDir)
cmd := fmt.Sprintf("cd \"%s\"; find . | cpio --quiet -o -H newc | %s -c > \"%s\"", outputDir, compressor, overlayImage)
wwlog.Debug("Created temporary directory for %s: %s", name, buildDir)
wwlog.Printf(wwlog.DEBUG, "RUNNING: %s\n", cmd)
err = exec.Command("/bin/sh", "-c", cmd).Run()
err = BuildOverlayIndir(nodeInfo, overlayNames, buildDir)
if err != nil {
return errors.Wrap(err, "could not generate compressed runtime image overlay")
return errors.Wrapf(err, "Failed to generate files for %s", name)
}
wwlog.Printf(wwlog.VERBOSE, "Completed building overlay image: %s\n", overlayImage)
wwlog.Printf(wwlog.DEBUG, "Removing temporary directory: %s\n", outputDir)
os.RemoveAll(outputDir)
return nil
wwlog.Debug("Generated files for %s", name)
err = util.BuildFsImage(
name,
buildDir,
overlayImage,
[]string{"*"},
[]string{},
// ignore cross-device files
true,
"newc")
return err
}
/*
@@ -199,38 +194,36 @@ exists it will be created.
*/
func BuildOverlayIndir(nodeInfo node.NodeInfo, overlayNames []string, outputDir string) error {
if len(overlayNames) == 0 {
return errors.New("At least one valid overlay is needed to build for a node\n")
return errors.New("At least one valid overlay is needed to build for a node")
}
if !util.IsDir(outputDir) {
return errors.New(fmt.Sprintf("output %s must a be a directory\n", outputDir))
return errors.Errorf("output must a be a directory: %s", outputDir)
}
controller, err := warewulfconf.New()
if err != nil {
wwlog.Printf(wwlog.ERROR, "%s\n", err)
wwlog.ErrorExc(err, "")
os.Exit(1)
}
nodeDB, err := node.New()
if err != nil {
wwlog.Printf(wwlog.ERROR, "%s\n", err)
wwlog.ErrorExc(err, "")
os.Exit(1)
}
allNodes, err := nodeDB.FindAllNodes()
if err != nil {
wwlog.Printf(wwlog.ERROR, "%s\n", err)
wwlog.ErrorExc(err, "")
os.Exit(1)
}
if !util.ValidString(strings.Join(overlayNames, ""), "^[a-zA-Z0-9-._:]+$") {
return errors.New(fmt.Sprintf("overlay names contains illegal characters: %v", overlayNames))
return errors.Errorf("overlay names contains illegal characters: %v", overlayNames)
}
wwlog.Printf(wwlog.VERBOSE, "Processing node/overlay: %s/%s\n", nodeInfo.Id.Get(), strings.Join(overlayNames, "-"))
wwlog.Verbose("Processing node/overlay: %s/%s", nodeInfo.Id.Get(), strings.Join(overlayNames, "-"))
var tstruct TemplateStruct
tstruct.Kernel = new(node.KernelConf)
tstruct.Ipmi = new(node.IpmiConf)
tstruct.Id = nodeInfo.Id.Get()
tstruct.Hostname = nodeInfo.Id.Get()
tstruct.Id = nodeInfo.Id.Get()
tstruct.Hostname = nodeInfo.Id.Get()
tstruct.ClusterName = nodeInfo.ClusterName.Get()
tstruct.Container = nodeInfo.ContainerName.Get()
tstruct.Kernel.Version = nodeInfo.Kernel.Override.Get()
@@ -302,28 +295,28 @@ func BuildOverlayIndir(nodeInfo node.NodeInfo, overlayNames []string, outputDir
tstruct.BuildTime = dt.Format("01-02-2006 15:04:05 MST")
tstruct.BuildTimeUnix = strconv.FormatInt(dt.Unix(), 10)
for _, overlayName := range overlayNames {
wwlog.Printf(wwlog.VERBOSE, "Building overlay %s for node %s in %s\n", overlayName, nodeInfo.Id.Get(), outputDir)
wwlog.Verbose("Building overlay %s for node %s in %s", overlayName, nodeInfo.Id.Get(), outputDir)
overlaySourceDir := OverlaySourceDir(overlayName)
wwlog.Printf(wwlog.DEBUG, "Starting to build overlay %s\nChanging directory to OverlayDir: %s\n", overlayName, overlaySourceDir)
wwlog.Debug("Starting to build overlay %s\nChanging directory to OverlayDir: %s", overlayName, overlaySourceDir)
err = os.Chdir(overlaySourceDir)
if err != nil {
return errors.Wrap(err, "could not change directory to overlay dir")
}
wwlog.Printf(wwlog.DEBUG, "Checking to see if overlay directory exists: %s\n", overlaySourceDir)
wwlog.Debug("Checking to see if overlay directory exists: %s", overlaySourceDir)
if !util.IsDir(overlaySourceDir) {
return errors.New("overlay does not exist: " + overlayName)
}
wwlog.Printf(wwlog.VERBOSE, "Walking the overlay structure: %s\n", overlaySourceDir)
wwlog.Verbose("Walking the overlay structure: %s", overlaySourceDir)
err = filepath.Walk(".", func(location string, info os.FileInfo, err error) error {
if err != nil {
return errors.Wrap(err, "error for "+location)
}
wwlog.Printf(wwlog.DEBUG, "Found overlay file: %s\n", location)
wwlog.Debug("Found overlay file: %s", location)
if info.IsDir() {
wwlog.Printf(wwlog.DEBUG, "Found directory: %s\n", location)
wwlog.Debug("Found directory: %s", location)
err = os.MkdirAll(path.Join(outputDir, location), info.Mode())
if err != nil {
@@ -334,11 +327,11 @@ func BuildOverlayIndir(nodeInfo node.NodeInfo, overlayNames []string, outputDir
return errors.Wrap(err, "failed setting permissions on overlay directory")
}
wwlog.Printf(wwlog.DEBUG, "Created directory in overlay: %s\n", location)
wwlog.Debug("Created directory in overlay: %s", location)
} else if filepath.Ext(location) == ".ww" {
tstruct.BuildSource = path.Join(overlaySourceDir, location)
wwlog.Printf(wwlog.VERBOSE, "Evaluating overlay template file: %s\n", location)
wwlog.Verbose("Evaluating overlay template file: %s", location)
destFile := strings.TrimSuffix(location, ".ww")
backupFile := true
writeFile := true
@@ -351,12 +344,12 @@ func BuildOverlayIndir(nodeInfo node.NodeInfo, overlayNames []string, outputDir
"dec": func(i int) int { return i - 1 },
"file": func(str string) string { return fmt.Sprintf("{{ /* file \"%s\" */ }}", str) },
"abort": func() string {
wwlog.Printf(wwlog.DEBUG, "abort file called in %s\n", location)
wwlog.Debug("abort file called in %s", location)
writeFile = false
return ""
},
"nobackup": func() string {
wwlog.Printf(wwlog.DEBUG, "not backup for %s\n", location)
wwlog.Debug("not backup for %s", location)
backupFile = false
return ""
},
@@ -372,7 +365,6 @@ func BuildOverlayIndir(nodeInfo node.NodeInfo, overlayNames []string, outputDir
err = tmpl.Execute(&buffer, tstruct)
if err != nil {
return errors.Wrap(err, "could not execute template")
}
if writeFile {
destFileName := destFile
@@ -386,7 +378,7 @@ func BuildOverlayIndir(nodeInfo node.NodeInfo, overlayNames []string, outputDir
line := fileScanner.Text()
filenameFromTemplate := reg.FindAllStringSubmatch(line, -1)
if len(filenameFromTemplate) != 0 {
wwlog.Printf(wwlog.DEBUG, "Found multifile comment, new filename %s\n", filenameFromTemplate[0][1])
wwlog.Debug("Found multifile comment, new filename %s", filenameFromTemplate[0][1])
if foundFileComment {
err = carefulWriteBuffer(path.Join(outputDir, destFileName),
fileBuffer, backupFile, info.Mode())
@@ -414,26 +406,26 @@ func BuildOverlayIndir(nodeInfo node.NodeInfo, overlayNames []string, outputDir
return errors.Wrap(err, "failed setting permissions on template output file")
}
wwlog.Printf(wwlog.DEBUG, "Wrote template file into overlay: %s\n", destFile)
wwlog.Debug("Wrote template file into overlay: %s", destFile)
// } else if b, _ := regexp.MatchString(`\.ww[a-zA-Z0-9\-\._]*$`, location); b {
// wwlog.Printf(wwlog.DEBUG, "Ignoring WW template file: %s\n", location)
// wwlog.Debug("Ignoring WW template file: %s", location)
}
} else if info.Mode()&os.ModeSymlink == os.ModeSymlink {
wwlog.Printf(wwlog.DEBUG, "Found symlink %s\n", location)
wwlog.Debug("Found symlink %s", location)
destination, err := os.Readlink(location)
if err != nil {
wwlog.Printf(wwlog.ERROR, "%s\n", err)
wwlog.ErrorExc(err, "")
}
err = os.Symlink(destination, path.Join(outputDir, location))
if err != nil {
wwlog.Printf(wwlog.ERROR, "%s\n", err)
wwlog.ErrorExc(err, "")
}
} else {
err := util.CopyFile(location, path.Join(outputDir, location))
if err == nil {
wwlog.Printf(wwlog.DEBUG, "Copied file into overlay: %s\n", location)
wwlog.Debug("Copied file into overlay: %s", location)
} else {
return errors.Wrap(err, "could not copy file into overlay")
}
@@ -453,13 +445,12 @@ func BuildOverlayIndir(nodeInfo node.NodeInfo, overlayNames []string, outputDir
Writes buffer to the destination file. If wwbackup is set a wwbackup will be created.
*/
func carefulWriteBuffer(destFile string, buffer bytes.Buffer, backupFile bool, perm fs.FileMode) error {
wwlog.Printf(wwlog.DEBUG, "Trying to careful write file %s\n", destFile)
wwlog.Debug("Trying to careful write file (%d bytes): %s", buffer.Len(), destFile )
if backupFile {
// if !util.IsFile(path.Join(outputDir, destFile+".wwbackup")) && util.IsFile(path.Join(outputDir, destFile)) {
if !util.IsFile(destFile+".wwbackup") && util.IsFile(destFile) {
err := util.CopyFile(destFile, destFile+".wwbackup")
if err != nil {
wwlog.Printf(wwlog.ERROR, "%s\n", err)
return errors.Wrapf(err, "Failed to create backup: %s -> %s.wwbackup", destFile, destFile)
}
}

View File

@@ -9,8 +9,10 @@ import (
"net"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"strings"
"syscall"
"time"
@@ -18,6 +20,18 @@ import (
"github.com/pkg/errors"
)
func FirstError(errs ...error) (err error) {
for _, e := range errs {
if err == nil {
err = e
}else if e != nil {
wwlog.ErrorExc(e, "Unhandled error")
}
}
return
}
func DirModTime(path string) (time.Time, error) {
var lastTime time.Time
@@ -45,17 +59,17 @@ func DirModTime(path string) (time.Time, error) {
}
func PathIsNewer(source string, compare string) bool {
wwlog.Printf(wwlog.DEBUG, "Comparing times on paths: '%s' - '%s'\n", source, compare)
wwlog.Debug("Comparing times on paths: '%s' - '%s'", source, compare)
time1, err := DirModTime(source)
if err != nil {
wwlog.Printf(wwlog.DEBUG, "%s\n", err)
wwlog.DebugExc(err, "")
return false
}
time2, err := DirModTime(compare)
if err != nil {
wwlog.Printf(wwlog.DEBUG, "%s\n", err)
wwlog.DebugExc(err, "")
return false
}
@@ -100,7 +114,7 @@ func SliceInSlice(A []string, B []string) bool {
}
func IsDir(path string) bool {
wwlog.Printf(wwlog.DEBUG, "Checking if path exists as a directory: %s\n", path)
wwlog.Debug("Checking if path exists as a directory: %s", path)
if path == "" {
return false
@@ -112,7 +126,7 @@ func IsDir(path string) bool {
}
func IsFile(path string) bool {
wwlog.Printf(wwlog.DEBUG, "Checking if path exists as a file: %s\n", path)
wwlog.Debug("Checking if path exists as a file: %s", path)
if path == "" {
return false
@@ -147,18 +161,19 @@ func ValidString(pattern string, expr string) bool {
func ValidateOrDie(message string, pattern string, expr string) {
if ValidString(pattern, expr) {
wwlog.Printf(wwlog.ERROR, "%s does not validate: '%s'\n", message, pattern)
wwlog.Error("%s does not validate: '%s'", message, pattern)
os.Exit(1)
}
}
//******************************************************************************
func FindFiles(path string) []string {
var ret []string
wwlog.Printf(wwlog.DEBUG, "Changing directory to FindFiles path: %s\n", path)
wwlog.Debug("Changing directory to FindFiles path: %s", path)
err := os.Chdir(path)
if err != nil {
wwlog.Printf(wwlog.WARN, "Could not chdir() to: %s\n", path)
wwlog.Warn("Could not chdir() to: %s", path)
return ret
}
@@ -172,10 +187,10 @@ func FindFiles(path string) []string {
}
if IsDir(location) {
wwlog.Printf(wwlog.DEBUG, "FindFiles() found directory: %s\n", location)
wwlog.Debug("FindFiles() found directory: %s", location)
ret = append(ret, location+"/")
} else {
wwlog.Printf(wwlog.DEBUG, "FindFiles() found file: %s\n", location)
wwlog.Debug("FindFiles() found file: %s", location)
ret = append(ret, location)
}
@@ -188,8 +203,128 @@ func FindFiles(path string) []string {
return ret
}
//******************************************************************************
func FindFilterFiles(
path string,
include []string,
ignore []string,
ignore_xdev bool) (ofiles []string, err error) {
wwlog.Debug("Finding files: %s", path)
cwd, err := os.Getwd()
if err != nil {
return ofiles, err
}
defer func() {
err = FirstError(err, os.Chdir(cwd))
}()
err = os.Chdir(path)
if err != nil {
return ofiles, errors.Wrapf(err, "Failed to change path: %s", path)
}
files := []string{}
for _, pattern := range include {
_files, err := filepath.Glob(pattern)
if err != nil {
return ofiles, errors.Wrapf(err, "Failed to apply pattern: %s", pattern)
}
wwlog.Debug("Including pattern: %s -> %d matches", pattern, len(_files))
files = append(files, _files...)
}
for i, pattern := range(ignore) {
if strings.HasPrefix(pattern, "./") {
ignore[i] = pattern[2:]
}
wwlog.Debug("Ignore pattern (%d): %s", i, ignore[i])
}
if ignore_xdev {
wwlog.Debug("Ignoring cross-device (xdev) files")
}
path_stat, err := os.Stat(".")
if err != nil {
return ofiles, err
}
dev := path_stat.Sys().(*syscall.Stat_t).Dev
for _, ifile := range files {
stat, err := os.Stat(ifile)
if err != nil {
return ofiles, err
}
if stat.IsDir() {
// recursivly include from the matched directory
num_init := len(ofiles)
err = filepath.Walk(ifile, func(location string, info os.FileInfo, err error) error {
var file string
if err != nil {
return err
}
if location == "." {
return nil
}
if info.IsDir() {
file = location + "/"
} else {
file = location
}
if ignore_xdev && info.Sys().(*syscall.Stat_t).Dev != dev {
wwlog.Debug("Ignored (cross-device): %s", file)
return nil
}
for i, pattern := range(ignore) {
m, err := filepath.Match(pattern, location)
if err != nil {
return err
}
if m {
wwlog.Debug("Ignored (%d): %s", i, file)
return nil
}
}
ofiles = append(ofiles, file)
return nil
})
num_final := len(ofiles)
wwlog.Debug("Included: %s -> %d files", ifile, num_final-num_init)
if err != nil {
return ofiles, err
}
}else{
wwlog.Debug("Included: %s", ifile)
ofiles = append(ofiles, ifile)
}
}
return ofiles, err
}
//******************************************************************************
func ExecInteractive(command string, a ...string) error {
wwlog.Printf(wwlog.DEBUG, "ExecInteractive(%s, %s)\n", command, a)
wwlog.Debug("ExecInteractive(%s, %s)", command, a)
c := exec.Command(command, a...)
c.Stdin = os.Stdin
c.Stdout = os.Stdout
@@ -223,7 +358,7 @@ func SliceRemoveElement(array []string, remove string) []string {
if r != remove {
ret = append(ret, r)
} else {
wwlog.Printf(wwlog.DEBUG, "Removing slice from array: %s\n", remove)
wwlog.Debug("Removing slice from array: %s", remove)
}
}
@@ -267,7 +402,7 @@ func SystemdStart(systemdName string) error {
startCmd := fmt.Sprintf("systemctl restart %s", systemdName)
enableCmd := fmt.Sprintf("systemctl enable %s", systemdName)
wwlog.Printf(wwlog.DEBUG, "Setting up Systemd service: %s\n", systemdName)
wwlog.Debug("Setting up Systemd service: %s", systemdName)
err := ExecInteractive("/bin/sh", "-c", startCmd)
if err != nil {
return errors.Wrap(err, "failed to run start cmd")
@@ -293,7 +428,7 @@ func CopyUIDGID(source string, dest string) error {
UID = int(stat.Uid)
GID = int(stat.Gid)
}
wwlog.Printf(wwlog.DEBUG, "Chown %d:%d '%s'\n", UID, GID, dest)
wwlog.Debug("Chown %d:%d '%s'", UID, GID, dest)
err = os.Chown(dest, UID, GID)
return err
}
@@ -357,18 +492,165 @@ func IncrementIPv4(start string, inc uint) string {
Appending the lines to the given file
*/
func AppendLines(fileName string, lines []string) error {
wwlog.Printf(wwlog.VERBOSE, "appending %v lines to %s\n", len(lines), fileName)
wwlog.Verbose("appending %v lines to %s", len(lines), fileName)
file, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return errors.Errorf("Can't open file %s: %s", fileName, err)
return errors.Wrapf(err, "Can't open file: %s", fileName)
}
defer file.Close()
for _, line := range lines {
wwlog.Printf(wwlog.DEBUG, "Appending '%s' to %s\n", line, fileName)
wwlog.Debug("Appending '%s' to %s", line, fileName)
if _, err := file.WriteString(fmt.Sprintf("%s\n", line)); err != nil {
return errors.Errorf("Can't write to file %s: %s", fileName, err)
return errors.Wrapf(err, "Can't write to file: %s", fileName)
}
}
return nil
}
/*******************************************************************************
Create an archive using cpio
*/
func CpioCreate(
ifiles []string,
ofile string,
format string,
cpio_args ...string ) (err error) {
args := []string{
"--quiet",
"--create",
"-H", format,
"--file=" + ofile }
args = append(args, cpio_args...)
proc := exec.Command("cpio", args...)
stdin, err := proc.StdinPipe()
if err != nil {
return err
}
err_in := make(chan error, 1)
go func() {
defer stdin.Close()
_, err := io.WriteString(stdin, strings.Join(ifiles, "\n"))
err_in <- err
}()
out, err := proc.CombinedOutput()
if len(out) > 0 {
wwlog.Debug(string(out))
}
return FirstError(err, <- err_in)
}
/*******************************************************************************
Compress a file using gzip or pigz
*/
func FileGz(
file string ) (err error) {
file_gz := file + ".gz"
if IsFile(file_gz) {
err := os.Remove(file_gz)
if err != nil {
return errors.Wrapf(err, "Could not remove existing file: %s", file_gz)
}
}
compressor, err := exec.LookPath("pigz")
if err != nil {
wwlog.Verbose("Could not locate PIGZ")
compressor = "gzip"
}
wwlog.Verbose("Using gz compressor: %s", compressor)
proc := exec.Command(
compressor,
"--keep",
file )
out, err := proc.CombinedOutput()
if len(out) > 0 {
wwlog.Debug(string(out))
}
return err
}
/*******************************************************************************
Create an archive using cpio
*/
func BuildFsImage(
name string,
rootfsPath string,
imagePath string,
include []string,
ignore []string,
ignore_xdev bool,
format string,
cpio_args ...string ) (err error) {
err = os.MkdirAll(path.Dir(imagePath), 0755)
if err != nil {
return errors.Wrapf(err, "Failed to create image directory for %s: %s", name, imagePath)
}
wwlog.Debug("Created image directory for %s: %s", name, imagePath)
// TODO: why is this done if the container must already exist?
err = os.MkdirAll(path.Dir(rootfsPath), 0755)
if err != nil {
return errors.Wrapf(err, "Failed to create fs directory for %s: %s", name, rootfsPath)
}
wwlog.Debug("Created fs directory for %s: %s", name, rootfsPath)
cwd, err := os.Getwd()
if err != nil {
return err
}
defer func() {
err = FirstError(err, os.Chdir(cwd))
}()
err = os.Chdir(rootfsPath)
if err != nil {
return errors.Wrapf(err, "Failed chdir to fs directory for %s: %s", name, rootfsPath)
}
files, err := FindFilterFiles(
".",
include,
ignore,
ignore_xdev )
if err != nil {
return errors.Wrapf(err, "Failed discovering files for %s: %s", name, rootfsPath)
}
err = CpioCreate(
files,
imagePath,
format,
cpio_args...)
if err != nil {
return errors.Wrapf(err, "Failed creating image for %s: %s", name, imagePath)
}
wwlog.Info("Created image for %s: %s", name, imagePath)
err = FileGz(imagePath)
if err != nil {
return errors.Wrapf(err, "Failed to compress image for %s: %s", name, imagePath + ".gz")
}
wwlog.Info("Compressed image for %s: %s", name, imagePath + ".gz")
return nil
}

View File

@@ -1,46 +0,0 @@
package warewulfd
import (
"net/http"
"strings"
"github.com/hpcng/warewulf/internal/pkg/container"
)
func ContainerSend(w http.ResponseWriter, req *http.Request) {
rinfo, err := parseReq(req)
if err != nil {
w.WriteHeader(404)
daemonLogf("ERROR: %s\n", err)
return
}
node, err := GetNode(rinfo.hwaddr)
if err != nil {
w.WriteHeader(403)
daemonLogf("ERROR(%s): %s\n", rinfo.hwaddr, err)
return
}
if node.AssetKey.Defined() && node.AssetKey.Get() != rinfo.assetkey {
w.WriteHeader(404)
daemonLogf("ERROR: Incorrect asset key for node: %s\n", node.Id.Get())
updateStatus(node.Id.Get(), "CONTAINER", "BAD_ASSET", rinfo.ipaddr)
return
}
if node.ContainerName.Defined() {
containerImage := container.ImageFile(node.ContainerName.Get())
updateStatus(node.Id.Get(), "CONTAINER", node.ContainerName.Get()+".img", strings.Split(req.RemoteAddr, ":")[0])
err = sendFile(w, containerImage, node.Id.Get())
if err != nil {
daemonLogf("ERROR: %s\n", err)
w.WriteHeader(503)
}
} else {
w.WriteHeader(503)
daemonLogf("WARNING: No Container set for node %s\n", node.Id.Get())
}
}

View File

@@ -3,7 +3,6 @@ package warewulfd
import (
"fmt"
"io/ioutil"
"log"
"log/syslog"
"os"
"os/exec"
@@ -14,6 +13,7 @@ import (
"github.com/hpcng/warewulf/internal/pkg/util"
"github.com/hpcng/warewulf/internal/pkg/version"
"github.com/hpcng/warewulf/internal/pkg/warewulfconf"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"github.com/pkg/errors"
)
@@ -22,37 +22,51 @@ const (
WAREWULFD_LOGFILE = "/var/log/warewulfd.log"
)
var logwriter *syslog.Writer
var loginit bool
func daemonLogf(message string, a ...interface{}) {
func DaemonFormatter(logLevel int, rec *wwlog.LogRecord) string {
return "[" + rec.Time.Format(time.UnixDate) + "] " + wwlog.DefaultFormatter(logLevel, rec)
}
func DaemonInitLogging() error {
if loginit {
return nil
}
wwlog.SetLogFormatter(DaemonFormatter)
level_str, ok := os.LookupEnv("WAREWULFD_LOGLEVEL")
if ok {
level, err := strconv.Atoi(level_str)
if err == nil {
wwlog.SetLogLevel(level)
}
}else{
wwlog.SetLogLevel(wwlog.SERV)
}
conf, err := warewulfconf.New()
if err != nil {
fmt.Printf("ERROR: Could not read Warewulf configuration file: %s\n", err)
return
return errors.Wrap(err, "Could not read Warewulf configuration file")
}
if conf.Warewulf.Syslog {
if !loginit {
var err error
logwriter, err = syslog.New(syslog.LOG_NOTICE, "warewulfd")
if err != nil {
return
}
log.SetOutput(logwriter)
loginit = true
wwlog.Debug("Changingq log output to syslog")
log.SetFlags(0)
log.SetPrefix("")
logwriter, err := syslog.New(syslog.LOG_NOTICE, "warewulfd")
if err != nil {
return errors.Wrap(err, "Could not create syslog writer")
}
log.Printf(message, a...)
wwlog.SetLogFormatter(wwlog.DefaultFormatter)
wwlog.SetLogWriters(logwriter, logwriter)
} else {
prefix := fmt.Sprintf("[%s] ", time.Now().Format(time.UnixDate))
fmt.Printf(prefix+message, a...)
}
loginit = true
return nil
}
func DaemonStart() error {
@@ -69,6 +83,13 @@ func DaemonStart() error {
os.Setenv("WAREWULFD_BACKGROUND", "1")
logLevel := wwlog.GetLogLevel()
if logLevel == wwlog.INFO {
os.Setenv("WAREWULFD_LOGLEVEL", strconv.Itoa(wwlog.SERV))
}else{
os.Setenv("WAREWULFD_LOGLEVEL", strconv.Itoa(logLevel))
}
f, err := os.OpenFile(WAREWULFD_LOGFILE, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return err
@@ -91,7 +112,7 @@ func DaemonStart() error {
fmt.Fprintf(p, "%d", pid)
fmt.Printf("Started Warewulf (%s) server at PID: %d\n", version.GetVersion(), pid)
wwlog.Serv("Started Warewulf (%s) server at PID: %d", version.GetVersion(), pid)
}
@@ -117,7 +138,7 @@ func DaemonStatus() error {
if err != nil {
return errors.Wrap(err, "failed to send process SIGCONT")
} else {
fmt.Printf("Warewulf server is running at PID: %d\n", pid)
wwlog.Serv("Warewulf server is running at PID: %d", pid)
}
}
@@ -145,12 +166,19 @@ func DaemonReload() error {
}
}
logLevel := wwlog.GetLogLevel()
if logLevel == wwlog.INFO {
os.Setenv("WAREWULFD_LOGLEVEL", strconv.Itoa(wwlog.SERV))
}else{
os.Setenv("WAREWULFD_LOGLEVEL", strconv.Itoa(logLevel))
}
return nil
}
func DaemonStop() error {
if !util.IsFile(WAREWULFD_PIDFILE) {
fmt.Printf("Warewulf daemon process not running\n")
wwlog.Warn("Warewulf daemon process not running")
return nil
}
@@ -171,7 +199,7 @@ func DaemonStop() error {
if err != nil {
return errors.Wrap(err, "failed to send process SIGTERM")
} else {
fmt.Printf("Terminated Warewulf server at PID: %d\n", pid)
wwlog.Serv("Terminated Warewulf server at PID: %d", pid)
}
}

View File

@@ -1,155 +0,0 @@
package warewulfd
import (
"net/http"
"path"
"strconv"
"strings"
"text/template"
"github.com/hpcng/warewulf/internal/pkg/buildconfig"
nodepkg "github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/overlay"
"github.com/hpcng/warewulf/internal/pkg/warewulfconf"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
)
type iPxeTemplate struct {
Message string
WaitTime string
Hostname string
Fqdn string
Id string
Cluster string
ContainerName string
Hwaddr string
Ipaddr string
Port string
KernelArgs string
KernelOverride string
}
func IpxeSend(w http.ResponseWriter, req *http.Request) {
conf, err := warewulfconf.New()
if err != nil {
daemonLogf("ERROR: Could not open Warewulf configuration: %s\n", err)
w.WriteHeader(503)
return
}
rinfo, err := parseReq(req)
if err != nil {
w.WriteHeader(404)
daemonLogf("ERROR: %s\n", err)
return
}
node, err := GetNode(rinfo.hwaddr)
if err != nil {
// If we failed to find a node, let's see if we can add one...
var netdev string
var unconfiguredNode bool
daemonLogf("IPXEREQ: %s (node not configured)\n", rinfo.hwaddr)
nodeDB, err := nodepkg.New()
if err != nil {
daemonLogf("Could not read node configuration file: %s\n", err)
w.WriteHeader(503)
return
}
n, netdev, err := nodeDB.FindDiscoverableNode()
if err != nil {
unconfiguredNode = true
} else {
n.NetDevs[netdev].Hwaddr.Set(rinfo.hwaddr)
n.Discoverable.SetB(false)
err := nodeDB.NodeUpdate(n)
if err != nil {
daemonLogf("IPXEREQ: %s (failed to set node configuration)\n", rinfo.hwaddr)
unconfiguredNode = true
} else {
err := nodeDB.Persist()
if err != nil {
daemonLogf("IPXEREQ: %s (failed to persist node configuration)\n", rinfo.hwaddr)
unconfiguredNode = true
} else {
node = n
_ = overlay.BuildAllOverlays([]nodepkg.NodeInfo{n})
daemonLogf("IPXEREQ: %s (node automatically configured)\n", rinfo.hwaddr)
err := LoadNodeDB()
if err != nil {
daemonLogf("Could not reload configuration: %s\n", err)
}
}
}
}
if unconfiguredNode {
daemonLogf("IPXEREQ: %s (unknown/unconfigured node)\n", rinfo.hwaddr)
tmpl, err := template.ParseFiles(path.Join(buildconfig.SYSCONFDIR(), "/warewulf/ipxe/unconfigured.ipxe"))
if err != nil {
daemonLogf("ERROR: Could not parse unconfigured node IPXE template: %s\n", err)
return
}
var replace iPxeTemplate
replace.Hwaddr = rinfo.hwaddr
err = tmpl.Execute(w, replace)
if err != nil {
daemonLogf("ERROR: Could not update unconfigured node IPXE template: %s\n", err)
return
}
return
}
}
if node.AssetKey.Defined() && node.AssetKey.Get() != rinfo.assetkey {
w.WriteHeader(404)
daemonLogf("ERROR: Incorrect asset key for node: %s\n", node.Id.Get())
updateStatus(node.Id.Get(), "IPXE", "BAD_ASSET", rinfo.ipaddr)
return
}
ipxeTemplate := path.Join(buildconfig.SYSCONFDIR(), "warewulf/ipxe/"+node.Ipxe.Get()+".ipxe")
tmpl, err := template.ParseFiles(ipxeTemplate)
if err != nil {
wwlog.Printf(wwlog.ERROR, "%s\n", err)
return
}
var replace iPxeTemplate
replace.Id = node.Id.Get()
replace.Cluster = node.ClusterName.Get()
replace.Fqdn = node.Id.Get()
replace.Ipaddr = conf.Ipaddr
replace.Port = strconv.Itoa(conf.Warewulf.Port)
replace.Hostname = node.Id.Get()
replace.Hwaddr = rinfo.hwaddr
replace.ContainerName = node.ContainerName.Get()
replace.KernelArgs = node.Kernel.Args.Get()
replace.KernelOverride = node.Kernel.Override.Get()
err = tmpl.Execute(w, replace)
if err != nil {
wwlog.Printf(wwlog.ERROR, "%s\n", err)
return
}
daemonLogf("SEND: %15s: %s\n", node.Id.Get(), ipxeTemplate)
updateStatus(node.Id.Get(), "IPXE", node.Ipxe.Get()+".ipxe", strings.Split(req.RemoteAddr, ":")[0])
}

View File

@@ -1,54 +0,0 @@
package warewulfd
import (
"net/http"
"path"
"strings"
"github.com/hpcng/warewulf/internal/pkg/container"
"github.com/hpcng/warewulf/internal/pkg/kernel"
)
func KernelSend(w http.ResponseWriter, req *http.Request) {
rinfo, err := parseReq(req)
if err != nil {
w.WriteHeader(404)
daemonLogf("ERROR: %s\n", err)
return
}
node, err := GetNode(rinfo.hwaddr)
if err != nil {
w.WriteHeader(403)
daemonLogf("ERROR(%s): %s\n", rinfo.hwaddr, err)
return
}
if node.AssetKey.Defined() && node.AssetKey.Get() != rinfo.assetkey {
w.WriteHeader(404)
daemonLogf("ERROR: Incorrect asset key for node: %s\n", node.Id.Get())
updateStatus(node.Id.Get(), "KERNEL", "BAD_ASSET", rinfo.ipaddr)
return
}
var fileName string
if node.Kernel.Override.Defined() {
fileName = kernel.KernelImage(node.Kernel.Override.Get())
} else if node.ContainerName.Defined() {
fileName = container.KernelFind(node.ContainerName.Get())
} else {
w.WriteHeader(503)
daemonLogf("WARNING: No 'kernel version' set for node %s\n", node.Id.Get())
return
}
updateStatus(node.Id.Get(), "KERNEL", path.Base(fileName), strings.Split(req.RemoteAddr, ":")[0])
err = sendFile(w, fileName, node.Id.Get())
if err != nil {
daemonLogf("ERROR: %s\n", err)
}
}

View File

@@ -1,41 +0,0 @@
package warewulfd
import (
"net/http"
"strings"
"github.com/hpcng/warewulf/internal/pkg/kernel"
)
func KmodsSend(w http.ResponseWriter, req *http.Request) {
rinfo, err := parseReq(req)
if err != nil {
w.WriteHeader(404)
daemonLogf("ERROR: %s\n", err)
return
}
node, err := GetNode(rinfo.hwaddr)
if err != nil {
w.WriteHeader(403)
daemonLogf("ERROR(%s): %s\n", rinfo.hwaddr, err)
return
}
if node.AssetKey.Defined() && node.AssetKey.Get() != rinfo.assetkey {
w.WriteHeader(404)
daemonLogf("ERROR: Incorrect asset key for node: %s\n", node.Id.Get())
updateStatus(node.Id.Get(), "KMODS_OVERLAY", "BAD_ASSET", rinfo.ipaddr)
return
}
if node.Kernel.Override.Defined() {
fileName := kernel.KmodsImage(node.Kernel.Override.Get())
updateStatus(node.Id.Get(), "KMODS_OVERLAY", node.Kernel.Override.Get()+".img", strings.Split(req.RemoteAddr, ":")[0])
err := sendFile(w, fileName, node.Id.Get())
if err != nil {
daemonLogf("ERROR: %s\n", err)
}
}
}

View File

@@ -14,14 +14,24 @@ type parserInfo struct {
remoteport int
assetkey string
uuid string
stage string
overlay string
compress string
}
func parseReq(req *http.Request) (parserInfo, error) {
var ret parserInfo
url := strings.Split(req.URL.Path, "?")[0]
hwaddr := strings.Split(url, "/")[2]
path_parts := strings.Split(url, "/")
if len(path_parts) != 3 {
return ret, errors.New("unknown path components in GET")
}
// handle when stage was passed in the url path /[stage]/hwaddr
stage := path_parts[1]
hwaddr := path_parts[2]
hwaddr = strings.ReplaceAll(hwaddr, "-", ":")
hwaddr = strings.ToLower(hwaddr)
@@ -36,9 +46,35 @@ func parseReq(req *http.Request) (parserInfo, error) {
if len(req.URL.Query()["uuid"]) > 0 {
ret.uuid = req.URL.Query()["uuid"][0]
}
if len(req.URL.Query()["stage"]) > 0 {
ret.stage = req.URL.Query()["stage"][0]
}else{
if stage == "ipxe" || stage == "provision" {
ret.stage = "ipxe"
}else if stage == "kernel" {
ret.stage = "kernel"
}else if stage == "kmods" {
ret.stage = "kmods"
}else if stage == "container" {
ret.stage = "container"
}else if stage == "overlay-system" {
ret.stage = "system"
}else if stage == "overlay-runtime" {
ret.stage = "runtime"
}
}
if len(req.URL.Query()["overlay"]) > 0 {
ret.overlay = req.URL.Query()["overlay"][0]
}
if len(req.URL.Query()["compress"]) > 0 {
ret.compress = req.URL.Query()["compress"][0]
}
if ret.stage == "" {
return ret, errors.New("no stage encoded in GET")
}
if ret.hwaddr == "" {
return ret, errors.New("no hwaddr encoded in GET")
}

View File

@@ -0,0 +1,278 @@
package warewulfd
import (
"net/http"
"path"
"strconv"
"bytes"
"text/template"
"github.com/hpcng/warewulf/internal/pkg/buildconfig"
nodepkg "github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/container"
"github.com/hpcng/warewulf/internal/pkg/kernel"
"github.com/hpcng/warewulf/internal/pkg/overlay"
"github.com/hpcng/warewulf/internal/pkg/util"
"github.com/hpcng/warewulf/internal/pkg/warewulfconf"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
)
type iPxeTemplate struct {
Message string
WaitTime string
Hostname string
Fqdn string
Id string
Cluster string
ContainerName string
Hwaddr string
Ipaddr string
Port string
KernelArgs string
KernelOverride string
}
func ProvisionSend(w http.ResponseWriter, req *http.Request) {
conf, err := warewulfconf.New()
if err != nil {
wwlog.Error("Could not open Warewulf configuration: %s", err)
w.WriteHeader(http.StatusServiceUnavailable)
return
}
rinfo, err := parseReq(req)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
wwlog.ErrorExc(err, "")
return
}
wwlog.Recv("hwaddr: %s, ipaddr: %s, stage: %s", rinfo.hwaddr, req.RemoteAddr, rinfo.stage )
if rinfo.stage == "runtime" && conf.Warewulf.Secure {
if rinfo.remoteport >= 1024 {
wwlog.Denied("Non-privledged port: %s", req.RemoteAddr)
w.WriteHeader(http.StatusUnauthorized)
return
}
}
status_stages := map[string]string{
"ipxe": "IPXE",
"kernel": "KERNEL",
"kmods": "KMODS_OVERLAY",
"system": "SYSTEM_OVERLAY",
"runtime": "RUNTIME_OVERLAY" }
status_stage := status_stages[rinfo.stage]
var stage_overlays []string
var stage_file string = ""
// TODO: when module version is upgraded to go1.18, should be 'any' type
var tmpl_data interface{}
node, err := GetNode(rinfo.hwaddr)
if err != nil {
// If we failed to find a node, let's see if we can add one...
var netdev string
wwlog.Warn("%s (node not configured)", rinfo.hwaddr)
nodeDB, err := nodepkg.New()
if err != nil {
wwlog.Error("Could not read node configuration file: %s", err)
w.WriteHeader(http.StatusServiceUnavailable)
return
}
n, netdev, err := nodeDB.FindDiscoverableNode()
if err == nil {
n.NetDevs[netdev].Hwaddr.Set(rinfo.hwaddr)
n.Discoverable.SetB(false)
err := nodeDB.NodeUpdate(n)
if err != nil {
wwlog.Serv("%s (failed to set node configuration)", rinfo.hwaddr)
} else {
err := nodeDB.Persist()
if err != nil {
wwlog.Serv("%s (failed to persist node configuration)", rinfo.hwaddr)
} else {
node = n
_ = overlay.BuildAllOverlays([]nodepkg.NodeInfo{n})
wwlog.Serv("%s (node automatically configured)", rinfo.hwaddr)
err := LoadNodeDB()
if err != nil {
wwlog.Warn("Could not reload configuration: %s", err)
}
}
}
}
}
if node.AssetKey.Defined() && node.AssetKey.Get() != rinfo.assetkey {
w.WriteHeader(http.StatusUnauthorized)
wwlog.Denied("Incorrect asset key for node: %s", node.Id.Get())
updateStatus(node.Id.Get(), status_stage, "BAD_ASSET", rinfo.ipaddr)
return
}
if !node.Id.Defined() {
wwlog.Error("%s (unknown/unconfigured node)", rinfo.hwaddr)
if rinfo.stage == "ipxe" {
stage_file = path.Join(buildconfig.SYSCONFDIR(), "/warewulf/ipxe/unconfigured.ipxe")
tmpl_data = iPxeTemplate{
Hwaddr : rinfo.hwaddr }
}
}else if rinfo.stage == "ipxe" {
stage_file = path.Join(buildconfig.SYSCONFDIR(), "warewulf/ipxe/"+node.Ipxe.Get()+".ipxe")
tmpl_data = iPxeTemplate{
Id : node.Id.Get(),
Cluster : node.ClusterName.Get(),
Fqdn : node.Id.Get(),
Ipaddr : conf.Ipaddr,
Port : strconv.Itoa(conf.Warewulf.Port),
Hostname : node.Id.Get(),
Hwaddr : rinfo.hwaddr,
ContainerName : node.ContainerName.Get(),
KernelArgs : node.Kernel.Args.Get(),
KernelOverride : node.Kernel.Override.Get() }
}else if rinfo.stage == "kernel" {
if node.Kernel.Override.Defined() {
stage_file = kernel.KernelImage(node.Kernel.Override.Get())
} else if node.ContainerName.Defined() {
stage_file = container.KernelFind(node.ContainerName.Get())
if stage_file == "" {
wwlog.Error("No kernel found for container %s", node.ContainerName.Get())
}
} else {
wwlog.Warn("No kernel version set for node %s", node.Id.Get())
}
}else if rinfo.stage == "kmods" {
if node.Kernel.Override.Defined() {
stage_file = kernel.KmodsImage(node.Kernel.Override.Get())
}else{
wwlog.Warn("No kernel override modules set for node %s", node.Id.Get())
}
}else if rinfo.stage == "container" {
if node.ContainerName.Defined() {
stage_file = container.ImageFile(node.ContainerName.Get())
} else {
wwlog.Warn("No container set for node %s", node.Id.Get())
}
}else if rinfo.stage == "system" {
if len(node.SystemOverlay.GetSlice()) != 0 {
stage_overlays = node.SystemOverlay.GetSlice()
} else {
wwlog.Warn("No system overlay set for node %s", node.Id.Get())
}
}else if rinfo.stage == "runtime" {
if rinfo.overlay != "" {
stage_overlays = []string{rinfo.overlay}
} else if len(node.RuntimeOverlay.GetSlice()) != 0 {
stage_overlays = node.RuntimeOverlay.GetSlice()
} else {
wwlog.Warn("No runtime overlay set for node %s", node.Id.Get())
}
}
if len(stage_overlays) > 0 {
stage_file = overlay.OverlayImage(node.Id.Get(), stage_overlays)
if conf.Warewulf.AutobuildOverlays {
oneoverlaynewer := false
for _, overlayname := range stage_overlays {
oneoverlaynewer = oneoverlaynewer || util.PathIsNewer(stage_file, overlay.OverlaySourceDir(overlayname))
}
if !util.IsFile(stage_file) || util.PathIsNewer(stage_file, nodepkg.ConfigFile) || oneoverlaynewer {
wwlog.Serv("BUILD %15s, overlays %v", node.Id.Get(), stage_overlays)
_ = overlay.BuildOverlay(node, stage_overlays)
}
}
}
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
}
tmpl, err := template.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 = tmpl.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.Send("%15s: %s", node.Id.Get(), stage_file)
}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, stage_file, node.Id.Get())
if err != nil {
wwlog.ErrorExc(err, "")
return
}
}
updateStatus(node.Id.Get(), status_stage, path.Base(stage_file), rinfo.ipaddr)
}else if stage_file == "" {
w.WriteHeader(http.StatusBadRequest)
wwlog.Error("No resource selected")
updateStatus(node.Id.Get(), status_stage, "BAD_REQUEST", rinfo.ipaddr)
}else{
w.WriteHeader(http.StatusNotFound)
wwlog.Error("Not found: %s", stage_file )
updateStatus(node.Id.Get(), status_stage, "NOT_FOUND", rinfo.ipaddr)
}
}

View File

@@ -1,82 +0,0 @@
package warewulfd
import (
"net/http"
"strings"
nodepkg "github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/overlay"
"github.com/hpcng/warewulf/internal/pkg/util"
"github.com/hpcng/warewulf/internal/pkg/warewulfconf"
)
func RuntimeOverlaySend(w http.ResponseWriter, req *http.Request) {
rinfo, err := parseReq(req)
if err != nil {
w.WriteHeader(404)
daemonLogf("ERROR: %s\n", err)
return
}
node, err := GetNode(rinfo.hwaddr)
if err != nil {
w.WriteHeader(403)
daemonLogf("ERROR(%s): %s\n", rinfo.hwaddr, err)
return
}
if node.AssetKey.Defined() && node.AssetKey.Get() != rinfo.assetkey {
w.WriteHeader(404)
daemonLogf("ERROR: Incorrect asset key for node: %s\n", node.Id.Get())
updateStatus(node.Id.Get(), "RUNTIME_OVERLAY", "BAD_ASSET", rinfo.ipaddr)
return
}
conf, err := warewulfconf.New()
if err != nil {
daemonLogf("ERROR: Could not read Warewulf configuration file: %s\n", err)
w.WriteHeader(503)
return
}
if conf.Warewulf.Secure {
if rinfo.remoteport >= 1024 {
daemonLogf("DENIED: Connection coming from non-privledged port: %s\n", req.RemoteAddr)
w.WriteHeader(401)
return
}
}
if rinfo.overlay != "" {
fileName := overlay.OverlayImage(node.Id.Get(), []string{rinfo.overlay})
if conf.Warewulf.AutobuildOverlays {
_ = overlay.BuildOverlay(node, []string{rinfo.overlay})
}
err := sendFile(w, fileName, node.Id.Get())
if err != nil {
daemonLogf("ERROR: %s\n", err)
}
} else if len(node.RuntimeOverlay.GetSlice()) != 0 {
fileName := overlay.OverlayImage(node.Id.Get(), node.RuntimeOverlay.GetSlice())
updateStatus(node.Id.Get(), "RUNTIME_OVERLAY", node.RuntimeOverlay.Get(), strings.Split(req.RemoteAddr, ":")[0])
if conf.Warewulf.AutobuildOverlays {
oneoverlaynewer := false
for _, overlayname := range node.RuntimeOverlay.GetSlice() {
oneoverlaynewer = oneoverlaynewer || util.PathIsNewer(fileName, overlay.OverlaySourceDir(overlayname))
}
if !util.IsFile(fileName) || util.PathIsNewer(fileName, nodepkg.ConfigFile) || oneoverlaynewer {
daemonLogf("BUILD: %15s: Runtime Overlay\n", node.Id.Get())
_ = overlay.BuildOverlay(node, node.RuntimeOverlay.GetSlice())
}
}
err := sendFile(w, fileName, node.Id.Get())
if err != nil {
daemonLogf("ERROR: %s\n", err)
}
} else {
w.WriteHeader(503)
daemonLogf("WARNING: No 'runtime overlay' set for node %s\n", node.Id.Get())
}
}

View File

@@ -58,7 +58,7 @@ func LoadNodeStatus() error {
func updateStatus(nodeID, stage, sent, ipaddr string) {
rightnow := time.Now().Unix()
wwlog.Printf(wwlog.DEBUG, "Updating node status data: %s\n", nodeID)
wwlog.Debug("Updating node status data: %s", nodeID)
var n NodeStatus
n.NodeName = nodeID
@@ -71,7 +71,7 @@ func updateStatus(nodeID, stage, sent, ipaddr string) {
func statusJSON() ([]byte, error) {
wwlog.Printf(wwlog.DEBUG, "Request for node status data...\n")
wwlog.Debug("Request for node status data...")
ret, err := json.MarshalIndent(statusDB, "", " ")
if err != nil {
@@ -85,11 +85,12 @@ func StatusSend(w http.ResponseWriter, req *http.Request) {
status, err := statusJSON()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
_, err = w.Write(status)
if err != nil {
wwlog.Printf(wwlog.WARN, "Could not send status JSON: %s\n", err)
wwlog.Warn("Could not send status JSON: %s", err)
}
}

View File

@@ -1,66 +0,0 @@
package warewulfd
import (
"net/http"
"strings"
nodepkg "github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/overlay"
"github.com/hpcng/warewulf/internal/pkg/util"
"github.com/hpcng/warewulf/internal/pkg/warewulfconf"
)
func SystemOverlaySend(w http.ResponseWriter, req *http.Request) {
rinfo, err := parseReq(req)
if err != nil {
w.WriteHeader(404)
daemonLogf("ERROR: %s\n", err)
return
}
node, err := GetNode(rinfo.hwaddr)
if err != nil {
w.WriteHeader(403)
daemonLogf("ERROR(%s): %s\n", rinfo.hwaddr, err)
return
}
if node.AssetKey.Defined() && node.AssetKey.Get() != rinfo.assetkey {
w.WriteHeader(404)
daemonLogf("ERROR: Incorrect asset key for node: %s\n", node.Id.Get())
updateStatus(node.Id.Get(), "SYSTEM_OVERLAY", "BAD_ASSET", rinfo.ipaddr)
return
}
conf, err := warewulfconf.New()
if err != nil {
daemonLogf("ERROR: Could not read Warewulf configuration file: %s\n", err)
w.WriteHeader(503)
return
}
if len(node.SystemOverlay.GetSlice()) != 0 {
fileName := overlay.OverlayImage(node.Id.Get(), node.SystemOverlay.GetSlice())
updateStatus(node.Id.Get(), "SYSTEM_OVERLAY", node.SystemOverlay.Get(), strings.Split(req.RemoteAddr, ":")[0])
if conf.Warewulf.AutobuildOverlays {
oneoverlaynewer := false
for _, overlayname := range node.RuntimeOverlay.GetSlice() {
oneoverlaynewer = oneoverlaynewer || util.PathIsNewer(fileName, overlay.OverlaySourceDir(overlayname))
}
if !util.IsFile(fileName) || util.PathIsNewer(fileName, nodepkg.ConfigFile) || oneoverlaynewer {
daemonLogf("BUILD: %15s: System Overlay\n", node.Id.Get())
_ = overlay.BuildOverlay(node, node.SystemOverlay.GetSlice())
}
}
err := sendFile(w, fileName, node.Id.Get())
if err != nil {
daemonLogf("ERROR: %s\n", err)
}
} else {
w.WriteHeader(503)
daemonLogf("WARNING: No 'system system-overlay' set for node %s\n", node.Id.Get())
}
}

View File

@@ -2,16 +2,19 @@ package warewulfd
import (
"io"
"bytes"
"net/http"
"os"
"strconv"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"github.com/pkg/errors"
)
func sendFile(w http.ResponseWriter, filename string, sendto string) error {
fd, err := os.Open(filename)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return err
}
defer fd.Close()
@@ -19,27 +22,33 @@ func sendFile(w http.ResponseWriter, filename string, sendto string) error {
FileHeader := make([]byte, 512)
_, err = fd.Read(FileHeader)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return errors.Wrap(err, "failed to read header")
}
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)
_, err = fd.Seek(0, 0)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return errors.Wrap(err, "failed to seek")
}
_, err = io.Copy(w, fd)
var buf bytes.Buffer
_, err = io.Copy(&buf, fd)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return errors.Wrap(err, "failed to copy")
}
daemonLogf("SEND: %15s: %s\n", sendto, filename)
w.Header().Set("Content-Disposition", "attachment; filename=kernel")
w.Header().Set("Content-Type", FileContentType)
w.Header().Set("Content-Length", FileSize)
_, err = buf.WriteTo(w)
return nil
wwlog.Send("%15s: %s", sendto, filename)
return err
}

View File

@@ -1,7 +1,6 @@
package warewulfd
import (
"fmt"
"net/http"
"os"
"os/signal"
@@ -17,41 +16,46 @@ import (
// TODO: https://github.com/pin/tftp
func RunServer() error {
err := DaemonInitLogging()
if err != nil {
return errors.Wrap(err, "Failed to initialize logging")
}
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP)
go func() {
for range c {
daemonLogf("Recieved SIGHUP, reloading...\n")
wwlog.Warn("Recieved SIGHUP, reloading...")
err := LoadNodeDB()
if err != nil {
wwlog.Printf(wwlog.ERROR, "Could not load node DB: %s\n", err)
wwlog.Error("Could not load node DB: %s", err)
}
err = LoadNodeStatus()
if err != nil {
wwlog.Printf(wwlog.ERROR, "Could not prepopulate node status DB: %s\n", err)
wwlog.Error("Could not prepopulate node status DB: %s", err)
}
}
}()
err := LoadNodeDB()
err = LoadNodeDB()
if err != nil {
fmt.Printf("ERROR: Could not load database: %s\n", err)
wwlog.Error("Could not load database: %s", err)
}
err = LoadNodeStatus()
if err != nil {
wwlog.Printf(wwlog.ERROR, "Could not prepopulate node status DB: %s\n", err)
wwlog.Error("Could not prepopulate node status DB: %s", err)
}
http.HandleFunc("/ipxe/", IpxeSend)
http.HandleFunc("/kernel/", KernelSend)
http.HandleFunc("/kmods/", KmodsSend)
http.HandleFunc("/container/", ContainerSend)
http.HandleFunc("/overlay-system/", SystemOverlaySend)
http.HandleFunc("/overlay-runtime/", RuntimeOverlaySend)
http.HandleFunc("/provision/", ProvisionSend)
http.HandleFunc("/ipxe/", ProvisionSend)
http.HandleFunc("/kernel/", ProvisionSend)
http.HandleFunc("/kmods/", ProvisionSend)
http.HandleFunc("/container/", ProvisionSend)
http.HandleFunc("/overlay-system/", ProvisionSend)
http.HandleFunc("/overlay-runtime/", ProvisionSend)
http.HandleFunc("/status", StatusSend)
conf, err := warewulfconf.New()
@@ -60,7 +64,7 @@ func RunServer() error {
}
daemonPort := conf.Warewulf.Port
daemonLogf("Starting HTTPD REST service on port %d\n", daemonPort)
wwlog.Serv("Starting HTTPD REST service on port %d", daemonPort)
err = http.ListenAndServe(":"+strconv.Itoa(daemonPort), nil)
if err != nil {

View File

@@ -3,86 +3,316 @@ package wwlog
import (
"fmt"
"os"
"io"
"strings"
"time"
"runtime"
"reflect"
"sort"
)
const (
CRITICAL = 0
SECCRITICAL = 1
ERROR = 2
SECERROR = 3
WARN = 4
SECWARN = 5
INFO = 6
SECINFO = 7
VERBOSE = 8
SECVERBOSE = 9
DEBUG = 10
SECDEBUG = 11
)
type LogRecord struct {
Level int
Err error
Msg string
Args []interface{}
Pc uintptr
File string
Line int
Time time.Time
}
/*
Format a log message from a record
Only called if rec.level >= logLevel
*/
type LogFormatter func(logLevel int, rec *LogRecord) string
var (
logLevel = INFO
SECCRITICAL = SetLevelName(51, "SECCRITICAL")
CRITICAL = SetLevelName(50, "CRITICAL")
DENIED = SetLevelName(42, "DENIED")
SECERROR = SetLevelName(41, "SECERROR")
ERROR = SetLevelName(40, "ERROR")
SECWARN = SetLevelName(31, "SECWARN")
WARN = SetLevelName(30, "WARN")
SEND = SetLevelName(27, "SEND")
RECV = SetLevelName(26, "RECV")
SERV = SetLevelName(25, "SERV")
SECINFO = SetLevelName(21, "SECINFO")
INFO = SetLevelName(20, "INFO")
SECVERBOSE = SetLevelName(16, "SECVERBOSE")
VERBOSE = SetLevelName(15, "VERBOSE")
SECDEBUG = SetLevelName(11, "SECDEBUG")
DEBUG = SetLevelName(10, "DEBUG")
)
var levelNums = []int{0}
var levelNames = []string{"NOTSET"}
var logLevel = INFO
var logOut io.Writer = os.Stdout
var logErr io.Writer = os.Stderr
var logFormatter LogFormatter = DefaultFormatter
func LevelNameEff(level int) (int, int, string) {
n := len(levelNums)
idx := sort.SearchInts(levelNums, level)
if idx >= n {
idx = n-1
}
eff_level := levelNums[idx]
eff_name := levelNames[idx]
return idx, eff_level, eff_name
}
func LevelName(level int) string {
_, _, name := LevelNameEff(level)
return name
}
func SetLevelName(level int, name string) int {
n := len(levelNums)
idx := sort.SearchInts(levelNums, level)
if idx < n && levelNums[idx] == level {
levelNames[idx] = name
}else{
levelNums = append(levelNums, level)
levelNames = append(levelNames, name)
if idx < n {
copy(levelNums[idx+1:], levelNums[idx:])
copy(levelNames[idx+1:], levelNames[idx:])
levelNums[idx] = level
levelNames[idx] = name
}
}
return level
}
func DefaultFormatter(logLevel int, rec *LogRecord) string {
message := fmt.Sprintf(rec.Msg, rec.Args...)
if ( !strings.HasSuffix(message, "\n") ) {
// ensure written messages are separated by at least one newline
message += "\n"
}
if rec.Err != nil {
if logLevel < VERBOSE {
// when debugging errors, add file and line number, and any stack trace
message += fmt.Sprintf("%s:%d\n%+v\n", rec.File, rec.Line, rec.Err )
}else{
message += fmt.Sprintf("%v\n", rec.Err )
}
}
if rec.Level == INFO && logLevel == INFO {
// NOTE: this is a bit strange, but for user-friendliness it makes sense
// to not pollute the messages unless something bad happens (by default).
// if logLevel > INFO, then level == INFO messages should not be printed anyway,
// and if logLevel < INFO, then it seems like all messages should get prefixed
return message
}
name := LevelName(rec.Level)
if len(name) <= 7 {
return fmt.Sprintf("%-7s: %s", name, message)
}
return fmt.Sprintf("%-11s: %s", name, message)
}
func EnabledForLevel(level int) bool {
return level >= logLevel
}
/*
Set the central log level. Uneven values are security related
lof entries.
*/
func SetLevel(level int) {
func SetLogLevel(level int) {
logLevel = level
Printf(DEBUG, "Set log level to: %d\n", logLevel)
Debug("Set log level to: %d, %s", logLevel, LevelName(logLevel))
}
func GetLogLevel() int {
return logLevel
}
/*
generate the prefix for log level
Set the log output writers
By default they are set to os.Stdout and os.Stderr
*/
func prefixGen(level int) string {
switch level {
case DEBUG:
return "[DEBUG] : "
case SECDEBUG:
return "[SECDEBUG] : "
case VERBOSE:
return "[VERBOSE] : "
case SECVERBOSE:
return "[SECVERBOSE] : "
case INFO:
return "[INFO] : "
case SECINFO:
return "[SECINFO] : "
case WARN:
return "[WARNING] : "
case SECWARN:
return "[SECWARNING] : "
case ERROR:
return "[ERROR] : "
case SECERROR:
return "[SECERROR] : "
case CRITICAL:
return "[CRITICAL] : "
case SECCRITICAL:
return "[SECCRITICAL]: "
}
return "[UNDEF] : "
func SetLogWriters(out io.Writer, err io.Writer) {
logOut = out
logErr = err
Debug("Set log writers")
}
func printlog(level int, message string) {
if level == INFO && logLevel <= INFO {
fmt.Print(message)
} else if level <= logLevel {
if level < INFO {
fmt.Fprintf(os.Stderr, prefixGen(level)+message)
func GetLogWriters() (io.Writer, io.Writer) {
return logOut, logErr
}
/*
Set the log record formatter
By default this is set to DefaultFormatter
*/
func SetLogFormatter(formatter LogFormatter) {
logFormatter = formatter
Debug("Set log formatter: %s", runtime.FuncForPC(reflect.ValueOf(formatter).Pointer()).Name())
}
func GetLogFormatter() LogFormatter {
return logFormatter
}
/*
Internal method to create a log record
*/
func LogCaller(level int, skip int, err error, message string, a ...interface{}) {
if EnabledForLevel(level) {
pc, file, line, ok := runtime.Caller(skip + 1)
if !ok {
file = "[unknown]"
}
rec := LogRecord{
Level : level,
Err : err,
Msg : message,
Args : a,
Pc : pc,
File : file,
Line : line,
Time : time.Now() }
message = logFormatter(logLevel, &rec)
if level >= ERROR {
fmt.Fprint(logErr, message)
} else {
fmt.Printf(prefixGen(level) + message)
fmt.Fprint(logOut, message)
}
}
}
func Println(level int, message string) {
printlog(level, message)
LogCaller(level, 1, nil, message)
}
func Printf(level int, message string, a ...interface{}) {
printlog(level, fmt.Sprintf(message, a...))
LogCaller(level, 1, nil, message, a...)
}
/*******************************************************************************
Named log level functions
*/
func Log(level int, message string, a ...interface{}) {
LogCaller(level, 1, nil, message, a...)
}
func LogExc(level int, err error, message string, a ...interface{}) {
LogCaller(level, 1, err, message, a...)
}
func Debug(message string, a ...interface{}) {
LogCaller(DEBUG, 1, nil, message, a...)
}
func DebugExc(err error, message string, a ...interface{}) {
LogCaller(DEBUG, 1, err, message, a...)
}
func SecDebug(message string, a ...interface{}) {
LogCaller(SECDEBUG, 1, nil, message, a...)
}
func Verbose(message string, a ...interface{}) {
LogCaller(VERBOSE, 1, nil, message, a...)
}
func VerboseExc(err error, message string, a ...interface{}) {
LogCaller(VERBOSE, 1, err, message, a...)
}
func SecVerbose(message string, a ...interface{}) {
LogCaller(SECVERBOSE, 1, nil, message, a...)
}
func Info(message string, a ...interface{}) {
LogCaller(INFO, 1, nil, message, a...)
}
func InfoExc(err error, message string, a ...interface{}) {
LogCaller(INFO, 1, err, message, a...)
}
func SecInfo(message string, a ...interface{}) {
LogCaller(SECINFO, 1, nil, message, a...)
}
func Serv(message string, a ...interface{}) {
LogCaller(SERV, 1, nil, message, a...)
}
func Recv(message string, a ...interface{}) {
LogCaller(RECV, 1, nil, message, a...)
}
func Send(message string, a ...interface{}) {
LogCaller(SEND, 1, nil, message, a...)
}
func Warn(message string, a ...interface{}) {
LogCaller(WARN, 1, nil, message, a...)
}
func WarnExc(err error, message string, a ...interface{}) {
LogCaller(WARN, 1, err, message, a...)
}
func SecWarn(message string, a ...interface{}) {
LogCaller(SECWARN, 1, nil, message, a...)
}
func Error(message string, a ...interface{}) {
LogCaller(ERROR, 1, nil, message, a...)
}
func ErrorExc(err error, message string, a ...interface{}) {
LogCaller(ERROR, 1, err, message, a...)
}
func SecError(message string, a ...interface{}) {
LogCaller(SECERROR, 1, nil, message, a...)
}
func Denied(message string, a ...interface{}) {
LogCaller(DENIED, 1, nil, message, a...)
}
func Critical(message string, a ...interface{}) {
LogCaller(CRITICAL, 1, nil, message, a...)
}
func CriticalExc(err error, message string, a ...interface{}) {
LogCaller(CRITICAL, 1, err, message, a...)
}
func SecCritical(message string, a ...interface{}) {
LogCaller(SECCRITICAL, 1, nil, message, a...)
}

View File

@@ -1,8 +1,9 @@
{{if .Dhcp.Enabled -}}
# This file is autogenerated by warewulf
# This file is autogenerated by warewulf
# Host: {{.BuildHost}}
# Time: {{.BuildTime}}
# Source: {{.BuildSource}}
allow booting;
allow bootp;
ddns-update-style interim;
@@ -40,15 +41,22 @@ subnet {{$.Network}} netmask {{$.Netmask}} {
next-server {{$.Ipaddr}};
}
{{range $nodes := .AllNodes}}
{{- range $netdevs := $nodes.NetDevs}}
{{- if $netdevs.Primary.Get}}
host {{$nodes.Id.Get}} {
hardware ethernet {{$netdevs.Hwaddr.Get}};
fixed-address {{$netdevs.Ipaddr.Get}};
{{- range $netname, $netdevs := $nodes.NetDevs}}
host {{$nodes.Id.Get}}-{{if $netdevs.Device.Defined}}{{$netdevs.Device.Get}}{{else}}{{$netname}}{{end}} {
{{- if $netdevs.Hwaddr.Defined}}
hardware ethernet {{$netdevs.Hwaddr.Get}};
{{- end}}
{{- if $netdevs.Ipaddr.Defined}}
fixed-address {{$netdevs.Ipaddr.Get}};
{{- end}}
{{- if $netdevs.Primary.GetB}}
option host-name "{{$nodes.Id.Get}}";
{{else}}
option host-name "{{$nodes.Id.Get}}-{{if $netdevs.Device.Defined}}{{$netdevs.Device.Get}}{{else}}{{$netname}}{{end}}";
{{- end}}
}
{{end -}}
{{end -}}
{{end -}}
{{else -}}
subnet {{$.Network}} netmask {{$.Netmask}} {

View File

@@ -1,18 +1,19 @@
{{ IncludeBlock "/etc/hosts" "# Do not edit after this line" }}
# Do not edit after this line
# This block is autogenerated by warewulf
# This block is autogenerated by warewulf
# Host: {{.BuildHost}}
# Time: {{.BuildTime}}
# Source: {{.BuildSource}}
# Warewulf Server
{{$.Ipaddr}} {{$.BuildHost}}
{{range $node := $.AllNodes}} {{/* for each node */}}
# Entry for {{$node.Id.Get}}
{{- range $devname, $netdev := $node.NetDevs}} {{/* for each network device on the node */}}
{{- if $netdev.Ipaddr.Defined}} {{/* if we have an ip address on this network device */}}
{{- if $netdev.Primary.GetB}} {{/* emit the node name as hostname if this is the default */}}
{{$netdev.Ipaddr.Get}} {{$node.Id.Get}} {{$node.Id.Get}}-{{$devname}} {{$node.Id.Get}}-{{$netdev.Device.Get}}
{{- else}}
{{$netdev.Ipaddr.Get}} {{$node.Id.Get}}-{{$devname}} {{$node.Id.Get}}-{{$netdev.Device.Get}}
{{- end}} {{/* end if default */}}
{{- /* emit the node name as hostname if this is the primary */}}
{{$netdev.Ipaddr.Get}} {{$node.Id.Get}}-{{$devname}}
{{- if $netdev.Device.Defined}} {{$node.Id.Get}}-{{$netdev.Device.Get}}{{end}}
{{- if $netdev.Primary.GetB}} {{$node.Id.Get}}{{end}}
{{- end}} {{/* end if ip */}}
{{- end}} {{/* end for each network device */}}
{{end}} {{/* end for each node */}}
{{- end}} {{/* end for each node */}}