From 299f1db41c637b56460b6be2f8ee6dc7d2640c99 Mon Sep 17 00:00:00 2001 From: Rafael Lopez Date: Tue, 25 Mar 2025 14:37:15 +1100 Subject: [PATCH 1/4] fix import from yaml where node does not pre-exist Check if node exists, if not add it first before attempting SetNode(). Fixes: #1842 Signed-off-by: Rafael Lopez --- internal/pkg/api/node/edit.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/pkg/api/node/edit.go b/internal/pkg/api/node/edit.go index 31f46358..d8b90b23 100644 --- a/internal/pkg/api/node/edit.go +++ b/internal/pkg/api/node/edit.go @@ -22,6 +22,12 @@ func NodeAddFromYaml(nodeList *wwapiv1.NodeYaml) (err error) { return fmt.Errorf("could not unmarshal Yaml: %w", err) } for nodeName, node := range nodeMap { + if _, err = nodeDB.GetNodeOnly(nodeName); err == node.ErrNotFound { + _, err = nodeDB.AddNode(nodeName) + if err != nil { + return fmt.Errorf("couldn't add new node: %w", err) + } + } err = nodeDB.SetNode(nodeName, *node) if err != nil { return fmt.Errorf("couldn't set node: %w", err) From ca1830d8e17e285f81d9c57861352335ff34a260 Mon Sep 17 00:00:00 2001 From: Rafael Lopez Date: Tue, 25 Mar 2025 14:55:09 +1100 Subject: [PATCH 2/4] Rename value in loop Rename value from 'node' to 'nodeData' so it doesn't coincide with the package import name. Signed-off-by: Rafael Lopez --- internal/pkg/api/node/edit.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/pkg/api/node/edit.go b/internal/pkg/api/node/edit.go index d8b90b23..915ace54 100644 --- a/internal/pkg/api/node/edit.go +++ b/internal/pkg/api/node/edit.go @@ -21,14 +21,14 @@ func NodeAddFromYaml(nodeList *wwapiv1.NodeYaml) (err error) { if err != nil { return fmt.Errorf("could not unmarshal Yaml: %w", err) } - for nodeName, node := range nodeMap { + for nodeName, nodeData := range nodeMap { if _, err = nodeDB.GetNodeOnly(nodeName); err == node.ErrNotFound { _, err = nodeDB.AddNode(nodeName) if err != nil { return fmt.Errorf("couldn't add new node: %w", err) } } - err = nodeDB.SetNode(nodeName, *node) + err = nodeDB.SetNode(nodeName, *nodeData) if err != nil { return fmt.Errorf("couldn't set node: %w", err) } From cb1b667e71156865efd64947ab39a23fa1f47c0d Mon Sep 17 00:00:00 2001 From: Rafael Lopez Date: Wed, 26 Mar 2025 10:06:58 +1100 Subject: [PATCH 3/4] update changelog and contributors --- CHANGELOG.md | 1 + CONTRIBUTORS.md | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8465944f..9b1e9f95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Display a warning during overlay build if an overlay list is empty. #1808 - Fix processing of UNDEF and UNSET during `wwctl set`. #1837 - Actually cause grub to sleep and reboot when log messages indiacte. #1838 +- Fixed issue with importing new nodes from yaml. #1842 ### Changed diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index a501faff..70ff0b5f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -44,4 +44,5 @@ * Nicholas Porter * Ian Kaufman [@iankgt40](https://github.com/iankgt40) * Daniele Colombo [@dacolombo](https://github.com/dacolombo) -* Stephen Simpson [@ssimpson89](https://github.com/ssimpson89) \ No newline at end of file +* Stephen Simpson [@ssimpson89](https://github.com/ssimpson89) +* Rafael Lopez @rafalop From 1883f2e932425e17593712d7e5a901191d7f2c5e Mon Sep 17 00:00:00 2001 From: Jonathon Anderson Date: Tue, 25 Mar 2025 20:00:48 -0600 Subject: [PATCH 4/4] Added a test for `wwctl node import` To support this, also added `wwctl node import --yes`. Also corrected the spelling of `--cvs` to `--csv`. Signed-off-by: Jonathon Anderson --- CHANGELOG.md | 2 + internal/app/wwctl/node/imprt/main.go | 8 +-- internal/app/wwctl/node/imprt/main_test.go | 81 ++++++++++++++++++++++ internal/app/wwctl/node/imprt/root.go | 15 ++-- 4 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 internal/app/wwctl/node/imprt/main_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b1e9f95..41838b55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Added `wwctl overlay import --overwrite` to overwrite existing overlay file. - wwclient uses `WW_IPADDR`, if set, to contact the Warewulf server. #1788 +- Add `wwctl node import --yes` to assume yes to confirmations. ### Fixed @@ -31,6 +32,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Update GitHub actions to build aarch64 artifacts. - Explicitly enforce the number or arguments accepted by some `wwctl` subcommands. #1717 +- Renamed `wwctl node import --cvs` to `--csv`. ### Removed diff --git a/internal/app/wwctl/node/imprt/main.go b/internal/app/wwctl/node/imprt/main.go index f83fabd1..61e5b9cc 100644 --- a/internal/app/wwctl/node/imprt/main.go +++ b/internal/app/wwctl/node/imprt/main.go @@ -27,11 +27,10 @@ func CobraRunE(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("could not read: %s", err) } - if !ImportCVS { + if !ImportCSV { err = yaml.Unmarshal(buffer, importMap) if err == nil { - yes := util.Confirm(fmt.Sprintf("Are you sure you want to modify %d nodes", len(importMap))) - if yes { + if setYes || util.Confirm(fmt.Sprintf("Are you sure you want to modify %d nodes", len(importMap))) { err = apinode.NodeAddFromYaml(&wwapiv1.NodeYaml{NodeConfMapYaml: string(buffer)}) if err != nil { return fmt.Errorf("got following problem when writing back yaml: %s", err) @@ -68,8 +67,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error { } } } - yes := util.Confirm(fmt.Sprintf("Are you sure you want to import %d nodes", len(importMap))) - if yes { + if setYes || util.Confirm(fmt.Sprintf("Are you sure you want to import %d nodes", len(importMap))) { // create second buffer an marshall nodeMap to it buffer, err = yaml.Marshal(importMap) if err != nil { diff --git a/internal/app/wwctl/node/imprt/main_test.go b/internal/app/wwctl/node/imprt/main_test.go new file mode 100644 index 00000000..8ac0ceca --- /dev/null +++ b/internal/app/wwctl/node/imprt/main_test.go @@ -0,0 +1,81 @@ +package imprt + +import ( + "bytes" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/warewulfd" +) + +func Test_Node_Import(t *testing.T) { + tests := map[string]struct { + args []string + importFile string + wantErr bool + inDB string + outDB string + }{ + "import new node": { + args: []string{"importFile"}, + importFile: ` +n1: + id: n1 + profiles: + - default + network devices: + eth0: + device: eth0 + hwaddr: c4:cb:e1:bb:dd:e9 + ipaddr: 192.168.1.10`, + wantErr: false, + inDB: ` +nodeprofiles: {} +nodes: {}`, + outDB: ` +nodeprofiles: {} +nodes: + n1: + profiles: + - default + network devices: + eth0: + device: eth0 + hwaddr: c4:cb:e1:bb:dd:e9 + ipaddr: 192.168.1.10`, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + env := testenv.New(t) + defer env.RemoveAll() + { + wd, err := os.Getwd() + assert.NoError(t, err) + defer func() { assert.NoError(t, os.Chdir(wd)) }() + } + assert.NoError(t, os.Chdir(env.GetPath("."))) + env.WriteFile("./importFile", tt.importFile) + env.WriteFile("etc/warewulf/nodes.conf", tt.inDB) + warewulfd.SetNoDaemon() + + baseCmd := GetCommand() + args := append(tt.args, "--yes") + baseCmd.SetArgs(args) + buf := new(bytes.Buffer) + baseCmd.SetOut(buf) + baseCmd.SetErr(buf) + err := baseCmd.Execute() + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + content := env.ReadFile("etc/warewulf/nodes.conf") + assert.YAMLEq(t, tt.outDB, content) + } + }) + } +} diff --git a/internal/app/wwctl/node/imprt/root.go b/internal/app/wwctl/node/imprt/root.go index 3aa9bbcc..225b0e64 100644 --- a/internal/app/wwctl/node/imprt/root.go +++ b/internal/app/wwctl/node/imprt/root.go @@ -9,18 +9,25 @@ import ( var ( baseCmd = &cobra.Command{ DisableFlagsInUseLine: true, - Use: "import [OPTIONS] NODENAME", - Short: "Import node(s) from yaml file", + Use: "import [OPTIONS] FILE", + Short: "Import node(s) from yaml FILE", Long: "This command imports all the nodes defined in a file. It will overwrite nodes with same name.", RunE: CobraRunE, Args: cobra.ExactArgs(1), Aliases: []string{"import"}, } - ImportCVS bool + ImportCSV bool + setYes bool ) func init() { - baseCmd.PersistentFlags().BoolVarP(&ImportCVS, "cvs", "c", false, "Import CVS file") + baseCmd.PersistentFlags().BoolVarP(&ImportCSV, "csv", "c", false, "Import CSV file") + baseCmd.Flags().BoolVar(&ImportCSV, "cvs", false, "Import CSV file") + baseCmd.Flags().Lookup("cvs").Hidden = true + if err := baseCmd.Flags().MarkDeprecated("cvs", "use --csv instead"); err != nil { + panic(err) + } + baseCmd.PersistentFlags().BoolVarP(&setYes, "yes", "y", false, "Set 'yes' to all questions asked") } // GetRootCommand returns the root cobra.Command for the application.