From ed7dbdaf323ed48732b5a8af140ee550efde99d1 Mon Sep 17 00:00:00 2001 From: Gregory Kurtzer Date: Tue, 17 Nov 2020 21:50:27 -0800 Subject: [PATCH] Fleshed out the framework of most of the `wwctl overlay` command group. --- internal/app/wwctl/overlay/build/main.go | 58 ++++++++++ internal/app/wwctl/overlay/build/root.go | 25 +++++ internal/app/wwctl/overlay/copy/main.go | 13 +++ internal/app/wwctl/overlay/copy/root.go | 25 +++++ internal/app/wwctl/overlay/delete/main.go | 108 +++++++++++++++++- internal/app/wwctl/overlay/delete/root.go | 9 +- internal/app/wwctl/overlay/edit/main.go | 78 +++++++++++-- internal/app/wwctl/overlay/edit/root.go | 3 +- internal/app/wwctl/overlay/list/main.go | 39 +++++-- internal/app/wwctl/overlay/list/root.go | 7 +- internal/app/wwctl/overlay/mkdir/main.go | 71 ++++++++++++ internal/app/wwctl/overlay/mkdir/root.go | 28 +++++ internal/app/wwctl/overlay/root.go | 6 + internal/pkg/config/config.go | 59 ++++++++-- internal/pkg/overlay/overlay.go | 41 ++++++- internal/pkg/overlay/runtime.go | 129 +++++++++++++++++++++- internal/pkg/overlay/system.go | 127 +++++++++++++++++++++ internal/pkg/util/util.go | 19 +++- 18 files changed, 806 insertions(+), 39 deletions(-) create mode 100644 internal/app/wwctl/overlay/build/main.go create mode 100644 internal/app/wwctl/overlay/build/root.go create mode 100644 internal/app/wwctl/overlay/copy/main.go create mode 100644 internal/app/wwctl/overlay/copy/root.go create mode 100644 internal/app/wwctl/overlay/mkdir/main.go create mode 100644 internal/app/wwctl/overlay/mkdir/root.go diff --git a/internal/app/wwctl/overlay/build/main.go b/internal/app/wwctl/overlay/build/main.go new file mode 100644 index 00000000..9fd71277 --- /dev/null +++ b/internal/app/wwctl/overlay/build/main.go @@ -0,0 +1,58 @@ +package build + +import ( + "github.com/hpcng/warewulf/internal/pkg/assets" + "github.com/hpcng/warewulf/internal/pkg/overlay" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/spf13/cobra" + "os" +) + +func CobraRunE(cmd *cobra.Command, args []string) error { + + + var updateNodes []assets.NodeInfo + + if len(args) > 0 && BuildAll == false { + nodes, err := assets.FindAllNodes() + if err != nil { + wwlog.Printf(wwlog.ERROR, "Cloud not get nodeList: %s\n", err) + os.Exit(1) + } + + for _, node := range nodes { + if SystemOverlay == true && node.SystemOverlay == args[0] { + updateNodes = append(updateNodes, node) + } else if node.RuntimeOverlay == args[0] { + updateNodes = append(updateNodes, node) + } + } + } else { + var err error + updateNodes, err = assets.FindAllNodes() + if err != nil { + wwlog.Printf(wwlog.ERROR, "Cloud not get nodeList: %s\n", err) + os.Exit(1) + } + } + + wwlog.Printf(wwlog.DEBUG, "Checking on system overlay update\n") + if SystemOverlay == true || BuildAll == true { + wwlog.Printf(wwlog.INFO, "Updating System Overlays...\n") + err := overlay.SystemBuild(updateNodes, true) + if err != nil { + wwlog.Printf(wwlog.WARN, "Some system overlays failed to be generated\n") + } + } + + wwlog.Printf(wwlog.DEBUG, "Checking on system overlay update\n") + if SystemOverlay == false || BuildAll == true { + wwlog.Printf(wwlog.INFO, "Updating Runtime Overlays...\n") + err := overlay.RuntimeBuild(updateNodes, true) + if err != nil { + wwlog.Printf(wwlog.WARN, "Some runtime overlays failed to be generated\n") + } + } + + return nil +} \ No newline at end of file diff --git a/internal/app/wwctl/overlay/build/root.go b/internal/app/wwctl/overlay/build/root.go new file mode 100644 index 00000000..93efc9a1 --- /dev/null +++ b/internal/app/wwctl/overlay/build/root.go @@ -0,0 +1,25 @@ +package build + +import "github.com/spf13/cobra" + +var ( + baseCmd = &cobra.Command{ + Use: "build [overlay name]", + Short: "Build/Rebuild an overlay", + Long: "Build or rebuild a Warewulf overlay ", + RunE: CobraRunE, + } + SystemOverlay bool + BuildAll bool +) + +func init() { + baseCmd.PersistentFlags().BoolVarP(&SystemOverlay, "system", "s", false, "Show System Overlays as well") + baseCmd.PersistentFlags().BoolVarP(&BuildAll, "all", "a", false, "Build all overlays (runtime and system)") + +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + return baseCmd +} diff --git a/internal/app/wwctl/overlay/copy/main.go b/internal/app/wwctl/overlay/copy/main.go new file mode 100644 index 00000000..bf4d33ad --- /dev/null +++ b/internal/app/wwctl/overlay/copy/main.go @@ -0,0 +1,13 @@ +package copy + +import ( + "fmt" + "github.com/spf13/cobra" +) + +func CobraRunE(cmd *cobra.Command, args []string) error { + + fmt.Printf("This will copy '%s' to overlay '%s'\n", args[1], args[0]) + + return nil +} \ No newline at end of file diff --git a/internal/app/wwctl/overlay/copy/root.go b/internal/app/wwctl/overlay/copy/root.go new file mode 100644 index 00000000..f48e7689 --- /dev/null +++ b/internal/app/wwctl/overlay/copy/root.go @@ -0,0 +1,25 @@ +package copy + +import "github.com/spf13/cobra" + +var ( + baseCmd = &cobra.Command{ + Use: "copy [overlay name] [source file] (dest location)", + Short: "Copy Warewulf Overlay files", + Long: "Warewulf Copy overlay files", + RunE: CobraRunE, + Args: cobra.RangeArgs(2, 3), + Aliases: []string{"import"}, + } + SystemOverlay bool +) + +func init() { + baseCmd.PersistentFlags().BoolVarP(&SystemOverlay, "system", "s", false, "Show system overlays instead of runtime") + +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + return baseCmd +} diff --git a/internal/app/wwctl/overlay/delete/main.go b/internal/app/wwctl/overlay/delete/main.go index 65fc96aa..259f3fe0 100644 --- a/internal/app/wwctl/overlay/delete/main.go +++ b/internal/app/wwctl/overlay/delete/main.go @@ -2,11 +2,117 @@ package delete import ( "fmt" + "github.com/hpcng/warewulf/internal/pkg/assets" + "github.com/hpcng/warewulf/internal/pkg/config" + "github.com/hpcng/warewulf/internal/pkg/overlay" + "github.com/hpcng/warewulf/internal/pkg/util" + "github.com/hpcng/warewulf/internal/pkg/wwlog" "github.com/spf13/cobra" + "os" + "path" ) func CobraRunE(cmd *cobra.Command, args []string) error { - fmt.Printf("Delete: Hello World\n") + var overlayPath string + config := config.New() + + if SystemOverlay == true { + overlayPath = config.SystemOverlaySource(args[0]) + } else { + overlayPath = config.RuntimeOverlaySource(args[0]) + } + + if overlayPath == "" { + wwlog.Printf(wwlog.ERROR, "Overlay name did not render: '%s'\n", args[0]) + os.Exit(1) + } + + if util.IsDir(overlayPath) == false { + wwlog.Printf(wwlog.ERROR, "Overlay name does not exist: '%s'\n", args[0]) + os.Exit(1) + } + + if len(args) == 1 { + if Force == true { + err := os.RemoveAll(overlayPath) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Failed deleting overlay: %s\n", args[0]) + wwlog.Printf(wwlog.ERROR, "%s\n", err) + os.Exit(1) + } + } else { + err := os.Remove(overlayPath) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Failed deleting overlay: %s\n", args[0]) + wwlog.Printf(wwlog.ERROR, "%s\n", err) + os.Exit(1) + } + } + } else if len(args) > 1 { + for i := 1; i < len(args); i++ { + removePath := path.Join(overlayPath, args[i]) + + if Force == true { + err := os.RemoveAll(removePath) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Failed deleting file from overlay: %s:%s\n", args[0], args[i]) + wwlog.Printf(wwlog.ERROR, "%s\n", err) + os.Exit(1) + } + } else { + err := os.Remove(removePath) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Failed deleting overlay: %s:%s\n", args[0], args[i]) + wwlog.Printf(wwlog.ERROR, "%s\n", err) + os.Exit(1) + } + } + + if RmEmptyDirs == true { + // Cleanup any empty directories left behind... + i := path.Dir(removePath) + for i != overlayPath { + wwlog.Printf(wwlog.DEBUG, "Evaluating directory to remove: %s\n", i) + err := os.Remove(i) + if err != nil { + break + } + + wwlog.Printf(wwlog.VERBOSE, "Removed empty directory: %s\n", i) + i = path.Dir(i) + } + } + } + } + + fmt.Printf("Deleted from overlay: %s:%s\n", args[0], args[1]) + + // Everything below this point is to update the relevant overlays + nodes, err := assets.FindAllNodes() + if err != nil { + wwlog.Printf(wwlog.ERROR, "Cloud not get nodeList: %s\n", err) + os.Exit(1) + } + + var updateNodes []assets.NodeInfo + + for _, node := range nodes { + if SystemOverlay == true && node.SystemOverlay == args[0] { + updateNodes = append(updateNodes, node) + } else if node.RuntimeOverlay == args[0] { + updateNodes = append(updateNodes, node) + } + + } + + if SystemOverlay == true { + wwlog.Printf(wwlog.INFO, "Updating System Overlays...\n") + return overlay.SystemBuild(updateNodes, true) + } else { + wwlog.Printf(wwlog.INFO, "Updating Runtime Overlays...\n") + return overlay.RuntimeBuild(updateNodes, true) + } + return nil } \ No newline at end of file diff --git a/internal/app/wwctl/overlay/delete/root.go b/internal/app/wwctl/overlay/delete/root.go index 20d40cdb..033a21d3 100644 --- a/internal/app/wwctl/overlay/delete/root.go +++ b/internal/app/wwctl/overlay/delete/root.go @@ -10,17 +10,18 @@ var ( Short: "Delete Warewulf Overlay files", Long: "Warewulf Delete overlay files", RunE: CobraRunE, - Args: cobra.ExactArgs(1), - + Args: cobra.MinimumNArgs(1), + Aliases: []string{"rm", "del"}, } SystemOverlay bool Force bool - + RmEmptyDirs bool ) func init() { baseCmd.PersistentFlags().BoolVarP(&SystemOverlay, "system", "s", false, "Show system overlays instead of runtime") - baseCmd.PersistentFlags().BoolVar(&Force, "force", false, "Force deletion of a non-empty overlay") + baseCmd.PersistentFlags().BoolVarP(&Force, "force", "f", false, "Force deletion of a non-empty overlay") + baseCmd.PersistentFlags().BoolVarP(&RmEmptyDirs, "empty", "e", false, "Remove empty directories") } diff --git a/internal/app/wwctl/overlay/edit/main.go b/internal/app/wwctl/overlay/edit/main.go index 0465d9ec..9314f2cf 100644 --- a/internal/app/wwctl/overlay/edit/main.go +++ b/internal/app/wwctl/overlay/edit/main.go @@ -2,12 +2,15 @@ package edit import ( "fmt" + "github.com/hpcng/warewulf/internal/pkg/assets" "github.com/hpcng/warewulf/internal/pkg/config" + "github.com/hpcng/warewulf/internal/pkg/overlay" "github.com/hpcng/warewulf/internal/pkg/util" "github.com/hpcng/warewulf/internal/pkg/wwlog" "github.com/spf13/cobra" "os" "path" + "path/filepath" ) func CobraRunE(cmd *cobra.Command, args []string) error { @@ -16,12 +19,6 @@ func CobraRunE(cmd *cobra.Command, args []string) error { var overlaySourceDir string - if len(args) < 2 { - fmt.Printf("wwctl overlay edit [overlay name] [overlay file]\n") - cmd.Help() - os.Exit(1) - } - if SystemOverlay == true { overlaySourceDir = config.SystemOverlaySource(args[0]) } else { @@ -37,18 +34,75 @@ func CobraRunE(cmd *cobra.Command, args []string) error { wwlog.Printf(wwlog.DEBUG, "Will edit overlay file: %s\n", overlayFile) - err := os.MkdirAll(path.Dir(overlayFile), 0755) + if CreateDirs == true { + err := os.MkdirAll(path.Dir(overlayFile), 0755) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not create directory: %s\n", path.Dir(overlayFile)) + os.Exit(1) + } + } else { + if util.IsDir(path.Dir(overlayFile)) == false { + wwlog.Printf(wwlog.ERROR, "Can not create file, parent directory does not exist, try adding the\n") + wwlog.Printf(wwlog.ERROR, "'--parents' option to create the directory.\n") + os.Exit(1) + } + } + + if util.IsFile(overlayFile) == false && filepath.Ext(overlayFile) == ".ww" { + wwlog.Printf(wwlog.WARN, "This is a new file, creating some default content\n") + + w, err := os.OpenFile(overlayFile, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + wwlog.Printf(wwlog.WARN, "Could not create file for writing: %s\n", err) + } + + fmt.Fprintf(w, "# This is a Warewulf Template file.\n") + fmt.Fprintf(w, "#\n") + fmt.Fprintf(w, "# This file (suffix '.ww') will be automatically rewritten without the suffix\n") + fmt.Fprintf(w, "# when the overlay is rendered for the individual nodes. Here are some examples\n") + fmt.Fprintf(w, "# of macros and logic which can be used within this file:\n") + fmt.Fprintf(w, "#\n") + fmt.Fprintf(w, "# Node FQDN = {{.Fqdn}}\n") + fmt.Fprintf(w, "# Node Group = {{.GroupName}}\n") + fmt.Fprintf(w, "# Network Config = {{.NetDevs.eth0.Ipaddr}}, {{.NetDevs.eth0.Hwaddr}}, etc.\n") + fmt.Fprintf(w, "#\n") + fmt.Fprintf(w, "# Goto the documentation pages for more information: http://www.hpcng.org/...\n") + fmt.Fprintf(w, "\n") + } + + err := util.ExecInteractive(editor, overlayFile) + if err != nil { - wwlog.Printf(wwlog.ERROR, "Could not create directory: %s\n", path.Dir(overlayFile)) + wwlog.Printf(wwlog.ERROR, "Editor process existed with non-zero\n") os.Exit(1) } - if editor == "" { - wwlog.Printf(wwlog.WARN, "No default editor provided, will use `nano`.") - editor = "nano" + // Everything below this point is to update the relevant overlays + nodes, err := assets.FindAllNodes() + if err != nil { + wwlog.Printf(wwlog.ERROR, "Cloud not get nodeList: %s\n", err) + os.Exit(1) } - return util.ExecInteractive(editor, overlayFile) + var updateNodes []assets.NodeInfo + for _, node := range nodes { + if SystemOverlay == true && node.SystemOverlay == args[0] { + updateNodes = append(updateNodes, node) + } else if node.RuntimeOverlay == args[0] { + updateNodes = append(updateNodes, node) + } + + } + + if SystemOverlay == true { + wwlog.Printf(wwlog.INFO, "Updating System Overlays...\n") + return overlay.SystemBuild(updateNodes, true) + } else { + wwlog.Printf(wwlog.INFO, "Updating Runtime Overlays...\n") + return overlay.RuntimeBuild(updateNodes, true) + } + + return nil } diff --git a/internal/app/wwctl/overlay/edit/root.go b/internal/app/wwctl/overlay/edit/root.go index 15b86a1b..b08c5091 100644 --- a/internal/app/wwctl/overlay/edit/root.go +++ b/internal/app/wwctl/overlay/edit/root.go @@ -15,12 +15,13 @@ var ( } SystemOverlay bool ListFiles bool - + CreateDirs bool ) func init() { baseCmd.PersistentFlags().BoolVarP(&SystemOverlay, "system", "s", false, "Show system overlays instead of runtime") baseCmd.PersistentFlags().BoolVarP(&ListFiles, "files", "f", false, "List files contained within a given overlay") + baseCmd.PersistentFlags().BoolVarP(&CreateDirs, "parents", "p", false, "Create any necessary parent directories") } diff --git a/internal/app/wwctl/overlay/list/main.go b/internal/app/wwctl/overlay/list/main.go index 34ad06f6..7171c966 100644 --- a/internal/app/wwctl/overlay/list/main.go +++ b/internal/app/wwctl/overlay/list/main.go @@ -8,6 +8,8 @@ import ( "github.com/hpcng/warewulf/internal/pkg/util" "github.com/hpcng/warewulf/internal/pkg/wwlog" "github.com/spf13/cobra" + "os" + "syscall" ) func CobraRunE(cmd *cobra.Command, args []string) error { @@ -18,10 +20,18 @@ func CobraRunE(cmd *cobra.Command, args []string) error { var nodeList []assets.NodeInfo if SystemOverlay == true { - fmt.Printf("%-25s %-8s %-8s\n", "SYSTEM OVERLAY NAME", "NODES", "FILES") + if ListLong == false { + fmt.Printf("%-30s %-12s %-12s\n", "SYSTEM OVERLAY NAME", "NODES", "FILES/DIRS") + } else { + fmt.Printf("%-10s %5s %-5s %-18s %s\n", "PERM MODE", "UID", "GID", "SYSTEM-OVERLAY", "FILE PATH") + } o, err = overlay.FindAllSystemOverlays() } else { - fmt.Printf("%-25s %-8s %-8s\n", "RUNTIME OVERLAY NAME", "NODES", "FILES") + if ListLong == false { + fmt.Printf("%-30s %-12s %-12s\n", "RUNTIME OVERLAY NAME", "NODES", "FILES/DIRS") + } else { + fmt.Printf("%-10s %5s %-5s %-18s %s\n", "PERM MODE", "UID", "GID", "RUNTIME-OVERLAY", "FILE PATH") + } o, err = overlay.FindAllRuntimeOverlays() } if err != nil { @@ -67,17 +77,32 @@ func CobraRunE(cmd *cobra.Command, args []string) error { files := util.FindFiles(path) wwlog.Printf(wwlog.DEBUG, "Iterating overlay path: %s\n", path) - if ListFiles == true { + if ListContents == true { var fileCount int for file := range files { - fmt.Printf("%-25s %-8d /%s\n", name, set[name], files[file]) - fileCount ++ + fmt.Printf("%-30s %-12d /%-12s\n", name, set[name], files[file]) + fileCount++ } if fileCount == 0 { - fmt.Printf("%-25s %-8d %-8d\n", name, set[name], 0) + fmt.Printf("%-30s %-12d %-12d\n", name, set[name], 0) + } + } else if ListLong == true { + for file := range files { + s, err := os.Stat(files[file]) + if err != nil { + continue + } + + fileMode := s.Mode() + perms := fileMode & os.ModePerm + + sys := s.Sys() + + fmt.Printf("%v %5d %-5d %-18s /%s\n", perms, sys.(*syscall.Stat_t).Uid, sys.(*syscall.Stat_t).Gid, o[overlay], files[file]) + } } else { - fmt.Printf("%-25s %-8d %-8d\n", name, set[name], len(files)) + fmt.Printf("%-30s %-12d %-12d\n", name, set[name], len(files)) } } else { diff --git a/internal/app/wwctl/overlay/list/root.go b/internal/app/wwctl/overlay/list/root.go index 90f16c51..b70bcc2d 100644 --- a/internal/app/wwctl/overlay/list/root.go +++ b/internal/app/wwctl/overlay/list/root.go @@ -10,14 +10,17 @@ var ( Short: "List Warewulf Overlays", Long: "Warewulf List overlay", RunE: CobraRunE, + Aliases: []string{"ls"}, } SystemOverlay bool - ListFiles bool + ListContents bool + ListLong bool ) func init() { baseCmd.PersistentFlags().BoolVarP(&SystemOverlay, "system", "s", false, "Show system overlays instead of runtime") - baseCmd.PersistentFlags().BoolVarP(&ListFiles, "files", "f", false, "List files contained within a given overlay") + baseCmd.PersistentFlags().BoolVarP(&ListContents, "all", "a", false, "List the contents of overlays") + baseCmd.PersistentFlags().BoolVarP(&ListLong, "long", "l", false, "List 'long' of all overlay contents") } diff --git a/internal/app/wwctl/overlay/mkdir/main.go b/internal/app/wwctl/overlay/mkdir/main.go new file mode 100644 index 00000000..85683f92 --- /dev/null +++ b/internal/app/wwctl/overlay/mkdir/main.go @@ -0,0 +1,71 @@ +package mkdir + +import ( + "fmt" + "github.com/hpcng/warewulf/internal/pkg/assets" + "github.com/hpcng/warewulf/internal/pkg/config" + "github.com/hpcng/warewulf/internal/pkg/overlay" + "github.com/hpcng/warewulf/internal/pkg/util" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/spf13/cobra" + "os" + "path" +) + +func CobraRunE(cmd *cobra.Command, args []string) error { + config := config.New() + var overlaySourceDir string +// mode := uint32(strconv.ParseUint(PermMode, 8, 32)) + + + if SystemOverlay == true { + overlaySourceDir = config.SystemOverlaySource(args[0]) + } else { + overlaySourceDir = config.RuntimeOverlaySource(args[0]) + } + + if util.IsDir(overlaySourceDir) == false { + wwlog.Printf(wwlog.ERROR, "Overlay does not exist: %s\n", args[0]) + os.Exit(1) + } + + overlayDir := path.Join(overlaySourceDir, args[1]) + + wwlog.Printf(wwlog.DEBUG, "Will create directory in overlay: %s:%s\n", args[0], overlayDir) + + err := os.MkdirAll(overlayDir, 0755) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not create directory: %s\n", path.Dir(overlayDir)) + os.Exit(1) + } + + fmt.Printf("Created directory within overlay: %s:%s\n", args[0], args[1]) + + // Everything below this point is to update the relevant overlays + nodes, err := assets.FindAllNodes() + if err != nil { + wwlog.Printf(wwlog.ERROR, "Cloud not get nodeList: %s\n", err) + os.Exit(1) + } + + var updateNodes []assets.NodeInfo + + for _, node := range nodes { + if SystemOverlay == true && node.SystemOverlay == args[0] { + updateNodes = append(updateNodes, node) + } else if node.RuntimeOverlay == args[0] { + updateNodes = append(updateNodes, node) + } + + } + + if SystemOverlay == true { + wwlog.Printf(wwlog.INFO, "Updating System Overlays...\n") + return overlay.SystemBuild(updateNodes, true) + } else { + wwlog.Printf(wwlog.INFO, "Updating Runtime Overlays...\n") + return overlay.RuntimeBuild(updateNodes, true) + } + + return nil +} \ No newline at end of file diff --git a/internal/app/wwctl/overlay/mkdir/root.go b/internal/app/wwctl/overlay/mkdir/root.go new file mode 100644 index 00000000..7f608cb2 --- /dev/null +++ b/internal/app/wwctl/overlay/mkdir/root.go @@ -0,0 +1,28 @@ +package mkdir + +import ( + "github.com/spf13/cobra" +) + +var ( + baseCmd = &cobra.Command{ + Use: "mkdir [overlay name] [directory path]", + Short: "Create a new directory", + Long: "Create a new directory within a Warewulf overlay", + RunE: CobraRunE, + Args: cobra.MinimumNArgs(2), + } + SystemOverlay bool +// PermMode os.FileMode +) + +func init() { + baseCmd.PersistentFlags().BoolVarP(&SystemOverlay, "system", "s", false, "Show System Overlays as well") + // This will be added back as soon as I figure out how to properly handle the FileMode case +// baseCmd.PersistentFlags().Uint32VarP(&PermMode, "mode", "m", 0755, "Permission mode for directory") +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + return baseCmd +} diff --git a/internal/app/wwctl/overlay/root.go b/internal/app/wwctl/overlay/root.go index f1947da0..a35732ab 100644 --- a/internal/app/wwctl/overlay/root.go +++ b/internal/app/wwctl/overlay/root.go @@ -1,10 +1,13 @@ package overlay import ( + "github.com/hpcng/warewulf/internal/app/wwctl/overlay/build" + "github.com/hpcng/warewulf/internal/app/wwctl/overlay/copy" "github.com/hpcng/warewulf/internal/app/wwctl/overlay/create" "github.com/hpcng/warewulf/internal/app/wwctl/overlay/delete" "github.com/hpcng/warewulf/internal/app/wwctl/overlay/edit" "github.com/hpcng/warewulf/internal/app/wwctl/overlay/list" + "github.com/hpcng/warewulf/internal/app/wwctl/overlay/mkdir" "github.com/hpcng/warewulf/internal/app/wwctl/overlay/show" "github.com/spf13/cobra" @@ -27,6 +30,9 @@ func init() { baseCmd.AddCommand(create.GetCommand()) baseCmd.AddCommand(edit.GetCommand()) baseCmd.AddCommand(delete.GetCommand()) + baseCmd.AddCommand(mkdir.GetCommand()) + baseCmd.AddCommand(build.GetCommand()) + baseCmd.AddCommand(copy.GetCommand()) } diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go index 702af958..60276073 100644 --- a/internal/pkg/config/config.go +++ b/internal/pkg/config/config.go @@ -53,6 +53,9 @@ func init() { if c.LocalStateDir == "" { c.LocalStateDir = "/var/warewulf" } + if c.Editor == "" { + c.Editor = "vi" + } util.ValidateOrDie("warewulf.conf", "warewulfd ipaddr", c.Ipaddr, "^[0-9]+.[0-9]+.[0-9]+.[0-9]+$") util.ValidateOrDie("warewulf.conf", "system config dir", c.SysConfDir, "^[a-zA-Z0-9-._:/]+$") @@ -82,9 +85,14 @@ func (self *Config) RuntimeOverlayDir() string { } func (self *Config) SystemOverlaySource(overlayName string) string { + if overlayName == "" { + wwlog.Printf(wwlog.ERROR, "System overlay name is not defined\n") + return "" + } + if util.TaintCheck(overlayName, "^[a-zA-Z0-9-._]+$") == false { wwlog.Printf(wwlog.ERROR, "System overlay name contains illegal characters: %s\n", overlayName) - os.Exit(1) + return "" } return path.Join(self.SystemOverlayDir(), overlayName) @@ -92,63 +100,98 @@ func (self *Config) SystemOverlaySource(overlayName string) string { func (self *Config) RuntimeOverlaySource(overlayName string) string { + if overlayName == "" { + wwlog.Printf(wwlog.ERROR, "Runtime overlay name is not defined\n") + return "" + } + if util.TaintCheck(overlayName, "^[a-zA-Z0-9-._]+$") == false { wwlog.Printf(wwlog.ERROR, "Runtime overlay name contains illegal characters: %s\n", overlayName) - os.Exit(1) + return "" } return path.Join(self.RuntimeOverlayDir(), overlayName) } func (self *Config) KernelImage(kernelVersion string) string { + if kernelVersion == "" { + wwlog.Printf(wwlog.ERROR, "Kernel Version is not defined\n") + return "" + } + if util.TaintCheck(kernelVersion, "^[a-zA-Z0-9-._]+$") == false { wwlog.Printf(wwlog.ERROR, "Runtime overlay name contains illegal characters: %s\n", kernelVersion) - os.Exit(1) + return "" } return fmt.Sprintf("%s/provision/kernel/vmlinuz-%s", self.LocalStateDir, kernelVersion) } func (self *Config) KmodsImage(kernelVersion string) string { + if kernelVersion == "" { + wwlog.Printf(wwlog.ERROR, "Kernel Version is not defined\n") + return "" + } + if util.TaintCheck(kernelVersion, "^[a-zA-Z0-9-._]+$") == false { wwlog.Printf(wwlog.ERROR, "Runtime overlay name contains illegal characters: %s\n", kernelVersion) - os.Exit(1) + return "" } return fmt.Sprintf("%s/provision/kernel/kmods-%s.img", self.LocalStateDir, kernelVersion) } func (self *Config) VnfsImage(vnfsNameClean string) string { + if vnfsNameClean == "" { + wwlog.Printf(wwlog.ERROR, "VNFS name is not defined\n") + return "" + } + if util.TaintCheck(vnfsNameClean, "^[a-zA-Z0-9-._:]+$") == false { wwlog.Printf(wwlog.ERROR, "Runtime overlay name contains illegal characters: %s\n", vnfsNameClean) - os.Exit(1) + return "" } return fmt.Sprintf("%s/provision/vnfs/%s.img.gz", self.LocalStateDir, vnfsNameClean) } func (self *Config) SystemOverlayImage(nodeName string) string { + if nodeName == "" { + wwlog.Printf(wwlog.ERROR, "Node name is not defined\n") + return "" + } + if util.TaintCheck(nodeName, "^[a-zA-Z0-9-._:]+$") == false { wwlog.Printf(wwlog.ERROR, "System overlay name contains illegal characters: %s\n", nodeName) - os.Exit(1) + return "" } return fmt.Sprintf("%s/provision/overlays/system/%s.img", self.LocalStateDir, nodeName) } func (self *Config) RuntimeOverlayImage(nodeName string) string { + if nodeName == "" { + wwlog.Printf(wwlog.ERROR, "Node name is not defined\n") + return "" + } + if util.TaintCheck(nodeName, "^[a-zA-Z0-9-._:]+$") == false { wwlog.Printf(wwlog.ERROR, "System overlay name contains illegal characters: %s\n", nodeName) - os.Exit(1) + return "" } return fmt.Sprintf("%s/provision/overlays/runtime/%s.img", self.LocalStateDir, nodeName) } func (self *Config) VnfsChroot(vnfsNameClean string) string { + if vnfsNameClean == "" { + wwlog.Printf(wwlog.ERROR, "VNFS name is not defined\n") + return "" + } + if util.TaintCheck(vnfsNameClean, "^[a-zA-Z0-9-._:]+$") == false { wwlog.Printf(wwlog.ERROR, "Runtime overlay name contains illegal characters: %s\n", vnfsNameClean) - os.Exit(1) + return "" } return fmt.Sprintf("%s/chroot/%s.img", self.LocalStateDir, vnfsNameClean) diff --git a/internal/pkg/overlay/overlay.go b/internal/pkg/overlay/overlay.go index 67f65004..72569cd4 100644 --- a/internal/pkg/overlay/overlay.go +++ b/internal/pkg/overlay/overlay.go @@ -1,3 +1,42 @@ package overlay -//Shared code will go here.... \ No newline at end of file +import ( + "github.com/hpcng/warewulf/internal/pkg/config" + "github.com/hpcng/warewulf/internal/pkg/util" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/hpcng/warewulf/internal/pkg/vnfs" + "io/ioutil" + "path" + "strings" +) + + +func templateFileInclude(path string) string { + wwlog.Printf(wwlog.DEBUG, "Including file into template: %s\n", path) + content, err := ioutil.ReadFile(path) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Template include: %s\n", err) + } + return strings.TrimSuffix(string(content), "\n") +} + +func templateVnfsFileInclude(vnfsname string, filepath string) string { + wwlog.Printf(wwlog.DEBUG, "Including VNFS file into template: %s: %s\n", vnfsname, filepath) + config := config.New() + v := vnfs.New(vnfsname) + + vnfsdir := config.VnfsChroot(v.NameClean()) + + if util.IsDir(vnfsdir) == false { + wwlog.Printf(wwlog.WARN, "Could not include file from non-existent VNFS cache: %s:%s\n", vnfsname, filepath) + return "" + } + wwlog.Printf(wwlog.DEBUG, "IncludeVnfs file from: %s/%s\n", vnfsdir, filepath) + + content, err := ioutil.ReadFile(path.Join(vnfsdir, filepath)) + + if err != nil { + wwlog.Printf(wwlog.ERROR, "Template include: %s\n", err) + } + return strings.TrimSuffix(string(content), "\n") +} diff --git a/internal/pkg/overlay/runtime.go b/internal/pkg/overlay/runtime.go index 57e45360..058419a6 100644 --- a/internal/pkg/overlay/runtime.go +++ b/internal/pkg/overlay/runtime.go @@ -1,12 +1,20 @@ package overlay import ( + "fmt" + "github.com/hpcng/warewulf/internal/pkg/assets" "github.com/hpcng/warewulf/internal/pkg/config" "github.com/hpcng/warewulf/internal/pkg/errors" "github.com/hpcng/warewulf/internal/pkg/util" "github.com/hpcng/warewulf/internal/pkg/wwlog" "io/ioutil" "os" + "os/exec" + "path" + "path/filepath" + "regexp" + "strings" + "text/template" ) func FindAllRuntimeOverlays() ([]string, error) { @@ -42,4 +50,123 @@ func RuntimeOverlayInit(name string) error { err := os.MkdirAll(path, 0755) return err -} \ No newline at end of file +} + + +func RuntimeBuild(nodeList []assets.NodeInfo, force bool) error { + config := config.New() + + wwlog.SetIndent(4) + + for _, node := range nodeList { + if node.RuntimeOverlay != "" { + OverlayDir := config.RuntimeOverlaySource(node.RuntimeOverlay) + OverlayFile := config.RuntimeOverlayImage(node.Fqdn) + + wwlog.Printf(wwlog.VERBOSE, "Building Runtime Overlay for: %s\n", node.Fqdn) + + tmpDir, err := ioutil.TempDir(os.TempDir(), ".wwctl-runtime-overlay-") + if err != nil { + return err + } + + if util.IsDir(OverlayDir) == false { + wwlog.Printf(wwlog.WARN, "Skipping non-existent overlay source: %s\n", OverlayDir) + continue + } + + err = os.MkdirAll(path.Dir(OverlayFile), 0755) + if err != nil { + return err + } + + if force == false { + wwlog.Printf(wwlog.DEBUG, "Checking if overlay is required\n") + } + if util.PathIsNewer(OverlayDir, OverlayFile) { + if force == false { + wwlog.Printf(wwlog.INFO, "%-35s: Skipping, overlay is current\n", node.Fqdn) + continue + } + } + + wwlog.Printf(wwlog.DEBUG, "Changing directory to OverlayDir: %s\n", OverlayDir) + err = os.Chdir(OverlayDir) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not chdir() to OverlayDir: %s\n", OverlayDir) + continue + } + + wwlog.Printf(wwlog.DEBUG, "Walking the file system: %s\n", OverlayDir) + err = filepath.Walk(".", func(location string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + wwlog.Printf(wwlog.DEBUG, "Found directory: %s\n", location) + + err := os.MkdirAll(path.Join(tmpDir, location), info.Mode()) + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + return err + } + + } else if filepath.Ext(location) == ".ww" { + wwlog.Printf(wwlog.DEBUG, "Found template file: %s\n", location) + + destFile := strings.TrimSuffix(location, ".ww") + + tmpl, err := template.New(path.Base(location)).Funcs(template.FuncMap{"Include": templateFileInclude, "IncludeVnfs": templateVnfsFileInclude}).ParseGlob(path.Join(OverlayDir, destFile + ".ww*")) + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + return err + } + + w, err := os.OpenFile(path.Join(tmpDir, destFile), os.O_RDWR|os.O_CREATE, info.Mode()) + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + return err + } + defer w.Close() + + err = tmpl.Execute(w, node) + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + return err + } + + } else if b, _ := regexp.MatchString(`\.ww[a-zA-Z0-9\-\._]*$`, location); b == true { + wwlog.Printf(wwlog.DEBUG, "Ignoring WW template file: %s\n", location) + } else { + wwlog.Printf(wwlog.DEBUG, "Found file: %s\n", location) + + err := util.CopyFile(path.Join(OverlayDir, location), path.Join(tmpDir, location)) + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + return err + } + + } + + return nil + }) + wwlog.Printf(wwlog.VERBOSE, "Finished generating overlay directory for: %s\n", node.Fqdn) + + cmd := fmt.Sprintf("cd \"%s\"; find . | cpio --quiet -o -H newc -F \"%s\"", tmpDir, OverlayFile) + wwlog.Printf(wwlog.DEBUG, "RUNNING: %s\n", cmd) + err = exec.Command("/bin/sh", "-c", cmd).Run() + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not generate runtime image overlay: %s\n", err) + continue + } + wwlog.Printf(wwlog.INFO, "%-35s: Done\n", node.Fqdn) + + wwlog.Printf(wwlog.DEBUG, "Removing temporary directory: %s\n", tmpDir) + os.RemoveAll(tmpDir) + } + } + + wwlog.SetIndent(0) + return nil +} diff --git a/internal/pkg/overlay/system.go b/internal/pkg/overlay/system.go index 04843b9a..53755efe 100644 --- a/internal/pkg/overlay/system.go +++ b/internal/pkg/overlay/system.go @@ -1,12 +1,20 @@ package overlay import ( + "fmt" + "github.com/hpcng/warewulf/internal/pkg/assets" "github.com/hpcng/warewulf/internal/pkg/config" "github.com/hpcng/warewulf/internal/pkg/errors" "github.com/hpcng/warewulf/internal/pkg/util" "github.com/hpcng/warewulf/internal/pkg/wwlog" "io/ioutil" "os" + "os/exec" + "path" + "path/filepath" + "regexp" + "strings" + "text/template" ) func FindAllSystemOverlays() ([]string, error) { @@ -42,4 +50,123 @@ func SystemOverlayInit(name string) error { err := os.MkdirAll(path, 0755) return err +} + + +func SystemBuild(nodeList []assets.NodeInfo, force bool) error { + config := config.New() + + wwlog.SetIndent(4) + + for _, node := range nodeList { + if node.SystemOverlay != "" { + OverlayDir := config.SystemOverlaySource(node.SystemOverlay) + OverlayFile := config.SystemOverlayImage(node.Fqdn) + + wwlog.Printf(wwlog.VERBOSE, "Building System Overlay for: %s\n", node.Fqdn) + + tmpDir, err := ioutil.TempDir(os.TempDir(), ".wwctl-system-overlay-") + if err != nil { + return err + } + + if util.IsDir(OverlayDir) == false { + wwlog.Printf(wwlog.WARN, "Skipping non-existent overlay source: %s\n", OverlayDir) + continue + } + + err = os.MkdirAll(path.Dir(OverlayFile), 0755) + if err != nil { + return err + } + + if force == false { + wwlog.Printf(wwlog.DEBUG, "Checking if overlay is required\n") + } + if util.PathIsNewer(OverlayDir, OverlayFile) { + if force == false { + wwlog.Printf(wwlog.INFO, "%-35s: Skipping, overlay is current\n", node.Fqdn) + continue + } + } + + wwlog.Printf(wwlog.DEBUG, "Changing directory to OverlayDir: %s\n", OverlayDir) + err = os.Chdir(OverlayDir) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not chdir() to OverlayDir: %s\n", OverlayDir) + continue + } + + wwlog.Printf(wwlog.DEBUG, "Walking the file system: %s\n", OverlayDir) + err = filepath.Walk(".", func(location string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + wwlog.Printf(wwlog.DEBUG, "Found directory: %s\n", location) + + err := os.MkdirAll(path.Join(tmpDir, location), info.Mode()) + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + return err + } + + } else if filepath.Ext(location) == ".ww" { + wwlog.Printf(wwlog.DEBUG, "Found template file: %s\n", location) + + destFile := strings.TrimSuffix(location, ".ww") + + tmpl, err := template.New(path.Base(location)).Funcs(template.FuncMap{"Include": templateFileInclude, "IncludeVnfs": templateVnfsFileInclude}).ParseGlob(path.Join(OverlayDir, destFile + ".ww*")) + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + return err + } + + w, err := os.OpenFile(path.Join(tmpDir, destFile), os.O_RDWR|os.O_CREATE, info.Mode()) + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + return err + } + defer w.Close() + + err = tmpl.Execute(w, node) + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + return err + } + + } else if b, _ := regexp.MatchString(`\.ww[a-zA-Z0-9\-\._]*$`, location); b == true { + wwlog.Printf(wwlog.DEBUG, "Ignoring WW template file: %s\n", location) + } else { + wwlog.Printf(wwlog.DEBUG, "Found file: %s\n", location) + + err := util.CopyFile(path.Join(OverlayDir, location), path.Join(tmpDir, location)) + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + return err + } + + } + + return nil + }) + wwlog.Printf(wwlog.VERBOSE, "Finished generating overlay directory for: %s\n", node.Fqdn) + + cmd := fmt.Sprintf("cd \"%s\"; find . | cpio --quiet -o -H newc -F \"%s\"", tmpDir, OverlayFile) + wwlog.Printf(wwlog.DEBUG, "RUNNING: %s\n", cmd) + err = exec.Command("/bin/sh", "-c", cmd).Run() + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not generate system image overlay: %s\n", err) + continue + } + wwlog.Printf(wwlog.INFO, "%-35s: Done\n", node.Fqdn) + + wwlog.Printf(wwlog.DEBUG, "Removing temporary directory: %s\n", tmpDir) + os.RemoveAll(tmpDir) + } + } + + wwlog.SetIndent(0) + return nil } \ No newline at end of file diff --git a/internal/pkg/util/util.go b/internal/pkg/util/util.go index 32851d7b..15141e3c 100644 --- a/internal/pkg/util/util.go +++ b/internal/pkg/util/util.go @@ -92,6 +92,13 @@ func IsDir(path string) (bool) { return false } +func IsFile(path string) (bool) { + if _, err := os.Stat(path); err == nil { + return true + } + return false +} + func TaintCheck(pattern string, expr string) bool { if b, _ := regexp.MatchString(expr, pattern); b == true { @@ -122,10 +129,18 @@ func FindFiles(path string) []string { return err } - if IsDir(location) == false { - wwlog.Printf(wwlog.DEBUG, "FindFiles() found: %s\n", location) + if location == "." { + return nil + } + + if IsDir(location) == true { + wwlog.Printf(wwlog.DEBUG, "FindFiles() found directory: %s\n", location) + ret = append(ret, location +"/") + } else { + wwlog.Printf(wwlog.DEBUG, "FindFiles() found file: %s\n", location) ret = append(ret, location) } + return nil }) if err != nil {