added the wwctl overlay parse command

The `wwctl overlay parse -n NODE FILE` will interpret the given overlay file and print it stdout, via wwlog.PRINTF.
The overlay code had to be refactored for this, but hopefully this makes the code a bit more readable. Also the tstruct has not its own initilization code.
This commit is contained in:
Christian Goll
2022-05-25 11:41:33 +02:00
parent dfe3f907b9
commit 9e5b2d1aa3
4 changed files with 140 additions and 6 deletions

View File

@@ -0,0 +1,83 @@
package parse
import (
"bufio"
"bytes"
"os"
"path/filepath"
"regexp"
"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/wwlog"
"github.com/spf13/cobra"
)
func CobraRunE(cmd *cobra.Command, args []string) error {
var host node.NodeInfo
if NodeName == "" {
host.Kernel = new(node.KernelEntry)
host.Ipmi = new(node.IpmiEntry)
var idEntry node.Entry
hostname, _ := os.Hostname()
idEntry.Set(hostname)
host.Id = idEntry
} else {
nodeDB, err := node.New()
if err != nil {
wwlog.Printf(wwlog.ERROR, "Could not open node configuration: %s\n", err)
os.Exit(1)
}
nodes, err := nodeDB.FindAllNodes()
if err != nil {
wwlog.Printf(wwlog.ERROR, "Could not get node list: %s\n", err)
os.Exit(1)
}
node := node.FilterByName(nodes, []string{NodeName})
if len(node) != 1 {
wwlog.Printf(wwlog.ERROR, "%v does not identify a single node\n", NodeName)
os.Exit(1)
}
host = node[0]
}
if !util.IsFile(args[0]) {
wwlog.Printf(wwlog.ERROR, "%s is not a file\n", args[0])
}
tstruct := overlay.InitStruct(host)
buffer, backupFile, writeFile, err := overlay.RenderTemplateFile(args[0], tstruct)
if err != nil {
return err
}
if filepath.Ext(args[0]) != ".ww" {
wwlog.Printf(wwlog.WARN, "%s has not the '.ww' so wont be rendered if in overlay\n", args[0])
}
wwlog.Printf(wwlog.INFO, "backupFile: %v\nwriteFile: %v\n", backupFile, writeFile)
var outBuffer bytes.Buffer
// search for magic file name comment
bufferScanner := bufio.NewScanner(bytes.NewReader(buffer.Bytes()))
bufferScanner.Split(overlay.ScanLines)
reg := regexp.MustCompile(`.*{{\s*/\*\s*file\s*["'](.*)["']\s*\*/\s*}}.*`)
foundFileComment := false
destFileName := args[0]
for bufferScanner.Scan() {
line := bufferScanner.Text()
filenameFromTemplate := reg.FindAllStringSubmatch(line, -1)
if len(filenameFromTemplate) != 0 {
wwlog.Printf(wwlog.DEBUG, "Found multifile comment, new filename %s\n", filenameFromTemplate[0][1])
if foundFileComment {
wwlog.Printf(wwlog.INFO, "%s", outBuffer.String())
outBuffer.Reset()
}
destFileName = filenameFromTemplate[0][1]
wwlog.Printf(wwlog.INFO, "Filename: %s\n\n", destFileName)
wwlog.Printf(wwlog.INFO, "%s", outBuffer.String())
foundFileComment = true
} else {
_, _ = outBuffer.WriteString(line)
}
}
wwlog.Printf(wwlog.INFO, "Filename: %s\n\n", destFileName)
wwlog.Printf(wwlog.INFO, "%s", outBuffer.String())
return nil
}

View File

@@ -0,0 +1,41 @@
package parse
import (
"log"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/spf13/cobra"
)
var (
baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "parse [OPTIONS] FILE_NAME",
Short: "parses interprets the given file as warewulf template",
Long: "This command tries to parse a given file as warewulf template, it needs not to be in overlay folder.",
RunE: CobraRunE,
Aliases: []string{"parse"},
Args: cobra.ExactArgs(1),
}
NodeName string
)
func init() {
baseCmd.PersistentFlags().StringVarP(&NodeName, "node", "n", "", "node used for the variables in the template")
if err := baseCmd.RegisterFlagCompletionFunc("node", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
nodeDB, _ := node.New()
nodes, _ := nodeDB.FindAllNodes()
var node_names []string
for _, node := range nodes {
node_names = append(node_names, node.Id.Get())
}
return node_names, cobra.ShellCompDirectiveNoFileComp
}); err != nil {
log.Println(err)
}
}
// GetRootCommand returns the root cobra.Command for the application.
func GetCommand() *cobra.Command {
return baseCmd
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/hpcng/warewulf/internal/app/wwctl/overlay/imprt" "github.com/hpcng/warewulf/internal/app/wwctl/overlay/imprt"
"github.com/hpcng/warewulf/internal/app/wwctl/overlay/list" "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/mkdir"
"github.com/hpcng/warewulf/internal/app/wwctl/overlay/parse"
"github.com/hpcng/warewulf/internal/app/wwctl/overlay/show" "github.com/hpcng/warewulf/internal/app/wwctl/overlay/show"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -17,9 +18,9 @@ import (
var ( var (
baseCmd = &cobra.Command{ baseCmd = &cobra.Command{
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
Use: "overlay COMMAND [OPTIONS]", Use: "overlay COMMAND [OPTIONS]",
Short: "Warewulf Overlay Management", Short: "Warewulf Overlay Management",
Long: "Management interface for Warewulf overlays", Long: "Management interface for Warewulf overlays",
} }
) )
@@ -34,6 +35,8 @@ func init() {
baseCmd.AddCommand(imprt.GetCommand()) baseCmd.AddCommand(imprt.GetCommand())
baseCmd.AddCommand(chmod.GetCommand()) baseCmd.AddCommand(chmod.GetCommand())
baseCmd.AddCommand(chown.GetCommand()) baseCmd.AddCommand(chown.GetCommand())
baseCmd.AddCommand(parse.GetCommand())
} }
// GetRootCommand returns the root cobra.Command for the application. // GetRootCommand returns the root cobra.Command for the application.

View File

@@ -243,12 +243,15 @@ func BuildOverlayIndir(nodeInfo node.NodeInfo, overlayNames []string, outputDir
destFile := strings.TrimSuffix(location, ".ww") destFile := strings.TrimSuffix(location, ".ww")
buffer, backupFile, writeFile, err := RenderTemplateFile(location, tstruct) buffer, backupFile, writeFile, err := RenderTemplateFile(location, tstruct)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("Failed to render template %s", location))
}
if writeFile { if writeFile {
destFileName := destFile destFileName := destFile
var fileBuffer bytes.Buffer var fileBuffer bytes.Buffer
// search for magic file name comment // search for magic file name comment
fileScanner := bufio.NewScanner(bytes.NewReader(buffer.Bytes())) fileScanner := bufio.NewScanner(bytes.NewReader(buffer.Bytes()))
fileScanner.Split(scanLines) fileScanner.Split(ScanLines)
reg := regexp.MustCompile(`.*{{\s*/\*\s*file\s*["'](.*)["']\s*\*/\s*}}.*`) reg := regexp.MustCompile(`.*{{\s*/\*\s*file\s*["'](.*)["']\s*\*/\s*}}.*`)
foundFileComment := false foundFileComment := false
for fileScanner.Scan() { for fileScanner.Scan() {
@@ -346,7 +349,11 @@ Parses the template with the given filename, variables must be in data. Returns
parsed template as bytes.Buffer, and the bool variables for backupFile and writeFile. parsed template as bytes.Buffer, and the bool variables for backupFile and writeFile.
If something goes wrong an error is returned. If something goes wrong an error is returned.
*/ */
func RenderTemplateFile(fileName string, data TemplateStruct) (buffer bytes.Buffer, backupFile bool, writeFile bool, err error) { func RenderTemplateFile(fileName string, data TemplateStruct) (
buffer bytes.Buffer,
backupFile bool,
writeFile bool,
err error) {
backupFile = true backupFile = true
writeFile = true writeFile = true
tmpl, err := template.New(path.Base(fileName)).Option("missingkey=default").Funcs(template.FuncMap{ tmpl, err := template.New(path.Base(fileName)).Option("missingkey=default").Funcs(template.FuncMap{
@@ -385,7 +392,7 @@ func RenderTemplateFile(fileName string, data TemplateStruct) (buffer bytes.Buff
} }
// Simple version of ScanLines, but include the line break // Simple version of ScanLines, but include the line break
func scanLines(data []byte, atEOF bool) (advance int, token []byte, err error) { func ScanLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 { if atEOF && len(data) == 0 {
return 0, nil, nil return 0, nil, nil
} }