diff --git a/Makefile b/Makefile index 506776ca..8d444f80 100644 --- a/Makefile +++ b/Makefile @@ -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 - diff --git a/etc/ipxe/bigimage.ipxe b/etc/ipxe/bigimage.ipxe deleted file mode 100644 index e854f1c0..00000000 --- a/etc/ipxe/bigimage.ipxe +++ /dev/null @@ -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 diff --git a/etc/ipxe/default.ipxe b/etc/ipxe/default.ipxe index 8ebe42a5..04792a02 100644 --- a/etc/ipxe/default.ipxe +++ b/etc/ipxe/default.ipxe @@ -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,68 @@ echo Kernel: {{.ContainerName}} (container default) echo KernelArgs: {{.KernelArgs}} echo -set base http://{{.Ipaddr}}:{{.Port}} +{{if ne .Syslog "" -}} +set syslog {{$.Syslog}}; +{{- end}} +{{if ne .Syslogs "" -}} +set syslogs {{$.Syslogs}}; +{{- end}} -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 +set uri_base http://{{.Ipaddr}}:{{.Port}}/provision/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid} +echo Downloading node provisions: ${uri_base} +echo Kernel image: +kernel --name kernel ${uri_base}&stage=kernel || 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 + +echo Runtime image: +imgextract --name runtime ${uri_base}&stage=runtime&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 + +echo Runtime image: +initrd --name runtime ${uri_base}&stage=runtime || 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 initrd=runtime wwid={{.Hwaddr}} {{.KernelArgs}} +sleep 3 +boot kernel initrd=container initrd=kmods initrd=system initrd=runtime wwid={{.Hwaddr}} {{.KernelArgs}} || goto reboot +{{- else -}} +echo boot kernel initrd=container initrd=system initrd=runtime wwid={{.Hwaddr}} {{.KernelArgs}} +sleep 3 boot kernel initrd=container initrd=system initrd=runtime wwid={{.Hwaddr}} {{.KernelArgs}} || goto reboot +{{- end}} :reboot echo diff --git a/internal/app/wwclient/root.go b/internal/app/wwclient/root.go index 8b6c590c..1f6b9115 100644 --- a/internal/app/wwclient/root.go +++ b/internal/app/wwclient/root.go @@ -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 { diff --git a/internal/app/wwctl/container/imprt/main.go b/internal/app/wwctl/container/imprt/main.go index 0f6f47af..305c0d8c 100644 --- a/internal/app/wwctl/container/imprt/main.go +++ b/internal/app/wwctl/container/imprt/main.go @@ -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, !NoSyncUser) if err != nil && !NoSyncUser { - 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") diff --git a/internal/app/wwctl/node/add/main.go b/internal/app/wwctl/node/add/main.go index 43eae0b3..54b1c4ba 100644 --- a/internal/app/wwctl/node/add/main.go +++ b/internal/app/wwctl/node/add/main.go @@ -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) } } diff --git a/internal/app/wwctl/root.go b/internal/app/wwctl/root.go index 059b38db..e724e43b 100644 --- a/internal/app/wwctl/root.go +++ b/internal/app/wwctl/root.go @@ -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 } diff --git a/internal/pkg/container/build.go b/internal/pkg/container/build.go index 594e0540..147334ce 100644 --- a/internal/pkg/container/build.go +++ b/internal/pkg/container/build.go @@ -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 } diff --git a/internal/pkg/container/config.go b/internal/pkg/container/config.go index 420909b6..1faf7423 100644 --- a/internal/pkg/container/config.go +++ b/internal/pkg/container/config.go @@ -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") } diff --git a/internal/pkg/container/kernel.go b/internal/pkg/container/kernel.go index 9d74ee21..63494004 100644 --- a/internal/pkg/container/kernel.go +++ b/internal/pkg/container/kernel.go @@ -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 + } } } } diff --git a/internal/pkg/kernel/kernel.go b/internal/pkg/kernel/kernel.go index 5e1df815..4fae2ea9 100644 --- a/internal/pkg/kernel/kernel.go +++ b/internal/pkg/kernel/kernel.go @@ -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 } diff --git a/internal/pkg/overlay/funcmap.go b/internal/pkg/overlay/funcmap.go index e40e0c98..bc6e0785 100644 --- a/internal/pkg/overlay/funcmap.go +++ b/internal/pkg/overlay/funcmap.go @@ -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 } diff --git a/internal/pkg/overlay/overlay.go b/internal/pkg/overlay/overlay.go index 1e2d1316..8c542fe9 100644 --- a/internal/pkg/overlay/overlay.go +++ b/internal/pkg/overlay/overlay.go @@ -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) } } diff --git a/internal/pkg/util/util.go b/internal/pkg/util/util.go index a82e0065..e1841863 100644 --- a/internal/pkg/util/util.go +++ b/internal/pkg/util/util.go @@ -9,8 +9,10 @@ import ( "net" "os" "os/exec" + "path" "path/filepath" "regexp" + "strings" "syscall" "time" @@ -45,17 +47,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 } @@ -73,7 +75,7 @@ func RandomString(n int) string { } 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 @@ -85,7 +87,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 @@ -120,18 +122,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 } @@ -145,10 +148,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) } @@ -161,8 +164,123 @@ func FindFiles(path string) []string { return ret } +//****************************************************************************** +func FindFilterFiles( + path string, + include []string, + ignore []string, + ignore_xdev bool) ([]string, error) { + + wwlog.Debug("Finding files: %s", path) + + ofiles := []string{} + + cwd, err := os.Getwd() + if err != nil { + return ofiles, err + } + defer 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 { + if stat, err := os.Stat(ifile); err == nil && 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 @@ -196,7 +314,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) } } @@ -240,7 +358,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") @@ -266,7 +384,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 } @@ -330,18 +448,161 @@ 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 ) 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 + } + + go func() { + defer stdin.Close() + io.WriteString(stdin, strings.Join(ifiles, "\n")) + }() + + out, err := proc.CombinedOutput() + if len(out) > 0 { + wwlog.Debug(string(out)) + } + + return err +} + +/******************************************************************************* + Compress a file using gzip or pigz +*/ +func FileGz( + file string ) 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: ", 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 ) 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 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 +} diff --git a/internal/pkg/warewulfconf/datastructure.go b/internal/pkg/warewulfconf/datastructure.go index 887d11ef..0974eabe 100644 --- a/internal/pkg/warewulfconf/datastructure.go +++ b/internal/pkg/warewulfconf/datastructure.go @@ -38,6 +38,10 @@ type DhcpConf struct { RangeStart string `yaml:"range start" default:"192.168.200.50"` RangeEnd string `yaml:"range end" default:"192.168.200.99"` SystemdName string `yaml:"systemd name" default:"dhcpd"` + Syslog string `yaml:"syslog" default:""` + Syslogs string `yaml:"syslogs" default:""` + Interface string `yaml:"interface" default:""` + Hwaddr string `yaml:"hwaddr" default:""` } type TftpConf struct { diff --git a/internal/pkg/warewulfd/container.go b/internal/pkg/warewulfd/container.go deleted file mode 100644 index 32dc942e..00000000 --- a/internal/pkg/warewulfd/container.go +++ /dev/null @@ -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()) - } -} diff --git a/internal/pkg/warewulfd/daemon.go b/internal/pkg/warewulfd/daemon.go index 3025bbde..2879fbd6 100644 --- a/internal/pkg/warewulfd/daemon.go +++ b/internal/pkg/warewulfd/daemon.go @@ -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,50 @@ 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) + } + } + 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.SetLogWriters(logwriter, logwriter) + + wwlog.SetLogFormatter(wwlog.DefaultFormatter) - } else { - prefix := fmt.Sprintf("[%s] ", time.Now().Format(time.UnixDate)) - fmt.Printf(prefix+message, a...) } + + loginit = true + + return nil } func DaemonStart() error { @@ -68,6 +81,7 @@ func DaemonStart() error { } os.Setenv("WAREWULFD_BACKGROUND", "1") + os.Setenv("WAREWULFD_LOGLEVEL", strconv.Itoa(wwlog.GetLogLevel())) f, err := os.OpenFile(WAREWULFD_LOGFILE, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) if err != nil { @@ -91,7 +105,7 @@ func DaemonStart() error { fmt.Fprintf(p, "%d", pid) - fmt.Printf("Started Warewulf (%s) server at PID: %d\n", version.GetVersion(), pid) + wwlog.Info("Started Warewulf (%s) server at PID: %d", version.GetVersion(), pid) } @@ -117,7 +131,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.Info("Warewulf server is running at PID: %d", pid) } } @@ -145,12 +159,14 @@ func DaemonReload() error { } } + os.Setenv("WAREWULFD_LOGLEVEL", strconv.Itoa(wwlog.GetLogLevel())) + 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 +187,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.Info("Terminated Warewulf server at PID: %d", pid) } } diff --git a/internal/pkg/warewulfd/ipxe.go b/internal/pkg/warewulfd/ipxe.go deleted file mode 100644 index 9ceac517..00000000 --- a/internal/pkg/warewulfd/ipxe.go +++ /dev/null @@ -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]) - -} diff --git a/internal/pkg/warewulfd/kernel.go b/internal/pkg/warewulfd/kernel.go deleted file mode 100644 index 1c78bdff..00000000 --- a/internal/pkg/warewulfd/kernel.go +++ /dev/null @@ -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) - } - -} diff --git a/internal/pkg/warewulfd/kmods.go b/internal/pkg/warewulfd/kmods.go deleted file mode 100644 index e02fe661..00000000 --- a/internal/pkg/warewulfd/kmods.go +++ /dev/null @@ -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) - } - } -} diff --git a/internal/pkg/warewulfd/parser.go b/internal/pkg/warewulfd/parser.go index 169484fa..4f4ff167 100644 --- a/internal/pkg/warewulfd/parser.go +++ b/internal/pkg/warewulfd/parser.go @@ -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") } diff --git a/internal/pkg/warewulfd/provision.go b/internal/pkg/warewulfd/provision.go new file mode 100644 index 00000000..62be3faf --- /dev/null +++ b/internal/pkg/warewulfd/provision.go @@ -0,0 +1,279 @@ +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 + Syslog string + Syslogs 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 + } + + if conf.Warewulf.Secure { + if rinfo.remoteport >= 1024 { + wwlog.Error("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" } + + wwlog.Info("REQ: %s: %s", rinfo.hwaddr, rinfo.stage ) + + 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.Info("%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.Info("%s (failed to set node configuration)", rinfo.hwaddr) + + } else { + err := nodeDB.Persist() + if err != nil { + wwlog.Info("%s (failed to persist node configuration)", rinfo.hwaddr) + + } else { + node = n + _ = overlay.BuildAllOverlays([]nodepkg.NodeInfo{n}) + + wwlog.Info("%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.Error("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), + Syslog : conf.Dhcp.Syslog, + Syslogs : conf.Dhcp.Syslogs, + 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.Info("BUILD %15s, overlays %v", node.Id.Get(), stage_overlays) + _ = overlay.BuildOverlay(node, stage_overlays) + } + } + } + + wwlog.Info("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())) + buf.WriteTo(w) + + wwlog.Info("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) + } + +} diff --git a/internal/pkg/warewulfd/runtime.go b/internal/pkg/warewulfd/runtime.go deleted file mode 100644 index 96f5bb2e..00000000 --- a/internal/pkg/warewulfd/runtime.go +++ /dev/null @@ -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()) - } -} diff --git a/internal/pkg/warewulfd/status.go b/internal/pkg/warewulfd/status.go index eb4d462d..02333bfe 100644 --- a/internal/pkg/warewulfd/status.go +++ b/internal/pkg/warewulfd/status.go @@ -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) } } diff --git a/internal/pkg/warewulfd/system.go b/internal/pkg/warewulfd/system.go deleted file mode 100644 index 7574f00b..00000000 --- a/internal/pkg/warewulfd/system.go +++ /dev/null @@ -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()) - } -} diff --git a/internal/pkg/warewulfd/util.go b/internal/pkg/warewulfd/util.go index 02958602..16fad483 100644 --- a/internal/pkg/warewulfd/util.go +++ b/internal/pkg/warewulfd/util.go @@ -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) + buf.WriteTo(w) + + wwlog.Info("SEND: %15s: %s", sendto, filename) return nil } diff --git a/internal/pkg/warewulfd/warewulfd.go b/internal/pkg/warewulfd/warewulfd.go index 1600a637..5adaeb7d 100644 --- a/internal/pkg/warewulfd/warewulfd.go +++ b/internal/pkg/warewulfd/warewulfd.go @@ -1,7 +1,6 @@ package warewulfd import ( - "fmt" "net/http" "os" "os/signal" @@ -17,41 +16,43 @@ import ( // TODO: https://github.com/pin/tftp func RunServer() error { + DaemonInitLogging() 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() 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 +61,7 @@ func RunServer() error { } daemonPort := conf.Warewulf.Port - daemonLogf("Starting HTTPD REST service on port %d\n", daemonPort) + wwlog.Info("Starting HTTPD REST service on port %d", daemonPort) err = http.ListenAndServe(":"+strconv.Itoa(daemonPort), nil) if err != nil { diff --git a/internal/pkg/wwlog/wwlog.go b/internal/pkg/wwlog/wwlog.go index e700de35..d8b94ded 100644 --- a/internal/pkg/wwlog/wwlog.go +++ b/internal/pkg/wwlog/wwlog.go @@ -3,86 +3,287 @@ package wwlog import ( "fmt" "os" + "io" + "strings" + "time" + "runtime" + "reflect" ) const ( - CRITICAL = 0 - SECCRITICAL = 1 - ERROR = 2 - SECERROR = 3 - WARN = 4 - SECWARN = 5 - INFO = 6 - SECINFO = 7 - VERBOSE = 8 - SECVERBOSE = 9 - DEBUG = 10 + SECCRITICAL = 51 + CRITICAL = 50 + SECERROR = 41 + ERROR = 40 + SECWARN = 31 + WARN = 30 + SECINFO = 21 + INFO = 20 + SECVERBOSE = 16 + VERBOSE = 15 SECDEBUG = 11 + DEBUG = 10 + NOTSET = 0 ) -var ( - logLevel = INFO -) +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 + rec.level >= logLevel +*/ +type LogFormatter func(logLevel int, rec *LogRecord) string + +/* + Get string level name for level number +*/ +func LevelName(level int) string { + if level >= SECCRITICAL { + return "SECCRITICAL" + } + if level >= CRITICAL { + return "CRITICAL" + } + if level >= SECERROR { + return "SECERROR" + } + if level >= ERROR { + return "ERROR" + } + if level >= SECWARN { + return "SECWARN" + } + if level >= WARN { + return "WARN" + } + if level >= SECINFO { + return "SECINFO" + } + if level >= INFO { + return "INFO" + } + if level >= SECVERBOSE { + return "SECVERBOSE" + } + if level >= VERBOSE { + return "VERBOSE" + } + if level >= SECDEBUG { + return "SECDEBUG" + } + if level >= DEBUG { + return "DEBUG" + } + + return "NOTSET" +} + +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 + } + + return fmt.Sprintf("%-7s: %s", LevelName(rec.Level), message) +} + +var logLevel = INFO +var logOut io.Writer = os.Stdout +var logErr io.Writer = os.Stderr +var logFormatter LogFormatter = DefaultFormatter + +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 recordLog(level int, err error, message string, a ...interface{}) { + + if EnabledForLevel(level) { + pc, file, line, ok := runtime.Caller(2) + 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.Fprintf(logErr, message) } else { - fmt.Printf(prefixGen(level) + message) + fmt.Fprintf(logOut, message) } } } func Println(level int, message string) { - printlog(level, message) + recordLog(level, nil, message) } func Printf(level int, message string, a ...interface{}) { - printlog(level, fmt.Sprintf(message, a...)) + recordLog(level, nil, message, a...) +} + +/******************************************************************************* + Named log level functions +*/ +func Log(level int, message string, a ...interface{}) { + recordLog(level, nil, message, a...) +} + +func LogExc(level int, err error, message string, a ...interface{}) { + recordLog(level, err, message, a...) +} + +func Debug(message string, a ...interface{}) { + recordLog(DEBUG, nil, message, a...) +} + +func DebugExc(err error, message string, a ...interface{}) { + recordLog(DEBUG, err, message, a...) +} + +func SecDebug(message string, a ...interface{}) { + recordLog(SECDEBUG, nil, message, a...) +} + +func Verbose(message string, a ...interface{}) { + recordLog(VERBOSE, nil, message, a...) +} + +func VerboseExc(err error, message string, a ...interface{}) { + recordLog(VERBOSE, err, message, a...) +} + +func SecVerbose(message string, a ...interface{}) { + recordLog(SECVERBOSE, nil, message, a...) +} + +func Info(message string, a ...interface{}) { + recordLog(INFO, nil, message, a...) +} + +func InfoExc(err error, message string, a ...interface{}) { + recordLog(INFO, err, message, a...) +} + +func SecInfo(message string, a ...interface{}) { + recordLog(SECINFO, nil, message, a...) +} + +func Warn(message string, a ...interface{}) { + recordLog(WARN, nil, message, a...) +} + +func WarnExc(err error, message string, a ...interface{}) { + recordLog(WARN, err, message, a...) +} + +func SecWarn(message string, a ...interface{}) { + recordLog(SECWARN, nil, message, a...) +} + +func Error(message string, a ...interface{}) { + recordLog(ERROR, nil, message, a...) +} + +func ErrorExc(err error, message string, a ...interface{}) { + recordLog(ERROR, err, message, a...) +} + +func SecError(message string, a ...interface{}) { + recordLog(SECERROR, nil, message, a...) +} + +func Critical(message string, a ...interface{}) { + recordLog(CRITICAL, nil, message, a...) +} + +func CriticalExc(err error, message string, a ...interface{}) { + recordLog(CRITICAL, err, message, a...) +} + +func SecCritical(message string, a ...interface{}) { + recordLog(SECCRITICAL, nil, message, a...) } diff --git a/overlays/host/etc/dhcp/dhcpd.conf.ww b/overlays/host/etc/dhcp/dhcpd.conf.ww index c93e318c..8fe8c138 100644 --- a/overlays/host/etc/dhcp/dhcpd.conf.ww +++ b/overlays/host/etc/dhcp/dhcpd.conf.ww @@ -1,8 +1,13 @@ {{if .Dhcp.Enabled -}} -# This file is autogenerated by warewulf +# This file is autogenerated by warewulf # Host: {{.BuildHost}} # Time: {{.BuildTime}} # Source: {{.BuildSource}} + +{{if ne .Dhcp.Interface "" -}} +INTERFACESv4="{{$.Dhcp.Interface}}"; +{{- end}} + allow booting; allow bootp; ddns-update-style interim; @@ -14,6 +19,16 @@ option space ipxe; option ipxe.no-pxedhcp code 176 = unsigned integer 8; option ipxe.no-pxedhcp 1; +# https://ipxe.org/cfg/syslogs +{{if ne .Dhcp.Syslog "" -}} +option log-servers {{$.Dhcp.Syslog}}; +{{end -}} +{{if ne .Dhcp.Syslogs "" -}} +option ipxe-encap-opts code 175 = encapsulate ipxe; +option ipxe.syslogs code 85 = string; +option ipxe.syslogs "{{$.Dhcp.Syslogs}}"; +{{end -}} + option architecture-type code 93 = unsigned integer 16; if exists user-class and option user-class = "iPXE" { @@ -35,20 +50,35 @@ if exists user-class and option user-class = "iPXE" { } } +host {{$.Id}} { + {{- if ne .Dhcp.Hwaddr ""}} + hardware ethernet {{$.Dhcp.Hwaddr}}; + {{- end}} + fixed-address {{$.Ipaddr}}; + option host-name "{{$.Id}}"; +} + {{if eq .Dhcp.Template "static" -}} subnet {{$.Network}} netmask {{$.Netmask}} { next-server {{$.Ipaddr}}; } {{range $nodes := .AllNodes}} -{{- range $netdevs := $nodes.NetDevs}} -{{- if $netdevs.Default.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.Default.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}} { diff --git a/overlays/host/etc/hosts.ww b/overlays/host/etc/hosts.ww index 0cc05cd7..7ef3c0cd 100644 --- a/overlays/host/etc/hosts.ww +++ b/overlays/host/etc/hosts.ww @@ -1,18 +1,20 @@ {{ 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.Default.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 default */}} +{{$netdev.Ipaddr.Get}} +{{- if $netdev.Default.GetB}} {{$node.Id.Get}} +{{else}} {{$node.Id.Get}}-{{if $netdev.Device.Defined}}{{$netdev.Device.Get}}{{else}}{{$devname}}{{end}} +{{- end}} {{- end}} {{/* end if ip */}} {{- end}} {{/* end for each network device */}} -{{end}} {{/* end for each node */}} +{{- end}} {{/* end for each node */}}