From facde98fb59a88f56cff0e0b9a32573f42a6b71c Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Mon, 13 Oct 2025 17:07:14 +0200 Subject: [PATCH] new overlay variables command --- internal/app/wwctl/overlay/root.go | 2 + internal/app/wwctl/overlay/variables/main.go | 30 ++++++ .../app/wwctl/overlay/variables/main_test.go | 100 ++++++++++++++++++ internal/app/wwctl/overlay/variables/root.go | 39 +++++++ 4 files changed, 171 insertions(+) create mode 100644 internal/app/wwctl/overlay/variables/main.go create mode 100644 internal/app/wwctl/overlay/variables/main_test.go create mode 100644 internal/app/wwctl/overlay/variables/root.go diff --git a/internal/app/wwctl/overlay/root.go b/internal/app/wwctl/overlay/root.go index 529da93c..7f221a30 100644 --- a/internal/app/wwctl/overlay/root.go +++ b/internal/app/wwctl/overlay/root.go @@ -12,6 +12,7 @@ import ( "github.com/warewulf/warewulf/internal/app/wwctl/overlay/list" "github.com/warewulf/warewulf/internal/app/wwctl/overlay/mkdir" "github.com/warewulf/warewulf/internal/app/wwctl/overlay/show" + "github.com/warewulf/warewulf/internal/app/wwctl/overlay/variables" ) var ( @@ -35,6 +36,7 @@ func init() { baseCmd.AddCommand(imprt.GetCommand()) baseCmd.AddCommand(chmod.GetCommand()) baseCmd.AddCommand(chown.GetCommand()) + baseCmd.AddCommand(variables.GetCommand()) } // GetRootCommand returns the root cobra.Command for the application. diff --git a/internal/app/wwctl/overlay/variables/main.go b/internal/app/wwctl/overlay/variables/main.go new file mode 100644 index 00000000..fdf51222 --- /dev/null +++ b/internal/app/wwctl/overlay/variables/main.go @@ -0,0 +1,30 @@ +package variables + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + "github.com/warewulf/warewulf/internal/pkg/overlay" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func CobraRunE(cmd *cobra.Command, args []string) error { + overlayName := args[0] + filePath := args[1] + + ov, err := overlay.Get(overlayName) + if err != nil { + wwlog.Error("Failed to get overlay %s: %s", overlayName, err) + return err + } + + vars := ov.ParseVars(filePath) + if vars == nil { + return fmt.Errorf("could not parse variables for %s in overlay %s", filePath, overlayName) + } + + fmt.Println(strings.Join(vars, "\n")) + + return nil +} diff --git a/internal/app/wwctl/overlay/variables/main_test.go b/internal/app/wwctl/overlay/variables/main_test.go new file mode 100644 index 00000000..b97c150d --- /dev/null +++ b/internal/app/wwctl/overlay/variables/main_test.go @@ -0,0 +1,100 @@ +package variables + +import ( + "bytes" + "io" + "os" + "path" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_Overlay_Variables(t *testing.T) { + env := testenv.New(t) + defer env.RemoveAll() + conf := env.Configure() + + // be quiet + wwlog.SetLogFormatter(func(int, *wwlog.LogRecord) string { return "" }) + + overlayDir := path.Join(conf.Paths.SiteOverlaydir(), "test-overlay") + err := os.MkdirAll(overlayDir, 0755) + if err != nil { + t.Fatalf("could not create overlay dir: %v", err) + } + + templateContent := ` +{{ .Kernel.Tags.foo }} +{{ .Node.Tags.bar }} +{{ .Cluster.Tags.baz }} +` + templatePath := path.Join(overlayDir, "test.ww") + err = os.WriteFile(templatePath, []byte(templateContent), 0644) + if err != nil { + t.Fatalf("could not write template file: %v", err) + } + + // Redirect stdout + old := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + baseCmd.SetArgs([]string{"test-overlay", "test.ww"}) + err = baseCmd.Execute() + assert.NoError(t, err) + + // Restore stdout + w.Close() + os.Stdout = old + + var buf bytes.Buffer + _, err = io.Copy(&buf, r) + if err != nil { + t.Fatalf("could not read stdout: %v", err) + } + + output := strings.TrimSpace(buf.String()) + expected := []string{ + ".Kernel.Tags.foo", + ".Node.Tags.bar", + ".Cluster.Tags.baz", + } + + outputLines := strings.Split(output, "\n") + assert.ElementsMatch(t, expected, outputLines) +} + +func Test_Overlay_Variables_No_File(t *testing.T) { + env := testenv.New(t) + defer env.RemoveAll() + conf := env.Configure() + + // be quiet + wwlog.SetLogFormatter(func(int, *wwlog.LogRecord) string { return "" }) + overlayDir := path.Join(conf.Paths.SiteOverlaydir(), "test-overlay") + err := os.MkdirAll(overlayDir, 0755) + if err != nil { + t.Fatalf("could not create overlay dir: %v", err) + } + + baseCmd.SetArgs([]string{"test-overlay", "test.ww"}) + err = baseCmd.Execute() + assert.Error(t, err) +} + +func Test_Overlay_Variables_No_Overlay(t *testing.T) { + env := testenv.New(t) + defer env.RemoveAll() + env.Configure() + + // be quiet + wwlog.SetLogFormatter(func(int, *wwlog.LogRecord) string { return "" }) + + baseCmd.SetArgs([]string{"no-overlay", "test.ww"}) + err := baseCmd.Execute() + assert.Error(t, err) +} diff --git a/internal/app/wwctl/overlay/variables/root.go b/internal/app/wwctl/overlay/variables/root.go new file mode 100644 index 00000000..48ec7940 --- /dev/null +++ b/internal/app/wwctl/overlay/variables/root.go @@ -0,0 +1,39 @@ +package variables + +import ( + "github.com/spf13/cobra" + "github.com/warewulf/warewulf/internal/pkg/overlay" +) + +var ( + baseCmd = &cobra.Command{ + Use: "variables [flags] OVERLAY_NAME FILE_PATH", + Short: "Show variables for a template file in an overlay", + Long: "This command will show the variables for a given template file in a given\n" + "overlay.", + Aliases: []string{"vars", "tags"}, + Args: cobra.ExactArgs(2), + RunE: CobraRunE, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return overlay.FindOverlays(), cobra.ShellCompDirectiveNoFileComp + } else if len(args) == 1 { + ov, err := overlay.Get(args[0]) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + files, err := ov.GetFiles() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + return files, cobra.ShellCompDirectiveNoFileComp + } + return nil, cobra.ShellCompDirectiveNoFileComp + }, + } +) + +// GetCommand returns the root cobra.Command for this application. +func GetCommand() *cobra.Command { + return baseCmd +} +