From 45539a0d1f3c8f45b3bb81694a94108ea14db8b6 Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Fri, 11 Aug 2023 10:31:29 -0600 Subject: [PATCH] Recursive handling for command line flags Every struct in a NodeConf with `lopt:"foo" set is now added as a command-line flag with the RecursiveCreateFlags call. For maps a struct with the key UNDEF is added so that it can be parsed out. As the flags for the command-line need variables which hold the values, for every map an element map[UNDEF] is added. When now calling the internal add, these map element can be filtered out and replace by the given name. (e.g., --netname) * rewrote node/profile add for recursive functions * rewrote node/profile set for recursive functions * rewrote node/profile list for recursive functions Signed-off-by: Christian Goll --- internal/app/wwctl/node/add/main.go | 39 +- internal/app/wwctl/node/add/main_test.go | 61 ++- internal/app/wwctl/node/add/root.go | 8 +- internal/app/wwctl/node/edit/main.go | 3 +- internal/app/wwctl/node/list/main.go | 2 + internal/app/wwctl/node/list/root.go | 10 +- internal/app/wwctl/node/set/main.go | 104 ++-- internal/app/wwctl/node/set/main_test.go | 348 ++++++++++++ internal/app/wwctl/node/set/root.go | 56 +- internal/app/wwctl/profile/add/main.go | 50 +- internal/app/wwctl/profile/add/main_test.go | 6 +- internal/app/wwctl/profile/add/root.go | 21 +- internal/app/wwctl/profile/edit/main.go | 4 +- internal/app/wwctl/profile/list/main.go | 5 +- internal/app/wwctl/profile/list/root.go | 6 +- internal/app/wwctl/profile/set/main.go | 92 +++- internal/app/wwctl/profile/set/root.go | 60 +- internal/pkg/api/node/list.go | 101 +--- internal/pkg/api/node/methods.go | 79 --- internal/pkg/api/node/node.go | 44 +- internal/pkg/api/profile/list.go | 96 +--- internal/pkg/api/profile/profile.go | 56 +- internal/pkg/api/routes/v1/routes.proto | 29 +- internal/pkg/api/routes/wwapiv1/routes.pb.go | 545 ++++++++++++------- internal/pkg/node/flags.go | 52 +- internal/pkg/node/list.go | 79 +++ internal/pkg/node/methods.go | 15 +- internal/pkg/node/methods_test.go | 8 +- internal/pkg/node/transformers.go | 81 ++- 29 files changed, 1346 insertions(+), 714 deletions(-) create mode 100644 internal/app/wwctl/node/set/main_test.go delete mode 100644 internal/pkg/api/node/methods.go create mode 100644 internal/pkg/node/list.go diff --git a/internal/app/wwctl/node/add/main.go b/internal/app/wwctl/node/add/main.go index 694e3e13..125c49e6 100644 --- a/internal/app/wwctl/node/add/main.go +++ b/internal/app/wwctl/node/add/main.go @@ -1,10 +1,14 @@ package add import ( + "fmt" + "strings" + "gopkg.in/yaml.v2" apinode "github.com/hpcng/warewulf/internal/pkg/api/node" "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" + "github.com/hpcng/warewulf/internal/pkg/node" "github.com/hpcng/warewulf/internal/pkg/wwlog" "github.com/spf13/cobra" ) @@ -22,17 +26,36 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) error { return err } } - // remove the default network as all network values are assigned + // remove the UNDEF network as all network values are assigned // to this network - if _, ok := vars.nodeConf.NetDevs["default"]; ok && vars.netName != "" { - netDev := *vars.nodeConf.NetDevs["default"] + if !node.ObjectIsEmpty(vars.nodeConf.NetDevs["UNDEF"]) { + netDev := *vars.nodeConf.NetDevs["UNDEF"] vars.nodeConf.NetDevs[vars.netName] = &netDev - delete(vars.nodeConf.NetDevs, "default") - } else { - if vars.nodeConf.NetDevs["default"].Empty() { - delete(vars.nodeConf.NetDevs, "default") - } } + delete(vars.nodeConf.NetDevs, "UNDEF") + if vars.fsName != "" { + if !strings.HasPrefix(vars.fsName, "/dev") { + if vars.fsName == vars.partName { + vars.fsName = "/dev/disk/by-partlabel/" + vars.partName + } else { + return fmt.Errorf("filesystems need to have a underlying blockdev") + } + } + fs := *vars.nodeConf.FileSystems["UNDEF"] + vars.nodeConf.FileSystems[vars.fsName] = &fs + } + delete(vars.nodeConf.FileSystems, "UNDEF") + if vars.diskName != "" && vars.partName != "" { + prt := *vars.nodeConf.Disks["UNDEF"].Partitions["UNDEF"] + vars.nodeConf.Disks["UNDEF"].Partitions[vars.partName] = &prt + delete(vars.nodeConf.Disks["UNDEF"].Partitions, "UNDEF") + dsk := *vars.nodeConf.Disks["UNDEF"] + vars.nodeConf.Disks[vars.diskName] = &dsk + } + if (vars.diskName != "") != (vars.partName != "") { + return fmt.Errorf("partition and disk must be specified") + } + delete(vars.nodeConf.Disks, "UNDEF") buffer, err := yaml.Marshal(vars.nodeConf) if err != nil { wwlog.Error("Can't marshall nodeInfo", err) diff --git a/internal/app/wwctl/node/add/main_test.go b/internal/app/wwctl/node/add/main_test.go index 71513ea1..8a243724 100644 --- a/internal/app/wwctl/node/add/main_test.go +++ b/internal/app/wwctl/node/add/main_test.go @@ -5,8 +5,8 @@ import ( "os" "testing" - "github.com/hpcng/warewulf/internal/pkg/node" warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" + "github.com/hpcng/warewulf/internal/pkg/node" "github.com/hpcng/warewulf/internal/pkg/warewulfd" "github.com/stretchr/testify/assert" ) @@ -234,6 +234,65 @@ nodes: network devices: foo: ipaddr: 10.10.0.3 +`}, + {name: "one node with filesystem", + args: []string{"--fsname=/dev/vda1", "--fspath=/var", "n01"}, + wantErr: false, + stdout: "", + outDb: `WW_INTERNAL: 43 +nodeprofiles: {} +nodes: + n01: + profiles: + - default + filesystems: + /dev/vda1: + path: /var +`}, + {name: "one node with filesystem", + args: []string{"--fsname=dev/vda1", "--fspath=/var", "n01"}, + wantErr: true, + stdout: "", + outDb: `WW_INTERNAL: 43 +nodeprofiles: {} +nodes: {} +`}, + {name: "one node with filesystem and partition ", + args: []string{"--fsname=var", "--fspath=/var", "--partname=var", "--diskname=/dev/vda", "n01"}, + wantErr: false, + stdout: "", + outDb: `WW_INTERNAL: 43 +nodeprofiles: {} +nodes: + n01: + profiles: + - default + disks: + /dev/vda: + partitions: + var: {} + filesystems: + /dev/disk/by-partlabel/var: + path: /var +`}, + {name: "one node with filesystem with btrfs and partition ", + args: []string{"--fsname=var", "--fspath=/var", "--fsformat=btrfs", "--partname=var", "--diskname=/dev/vda", "n01"}, + wantErr: false, + stdout: "", + outDb: `WW_INTERNAL: 43 +nodeprofiles: {} +nodes: + n01: + profiles: + - default + disks: + /dev/vda: + partitions: + var: {} + filesystems: + /dev/disk/by-partlabel/var: + format: btrfs + path: /var `}, } conf_yml := `WW_INTERNAL: 0` diff --git a/internal/app/wwctl/node/add/root.go b/internal/app/wwctl/node/add/root.go index a74a4cf4..caab5cee 100644 --- a/internal/app/wwctl/node/add/root.go +++ b/internal/app/wwctl/node/add/root.go @@ -13,6 +13,9 @@ import ( // Holds the variables which are needed in CobraRunE type variables struct { netName string + fsName string + partName string + diskName string nodeConf node.NodeConf converters []func() error } @@ -30,7 +33,10 @@ func GetCommand() *cobra.Command { Args: cobra.MinimumNArgs(1), } vars.converters = vars.nodeConf.CreateFlags(baseCmd, []string{"tagdel", "nettagdel", "ipmitagdel"}) - baseCmd.PersistentFlags().StringVar(&vars.netName, "netname", "", "Set network name for network options") + baseCmd.PersistentFlags().StringVar(&vars.netName, "netname", "default", "Set network name for network options") + baseCmd.PersistentFlags().StringVar(&vars.fsName, "fsname", "", "set the file system name which must match a partition name") + baseCmd.PersistentFlags().StringVar(&vars.partName, "partname", "", "set the partition name so it can be used by a file system") + baseCmd.PersistentFlags().StringVar(&vars.diskName, "diskname", "", "set disk device name for the partition") // register the command line completions if err := baseCmd.RegisterFlagCompletionFunc("container", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { list, _ := container.ListSources() diff --git a/internal/app/wwctl/node/edit/main.go b/internal/app/wwctl/node/edit/main.go index 5ac5a202..adde59aa 100644 --- a/internal/app/wwctl/node/edit/main.go +++ b/internal/app/wwctl/node/edit/main.go @@ -45,8 +45,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error { wwlog.Error("Could not create temp file:%s \n", err) } defer os.Remove(file.Name()) - nodeConf := node.NewConf() - yamlTemplate := nodeConf.UnmarshalConf([]string{"tagsdel", "default", "profiles"}) + yamlTemplate := node.UnmarshalConf(node.NodeConf{}, []string{"tagsdel", "default", "profiles"}) for { _ = file.Truncate(0) _, _ = file.Seek(0, 0) diff --git a/internal/app/wwctl/node/list/main.go b/internal/app/wwctl/node/list/main.go index 6e748f8d..6e44fe92 100644 --- a/internal/app/wwctl/node/list/main.go +++ b/internal/app/wwctl/node/list/main.go @@ -26,6 +26,8 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err req.Type = wwapiv1.GetNodeList_Network } else if vars.showLong { req.Type = wwapiv1.GetNodeList_Long + } else if vars.showFullAll { + req.Type = wwapiv1.GetNodeList_FullAll } nodeInfo, err := apinode.NodeList(&req) if len(nodeInfo.Output) > 0 { diff --git a/internal/app/wwctl/node/list/root.go b/internal/app/wwctl/node/list/root.go index 37acc641..a3b226e7 100644 --- a/internal/app/wwctl/node/list/root.go +++ b/internal/app/wwctl/node/list/root.go @@ -6,10 +6,11 @@ import ( ) type variables struct { - showNet bool - showIpmi bool - showAll bool - showLong bool + showNet bool + showIpmi bool + showAll bool + showLong bool + showFullAll bool } func GetCommand() *cobra.Command { @@ -39,6 +40,7 @@ func GetCommand() *cobra.Command { baseCmd.PersistentFlags().BoolVarP(&vars.showNet, "net", "n", false, "Show node network configurations") baseCmd.PersistentFlags().BoolVarP(&vars.showIpmi, "ipmi", "i", false, "Show node IPMI configurations") baseCmd.PersistentFlags().BoolVarP(&vars.showAll, "all", "a", false, "Show all node configurations") + baseCmd.PersistentFlags().BoolVarP(&vars.showFullAll, "fullall", "A", false, "Show all node configurations inclusive empty entries") baseCmd.PersistentFlags().BoolVarP(&vars.showLong, "long", "l", false, "Show long or wide format") return baseCmd diff --git a/internal/app/wwctl/node/set/main.go b/internal/app/wwctl/node/set/main.go index 4bc44f21..00e08135 100644 --- a/internal/app/wwctl/node/set/main.go +++ b/internal/app/wwctl/node/set/main.go @@ -3,54 +3,86 @@ package set import ( "fmt" "os" + "strings" apinode "github.com/hpcng/warewulf/internal/pkg/api/node" "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" "github.com/hpcng/warewulf/internal/pkg/api/util" + "github.com/hpcng/warewulf/internal/pkg/node" "github.com/hpcng/warewulf/internal/pkg/wwlog" "github.com/spf13/cobra" "gopkg.in/yaml.v2" ) -func CobraRunE(cmd *cobra.Command, args []string) (err error) { - // run converters for different types - for _, c := range Converters { - if err := c(); err != nil { - return err +func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) { + return func(cmd *cobra.Command, args []string) error { + // run converters for different types + for _, c := range vars.converters { + if err := c(); err != nil { + return err + } } - } - // remove the default network as the all network values are assigned - // to this network - if NetName != "default" { - NodeConf.NetDevs[NetName] = NodeConf.NetDevs["default"] - delete(NodeConf.NetDevs, "default") - } - buffer, err := yaml.Marshal(NodeConf) - if err != nil { - wwlog.Error("Can't marshall nodeInfo", err) - os.Exit(1) - } - set := wwapiv1.NodeSetParameter{ - NodeConfYaml: string(buffer[:]), - - NetdevDelete: SetNetDevDel, - AllNodes: SetNodeAll, - Force: SetForce, - NodeNames: args, - } - - if !SetYes { - var nodeCount uint - // The checks run twice in the prompt case. - // Avoiding putting in a blocking prompt in an API. - _, nodeCount, err = apinode.NodeSetParameterCheck(&set, false) + // remove the default network as the all network values are assigned + // to this network + if !node.ObjectIsEmpty(vars.nodeConf.NetDevs["UNDEF"]) { + netDev := *vars.nodeConf.NetDevs["UNDEF"] + vars.nodeConf.NetDevs[vars.netName] = &netDev + } + delete(vars.nodeConf.NetDevs, "UNDEF") + if vars.fsName != "" { + if !strings.HasPrefix(vars.fsName, "/dev") { + if vars.fsName == vars.partName { + vars.fsName = "/dev/disk/by-partlabel/" + vars.partName + } else { + return fmt.Errorf("filesystems need to have a underlying blockdev") + } + } + fs := *vars.nodeConf.FileSystems["UNDEF"] + vars.nodeConf.FileSystems[vars.fsName] = &fs + } + delete(vars.nodeConf.FileSystems, "UNDEF") + if vars.diskName != "" && vars.partName != "" { + prt := *vars.nodeConf.Disks["UNDEF"].Partitions["UNDEF"] + vars.nodeConf.Disks["UNDEF"].Partitions[vars.partName] = &prt + delete(vars.nodeConf.Disks["UNDEF"].Partitions, "UNDEF") + dsk := *vars.nodeConf.Disks["UNDEF"] + vars.nodeConf.Disks[vars.diskName] = &dsk + } + if (vars.diskName != "") != (vars.partName != "") { + return fmt.Errorf("partition and disk must be specified") + } + delete(vars.nodeConf.Disks, "UNDEF") + buffer, err := yaml.Marshal(vars.nodeConf) if err != nil { - return + wwlog.Error("Can't marshall nodeInfo", err) + os.Exit(1) } - yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to modify %d nodes(s)", nodeCount)) - if !yes { - return + wwlog.Debug("sending following values: %s", string(buffer)) + set := wwapiv1.NodeSetParameter{ + NodeConfYaml: string(buffer[:]), + + NetdevDelete: vars.setNetDevDel, + PartitionDelete: vars.setPartDel, + DiskDelete: vars.setDiskDel, + FilesystemDelete: vars.setFsDel, + AllNodes: vars.setNodeAll, + Force: vars.setForce, + NodeNames: args, } + + if !vars.setYes { + var nodeCount uint + // The checks run twice in the prompt case. + // Avoiding putting in a blocking prompt in an API. + _, nodeCount, err = apinode.NodeSetParameterCheck(&set, false) + if err != nil { + return nil + } + yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to modify %d nodes(s)", nodeCount)) + if !yes { + return nil + } + } + return apinode.NodeSet(&set) } - return apinode.NodeSet(&set) } diff --git a/internal/app/wwctl/node/set/main_test.go b/internal/app/wwctl/node/set/main_test.go new file mode 100644 index 00000000..4feda0d3 --- /dev/null +++ b/internal/app/wwctl/node/set/main_test.go @@ -0,0 +1,348 @@ +package set + +import ( + "bytes" + "os" + "testing" + + warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/hpcng/warewulf/internal/pkg/warewulfd" + "github.com/stretchr/testify/assert" +) + +func Test_Add(t *testing.T) { + tests := []struct { + name string + args []string + wantErr bool + stdout string + chkout bool + outDb string + inDB string + }{ + {name: "single node change profile", + args: []string{"--profile=foo", "n01"}, + wantErr: false, + stdout: "", + inDB: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + profiles: + - default`, + outDb: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + profiles: + - foo +`}, + {name: "multiple nodes change profile", + args: []string{"--profile=foo", "n0[1-2]"}, + wantErr: false, + stdout: "", + inDB: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + profiles: + - default + n02: + profiles: + - default`, + outDb: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + profiles: + - foo + n02: + profiles: + - foo +`}, + {name: "single node set ipmitag", + args: []string{"--ipmitagadd", "foo=baar", "n01"}, + wantErr: false, + stdout: "", + inDB: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + profiles: + - default`, + outDb: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + ipmi: + tags: + foo: baar + profiles: + - default +`}, + {name: "single node delete tag", + args: []string{"--tagdel", "tag1", "n01"}, + wantErr: false, + stdout: "", + inDB: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + profiles: + - default + tags: + tag1: value1 + tag2: value2`, + outDb: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + profiles: + - default + tags: + tag2: value2 +`}, + {name: "single node set fs,part and disk", + args: []string{"--fsname=var", "--fspath=/var", "--fsformat=btrfs", "--partname=var", "--diskname=/dev/vda", "n01"}, + wantErr: false, + stdout: "", + inDB: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + profiles: + - default +`, + outDb: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + profiles: + - default + disks: + /dev/vda: + partitions: + var: {} + filesystems: + /dev/disk/by-partlabel/var: + format: btrfs + path: /var +`}, + {name: "single delete not existing fs", + args: []string{"--fsdel=var", "n01"}, + wantErr: true, + stdout: "", + inDB: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + profiles: + - default + disks: + /dev/vda: + partitions: + var: {} + path: /var + filesystems: + /dev/disk/by-partlabel/var: + format: btrfs + path: /var +`, + outDb: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + profiles: + - default + disks: + /dev/vda: + partitions: + var: {} + filesystems: + /dev/disk/by-partlabel/var: + format: btrfs + path: /var +`}, + {name: "single node delete existing fs", + args: []string{"--fsdel=/dev/disk/by-partlabel/var", "n01"}, + wantErr: false, + stdout: "", + inDB: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + profiles: + - default + disks: + /dev/vda: + partitions: + var: {} + path: /var + filesystems: + /dev/disk/by-partlabel/var: + format: btrfs + path: /var +`, + outDb: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + profiles: + - default + disks: + /dev/vda: + partitions: + var: {} +`}, + {name: "single node delete existing partition", + args: []string{"--partdel=var", "n01"}, + wantErr: false, + stdout: "", + inDB: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + profiles: + - default + disks: + /dev/vda: + partitions: + var: {} + path: /var + filesystems: + /dev/disk/by-partlabel/var: + format: btrfs + path: /var +`, + outDb: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + profiles: + - default + disks: + /dev/vda: {} + filesystems: + /dev/disk/by-partlabel/var: + format: btrfs + path: /var +`}, + {name: "single node delete existing disk", + args: []string{"--diskdel=/dev/vda", "n01"}, + wantErr: false, + stdout: "", + inDB: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + profiles: + - default + disks: + /dev/vda: + partitions: + var: {} + path: /var + filesystems: + /dev/disk/by-partlabel/var: + format: btrfs + path: /var +`, + outDb: `WW_INTERNAL: 43 +nodeprofiles: + default: + comment: testit +nodes: + n01: + profiles: + - default + filesystems: + /dev/disk/by-partlabel/var: + format: btrfs + path: /var +`}, + } + conf_yml := `WW_INTERNAL: 0` + tempWarewulfConf, warewulfConfErr := os.CreateTemp("", "warewulf.conf-") + assert.NoError(t, warewulfConfErr) + defer os.Remove(tempWarewulfConf.Name()) + _, warewulfConfErr = tempWarewulfConf.Write([]byte(conf_yml)) + assert.NoError(t, warewulfConfErr) + assert.NoError(t, tempWarewulfConf.Sync()) + assert.NoError(t, warewulfconf.New().Read(tempWarewulfConf.Name())) + + tempNodeConf, nodesConfErr := os.CreateTemp("", "nodes.conf-") + assert.NoError(t, nodesConfErr) + defer os.Remove(tempNodeConf.Name()) + node.ConfigFile = tempNodeConf.Name() + warewulfd.SetNoDaemon() + for _, tt := range tests { + var err error + _, err = tempNodeConf.Seek(0, 0) + assert.NoError(t, err) + assert.NoError(t, tempNodeConf.Truncate(0)) + _, err = tempNodeConf.Write([]byte(tt.inDB)) + assert.NoError(t, err) + assert.NoError(t, tempNodeConf.Sync()) + t.Logf("Running test: %s\n", tt.name) + t.Run(tt.name, func(t *testing.T) { + baseCmd := GetCommand() + tt.args = append(tt.args, "--yes") + baseCmd.SetArgs(tt.args) + buf := new(bytes.Buffer) + baseCmd.SetOut(buf) + baseCmd.SetErr(buf) + err = baseCmd.Execute() + if (err != nil) != tt.wantErr { + t.Errorf("Got unwanted error: %s", err) + t.FailNow() + } + config, configErr := node.New() + assert.NoError(t, configErr) + dumpBytes, _ := config.Dump() + dump := string(dumpBytes) + if dump != tt.outDb { + t.Errorf("DB dump is wrong, got:'%s'\nwant:'%s'", dump, tt.outDb) + t.FailNow() + } + if tt.chkout && buf.String() != tt.stdout { + t.Errorf("Got wrong output, got:'%s'\nwant:'%s'", buf.String(), tt.stdout) + t.FailNow() + } + }) + } +} diff --git a/internal/app/wwctl/node/set/root.go b/internal/app/wwctl/node/set/root.go index ac4c1f14..5a278518 100644 --- a/internal/app/wwctl/node/set/root.go +++ b/internal/app/wwctl/node/set/root.go @@ -10,14 +10,32 @@ import ( "github.com/spf13/cobra" ) -var ( - baseCmd = &cobra.Command{ +type variables struct { + setNetDevDel string + setDiskDel string + setPartDel string + setFsDel string + netName string + partName string + diskName string + fsName string + setNodeAll bool + setYes bool + setForce bool + nodeConf node.NodeConf + converters []func() error +} + +func GetCommand() *cobra.Command { + vars := variables{} + vars.nodeConf = node.NewConf() + baseCmd := &cobra.Command{ DisableFlagsInUseLine: true, Use: "set [OPTIONS] PATTERN [PATTERN ...]", Short: "Configure node properties", Long: "This command sets configuration properties for nodes matching PATTERN.\n\nNote: use the string 'UNSET' to remove a configuration", Args: cobra.MinimumNArgs(0), - RunE: CobraRunE, + RunE: CobraRunE(&vars), ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 0 { return nil, cobra.ShellCompDirectiveNoFileComp @@ -32,23 +50,19 @@ var ( return node_names, cobra.ShellCompDirectiveNoFileComp }, } - SetNetDevDel string - NetName string - SetNodeAll bool - SetYes bool - SetForce bool - NodeConf node.NodeConf - Converters []func() error -) -func init() { - NodeConf = node.NewConf() - Converters = NodeConf.CreateFlags(baseCmd, []string{}) - baseCmd.PersistentFlags().StringVarP(&SetNetDevDel, "netdel", "D", "", "Delete the node's network device") - baseCmd.PersistentFlags().StringVar(&NetName, "netname", "default", "Set network name for network options") - baseCmd.PersistentFlags().BoolVarP(&SetNodeAll, "all", "a", false, "Set all nodes") - baseCmd.PersistentFlags().BoolVarP(&SetYes, "yes", "y", false, "Set 'yes' to all questions asked") - baseCmd.PersistentFlags().BoolVarP(&SetForce, "force", "f", false, "Force configuration (even on error)") + vars.converters = vars.nodeConf.CreateFlags(baseCmd, []string{}) + baseCmd.PersistentFlags().StringVarP(&vars.setNetDevDel, "netdel", "D", "", "Delete the node's network device") + baseCmd.PersistentFlags().StringVar(&vars.netName, "netname", "default", "Set network name for network options") + baseCmd.PersistentFlags().BoolVarP(&vars.setNodeAll, "all", "a", false, "Set all nodes") + baseCmd.PersistentFlags().StringVar(&vars.fsName, "fsname", "", "set the file system name which must match a partition name") + baseCmd.PersistentFlags().StringVar(&vars.partName, "partname", "", "set the partition name so it can be used by a file system") + baseCmd.PersistentFlags().StringVar(&vars.diskName, "diskname", "", "set disk device name for the partition") + baseCmd.PersistentFlags().StringVar(&vars.setDiskDel, "diskdel", "", "delete the disk from the configuration") + baseCmd.PersistentFlags().StringVar(&vars.setPartDel, "partdel", "", "delete the partition from the configuration") + baseCmd.PersistentFlags().StringVar(&vars.setFsDel, "fsdel", "", "delete the partition from the configuration") + baseCmd.PersistentFlags().BoolVarP(&vars.setYes, "yes", "y", false, "Set 'yes' to all questions asked") + baseCmd.PersistentFlags().BoolVarP(&vars.setForce, "force", "f", false, "Force configuration (even on error)") // register the command line completions if err := baseCmd.RegisterFlagCompletionFunc("container", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { list, _ := container.ListSources() @@ -86,9 +100,5 @@ func init() { log.Println(err) } -} - -// GetRootCommand returns the root cobra.Command for the application. -func GetCommand() *cobra.Command { return baseCmd } diff --git a/internal/app/wwctl/profile/add/main.go b/internal/app/wwctl/profile/add/main.go index 40ad46a4..8c04ece8 100644 --- a/internal/app/wwctl/profile/add/main.go +++ b/internal/app/wwctl/profile/add/main.go @@ -3,8 +3,10 @@ package add import ( "fmt" "os" + "strings" apiprofile "github.com/hpcng/warewulf/internal/pkg/api/profile" + "github.com/hpcng/warewulf/internal/pkg/node" "github.com/hpcng/warewulf/internal/pkg/wwlog" "gopkg.in/yaml.v2" @@ -16,7 +18,7 @@ import ( func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) { return func(cmd *cobra.Command, args []string) (err error) { // run converters for different types - for _, c := range Converters { + for _, c := range vars.Converters { if err := c(); err != nil { return err } @@ -24,25 +26,55 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err // remove the default network as the all network values are assigned // to this network if vars.netName != "" { - netDev := *vars.profileConf.NetDevs["default"] + netDev := *vars.profileConf.NetDevs["UNDEF"] vars.profileConf.NetDevs[vars.netName] = &netDev - delete(vars.profileConf.NetDevs, "default") + delete(vars.profileConf.NetDevs, "UNDEF") } + // remove the UNDEF network as all network values are assigned + // to this network + if !node.ObjectIsEmpty(vars.profileConf.NetDevs["UNDEF"]) { + netDev := *vars.profileConf.NetDevs["UNDEF"] + vars.profileConf.NetDevs[vars.netName] = &netDev + } + delete(vars.profileConf.NetDevs, "UNDEF") + if vars.fsName != "" { + if !strings.HasPrefix(vars.fsName, "/dev") { + if vars.fsName == vars.partName { + vars.fsName = "/dev/disk/by-partlabel/" + vars.partName + } else { + return fmt.Errorf("filesystems need to have a underlying blockdev") + } + } + fs := *vars.profileConf.FileSystems["UNDEF"] + vars.profileConf.FileSystems[vars.fsName] = &fs + } + delete(vars.profileConf.FileSystems, "UNDEF") + if vars.diskName != "" && vars.partName != "" { + prt := *vars.profileConf.Disks["UNDEF"].Partitions["UNDEF"] + vars.profileConf.Disks["UNDEF"].Partitions[vars.partName] = &prt + delete(vars.profileConf.Disks["UNDEF"].Partitions, "UNDEF") + dsk := *vars.profileConf.Disks["UNDEF"] + vars.profileConf.Disks[vars.diskName] = &dsk + } + if (vars.diskName != "") != (vars.partName != "") { + return fmt.Errorf("partition and disk must be specified") + } + delete(vars.profileConf.Disks, "UNDEF") buffer, err := yaml.Marshal(vars.profileConf) if err != nil { wwlog.Error("Cant marshall nodeInfo", err) os.Exit(1) } - set := wwapiv1.NodeSetParameter{ + set := wwapiv1.ProfileSetParameter{ NodeConfYaml: string(buffer[:]), - NetdevDelete: SetNetDevDel, - AllNodes: SetNodeAll, - Force: SetForce, - NodeNames: args, + NetdevDelete: vars.SetNetDevDel, + AllProfiles: vars.SetNodeAll, + Force: vars.SetForce, + ProfileNames: args, } - if !SetYes { + if !vars.SetYes { // The checks run twice in the prompt case. // Avoiding putting in a blocking prompt in an API. _, _, err = apiprofile.ProfileSetParameterCheck(&set, false) diff --git a/internal/app/wwctl/profile/add/main_test.go b/internal/app/wwctl/profile/add/main_test.go index f1a084e7..bbc49d0d 100644 --- a/internal/app/wwctl/profile/add/main_test.go +++ b/internal/app/wwctl/profile/add/main_test.go @@ -5,8 +5,8 @@ import ( "os" "testing" - "github.com/hpcng/warewulf/internal/pkg/node" warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" + "github.com/hpcng/warewulf/internal/pkg/node" "github.com/hpcng/warewulf/internal/pkg/warewulfd" "github.com/stretchr/testify/assert" ) @@ -26,9 +26,7 @@ func Test_Add(t *testing.T) { stdout: "", outDb: `WW_INTERNAL: 43 nodeprofiles: - p01: - network devices: - default: {} + p01: {} nodes: {} `, }, diff --git a/internal/app/wwctl/profile/add/root.go b/internal/app/wwctl/profile/add/root.go index 3a0d4336..42db93d0 100644 --- a/internal/app/wwctl/profile/add/root.go +++ b/internal/app/wwctl/profile/add/root.go @@ -11,19 +11,18 @@ import ( ) type variables struct { - netName string - profileConf node.NodeConf -} - -var ( + netName string + profileConf node.NodeConf SetNetDevDel string SetNodeAll bool SetYes bool SetForce bool - NetName string + fsName string + partName string + diskName string ProfileConf node.NodeConf Converters []func() error -) +} // GetRootCommand returns the root cobra.Command for the application. func GetCommand() *cobra.Command { @@ -37,9 +36,12 @@ func GetCommand() *cobra.Command { RunE: CobraRunE(&vars), Args: cobra.ExactArgs(1), } - Converters = vars.profileConf.CreateFlags(baseCmd, + vars.Converters = vars.profileConf.CreateFlags(baseCmd, []string{"ipaddr", "ipaddr6", "ipmiaddr", "profile"}) baseCmd.PersistentFlags().StringVar(&vars.netName, "netname", "", "Set network name for network options") + baseCmd.PersistentFlags().StringVar(&vars.fsName, "fsname", "", "set the file system name which must match a partition name") + baseCmd.PersistentFlags().StringVar(&vars.partName, "partname", "", "set the partition name so it can be used by a file system") + baseCmd.PersistentFlags().StringVar(&vars.diskName, "diskname", "", "set disk device name for the partition") // register the command line completions if err := baseCmd.RegisterFlagCompletionFunc("container", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { list, _ := container.ListSources() @@ -65,7 +67,6 @@ func GetCommand() *cobra.Command { }); err != nil { log.Println(err) } - baseCmd.PersistentFlags().BoolVarP(&SetYes, "yes", "y", false, "Set 'yes' to all questions asked") - + baseCmd.PersistentFlags().BoolVarP(&vars.SetYes, "yes", "y", false, "Set 'yes' to all questions asked") return baseCmd } diff --git a/internal/app/wwctl/profile/edit/main.go b/internal/app/wwctl/profile/edit/main.go index cbe018b3..a91ca3d9 100644 --- a/internal/app/wwctl/profile/edit/main.go +++ b/internal/app/wwctl/profile/edit/main.go @@ -45,8 +45,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error { wwlog.Error("Could not create temp file:%s \n", err) } defer os.Remove(file.Name()) - nodeConf := node.NewConf() - yamlTemplate := nodeConf.UnmarshalConf([]string{"tagsdel"}) + yamlTemplate := node.UnmarshalConf(node.NodeConf{}, []string{"tagsdel"}) for { _ = file.Truncate(0) _, _ = file.Seek(0, 0) @@ -112,7 +111,6 @@ func CobraRunE(cmd *cobra.Command, args []string) error { if yes { err = apiprofile.ProfileDelete(&wwapiv1.NodeDeleteParameter{NodeNames: pList, Force: true}) - if err != nil { wwlog.Verbose("Problem deleting nodes before modification %s") } diff --git a/internal/app/wwctl/profile/list/main.go b/internal/app/wwctl/profile/list/main.go index de4f55ac..57b8866c 100644 --- a/internal/app/wwctl/profile/list/main.go +++ b/internal/app/wwctl/profile/list/main.go @@ -16,8 +16,9 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err args = strings.FieldsFunc(args[0], func(r rune) bool { return r == ',' }) } req := wwapiv1.GetProfileList{ - ShowAll: vars.showAll, - Profiles: args, + ShowAll: vars.showAll, + ShowFullAll: vars.showFullAll, + Profiles: args, } profileInfo, err := apiprofile.ProfileList(&req) if err != nil { diff --git a/internal/app/wwctl/profile/list/root.go b/internal/app/wwctl/profile/list/root.go index 14689314..c686e525 100644 --- a/internal/app/wwctl/profile/list/root.go +++ b/internal/app/wwctl/profile/list/root.go @@ -3,7 +3,8 @@ package list import "github.com/spf13/cobra" type variables struct { - showAll bool + showAll bool + showFullAll bool } // GetRootCommand returns the root cobra.Command for the application. @@ -17,7 +18,8 @@ func GetCommand() *cobra.Command { RunE: CobraRunE(&vars), Aliases: []string{"ls"}, } - baseCmd.PersistentFlags().BoolVarP(&vars.showAll, "all", "a", false, "Show all node configurations") + baseCmd.PersistentFlags().BoolVarP(&vars.showAll, "all", "a", false, "Show all profile configurations") + baseCmd.PersistentFlags().BoolVarP(&vars.showFullAll, "fullall", "A", false, "Show all profile configurations inclusive empty entries") return baseCmd } diff --git a/internal/app/wwctl/profile/set/main.go b/internal/app/wwctl/profile/set/main.go index efa20fd2..fe553cbc 100644 --- a/internal/app/wwctl/profile/set/main.go +++ b/internal/app/wwctl/profile/set/main.go @@ -3,48 +3,80 @@ package set import ( "fmt" "os" + "strings" apiprofile "github.com/hpcng/warewulf/internal/pkg/api/profile" "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" "github.com/hpcng/warewulf/internal/pkg/api/util" + "github.com/hpcng/warewulf/internal/pkg/node" "github.com/hpcng/warewulf/internal/pkg/wwlog" "github.com/spf13/cobra" "gopkg.in/yaml.v2" ) -func CobraRunE(cmd *cobra.Command, args []string) (err error) { +func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) { + return func(cmd *cobra.Command, args []string) error { - // remove the default network as the all network values are assigned - // to this network - if NetName != "default" { - ProfileConf.NetDevs[NetName] = ProfileConf.NetDevs["default"] - delete(ProfileConf.NetDevs, "default") - } - buffer, err := yaml.Marshal(ProfileConf) - if err != nil { - wwlog.Error("Cant marshall nodeInfo", err) - os.Exit(1) - } - set := wwapiv1.NodeSetParameter{ - NodeConfYaml: string(buffer[:]), - NetdevDelete: SetNetDevDel, - AllNodes: SetNodeAll, - Force: SetForce, - NodeNames: args, - } - - if !SetYes { - var profileCount uint - // The checks run twice in the prompt case. - // Avoiding putting in a blocking prompt in an API. - _, profileCount, err = apiprofile.ProfileSetParameterCheck(&set, false) + // remove the default network as the all network values are assigned + // to this network + if !node.ObjectIsEmpty(vars.profileConf.NetDevs["UNDEF"]) { + netDev := *vars.profileConf.NetDevs["UNDEF"] + vars.profileConf.NetDevs[vars.netName] = &netDev + } + delete(vars.profileConf.NetDevs, "UNDEF") + if vars.fsName != "" { + if !strings.HasPrefix(vars.fsName, "/dev") { + if vars.fsName == vars.partName { + vars.fsName = "/dev/disk/by-partlabel/" + vars.partName + } else { + return fmt.Errorf("filesystems need to have a underlying blockdev") + } + } + fs := *vars.profileConf.FileSystems["UNDEF"] + vars.profileConf.FileSystems[vars.fsName] = &fs + } + delete(vars.profileConf.FileSystems, "UNDEF") + if vars.diskName != "" && vars.partName != "" { + prt := *vars.profileConf.Disks["UNDEF"].Partitions["UNDEF"] + vars.profileConf.Disks["UNDEF"].Partitions[vars.partName] = &prt + delete(vars.profileConf.Disks["UNDEF"].Partitions, "UNDEF") + dsk := *vars.profileConf.Disks["UNDEF"] + vars.profileConf.Disks[vars.diskName] = &dsk + } + if (vars.diskName != "") != (vars.partName != "") { + return fmt.Errorf("partition and disk must be specified") + } + delete(vars.profileConf.Disks, "UNDEF") + buffer, err := yaml.Marshal(vars.profileConf) if err != nil { - return + wwlog.Error("Cant marshall nodeInfo", err) + os.Exit(1) } - yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to modify %d profile(s)", profileCount)) - if !yes { - return + wwlog.Debug("sending following values: %s", string(buffer)) + set := wwapiv1.ProfileSetParameter{ + NodeConfYaml: string(buffer[:]), + NetdevDelete: vars.setNetDevDel, + PartitionDelete: vars.setPartDel, + DiskDelete: vars.setDiskDel, + FilesystemDelete: vars.setFsDel, + AllProfiles: vars.setNodeAll, + Force: vars.setForce, + ProfileNames: args, } + + if !vars.setYes { + var profileCount uint + // The checks run twice in the prompt case. + // Avoiding putting in a blocking prompt in an API. + _, profileCount, err = apiprofile.ProfileSetParameterCheck(&set, false) + if err != nil { + return err + } + yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to modify %d profile(s)", profileCount)) + if !yes { + return err + } + } + return apiprofile.ProfileSet(&set) } - return apiprofile.ProfileSet(&set) } diff --git a/internal/app/wwctl/profile/set/root.go b/internal/app/wwctl/profile/set/root.go index 0c1fd56b..534e4d85 100644 --- a/internal/app/wwctl/profile/set/root.go +++ b/internal/app/wwctl/profile/set/root.go @@ -10,14 +10,33 @@ import ( "github.com/spf13/cobra" ) -var ( - baseCmd = &cobra.Command{ +type variables struct { + setNetDevDel string + setDiskDel string + setPartDel string + setFsDel string + setNodeAll bool + setYes bool + setForce bool + netName string + partName string + diskName string + fsName string + profileConf node.NodeConf + converters []func() error +} + +func GetCommand() *cobra.Command { + vars := variables{} + vars.profileConf = node.NewConf() + + baseCmd := &cobra.Command{ Use: "set [OPTIONS] [PROFILE ...]", Short: "Configure node profile properties", Long: "This command sets configuration properties for the node PROFILE(s).\n\n" + "Note: use the string 'UNSET' to remove a configuration", Args: cobra.MinimumNArgs(0), - RunE: CobraRunE, + RunE: CobraRunE(&vars), ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 0 { return nil, cobra.ShellCompDirectiveNoFileComp @@ -32,24 +51,20 @@ var ( return p_names, cobra.ShellCompDirectiveNoFileComp }, } - SetNetDevDel string - SetNodeAll bool - SetYes bool - SetForce bool - NetName string - ProfileConf node.NodeConf - Converters []func() error -) - -func init() { - ProfileConf = node.NewConf() - Converters = ProfileConf.CreateFlags(baseCmd, + vars.profileConf = node.NewConf() + vars.converters = vars.profileConf.CreateFlags(baseCmd, []string{"ipaddr", "ipaddr6", "ipmiaddr", "profile"}) - baseCmd.PersistentFlags().StringVar(&NetName, "netname", "default", "Set network name for network options") - baseCmd.PersistentFlags().StringVarP(&SetNetDevDel, "netdel", "D", "", "Delete the node's network device") - baseCmd.PersistentFlags().BoolVarP(&SetNodeAll, "all", "a", false, "Set all nodes") - baseCmd.PersistentFlags().BoolVarP(&SetYes, "yes", "y", false, "Set 'yes' to all questions asked") - baseCmd.PersistentFlags().BoolVarP(&SetForce, "force", "f", false, "Force configuration (even on error)") + baseCmd.PersistentFlags().StringVar(&vars.netName, "netname", "default", "Set network name for network options") + baseCmd.PersistentFlags().StringVarP(&vars.setNetDevDel, "netdel", "D", "", "Delete the node's network device") + baseCmd.PersistentFlags().BoolVarP(&vars.setNodeAll, "all", "a", false, "Set all nodes") + baseCmd.PersistentFlags().BoolVarP(&vars.setYes, "yes", "y", false, "Set 'yes' to all questions asked") + baseCmd.PersistentFlags().BoolVarP(&vars.setForce, "force", "f", false, "Force configuration (even on error)") + baseCmd.PersistentFlags().StringVar(&vars.fsName, "fsname", "", "set the file system name which must match a partition name") + baseCmd.PersistentFlags().StringVar(&vars.partName, "partname", "", "set the partition name so it can be used by a file system") + baseCmd.PersistentFlags().StringVar(&vars.diskName, "diskname", "", "set disk device name for the partition") + baseCmd.PersistentFlags().StringVar(&vars.setDiskDel, "diskdel", "", "delete the disk from the configuration") + baseCmd.PersistentFlags().StringVar(&vars.setPartDel, "partdel", "", "delete the partition from the configuration") + baseCmd.PersistentFlags().StringVar(&vars.setFsDel, "fsdel", "", "delete the from the configuration") // register the command line completions if err := baseCmd.RegisterFlagCompletionFunc("container", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { list, _ := container.ListSources() @@ -75,10 +90,5 @@ func init() { }); err != nil { log.Println(err) } - -} - -// GetRootCommand returns the root cobra.Command for the application. -func GetCommand() *cobra.Command { return baseCmd } diff --git a/internal/pkg/api/node/list.go b/internal/pkg/api/node/list.go index df8efddc..7f79f10c 100644 --- a/internal/pkg/api/node/list.go +++ b/internal/pkg/api/node/list.go @@ -2,7 +2,6 @@ package apinode import ( "fmt" - "reflect" "sort" "strings" @@ -77,100 +76,14 @@ func NodeList(nodeGet *wwapiv1.GetNodeList) (nodeList wwapiv1.NodeList, err erro n.ContainerName.Print(), n.SystemOverlay.Print()+"/"+n.RuntimeOverlay.Print())) } - } else if nodeGet.Type == wwapiv1.GetNodeList_All { - nodeList.Output = append(nodeList.Output, - fmt.Sprintf("%s:=:%s:=:%s:=:%s", "NODE", "FIELD", "PROFILE", "VALUE")) + } else if nodeGet.Type == wwapiv1.GetNodeList_All || nodeGet.Type == wwapiv1.GetNodeList_FullAll { for _, n := range node.FilterByName(nodes, nodeGet.Nodes) { - nType := reflect.TypeOf(n) - nVal := reflect.ValueOf(n) - nConfType := reflect.TypeOf(node.NodeConf{}) - for i := 0; i < nType.NumField(); i++ { - var fieldName, fieldSource, fieldVal string - nConfField, ok := nConfType.FieldByName(nType.Field(i).Name) - if ok { - fieldName = nConfField.Tag.Get("lopt") - } else { - fieldName = nType.Field(i).Name - } - if nType.Field(i).Type == reflect.TypeOf(node.Entry{}) { - entr := nVal.Field(i).Interface().(node.Entry) - fieldSource = entr.Source() - fieldVal = entr.Print() - nodeList.Output = append(nodeList.Output, - fmt.Sprintf("%s:=:%s:=:%s:=:%s", n.Id.Print(), fieldName, fieldSource, fieldVal)) - } else if nType.Field(i).Type == reflect.TypeOf(map[string]*node.Entry{}) { - entrMap := nVal.Field(i).Interface().(map[string]*node.Entry) - for key, val := range entrMap { - nodeList.Output = append(nodeList.Output, - fmt.Sprintf("%s:=:%s:=:%s:=:%s", n.Id.Print(), key, val.Source(), val.Print())) - } - } else if nType.Field(i).Type == reflect.TypeOf(map[string]*node.NetDevEntry{}) { - netDevs := nVal.Field(i).Interface().(map[string]*node.NetDevEntry) - for netName, netWork := range netDevs { - netInfoType := reflect.TypeOf(*netWork) - netInfoVal := reflect.ValueOf(*netWork) - netConfType := reflect.TypeOf(node.NetDevs{}) - for j := 0; j < netInfoType.NumField(); j++ { - netConfField, ok := netConfType.FieldByName(netInfoType.Field(j).Name) - if ok { - if netConfField.Tag.Get("lopt") != "nettagadd" { - fieldName = netName + ":" + netConfField.Tag.Get("lopt") - } else { - fieldName = netName + ":tag" - } - } else { - fieldName = netName + ":" + netInfoType.Field(j).Name - } - if netInfoType.Field(j).Type == reflect.TypeOf(node.Entry{}) { - entr := netInfoVal.Field(j).Interface().(node.Entry) - fieldSource = entr.Source() - fieldVal = entr.Print() - // only print fields with lopt - if netConfField.Tag.Get("lopt") != "" { - nodeList.Output = append(nodeList.Output, - fmt.Sprintf("%s:=:%s:=:%s:=:%s", n.Id.Print(), fieldName, fieldSource, fieldVal)) - } - } else if netInfoType.Field(j).Type == reflect.TypeOf(map[string]*node.Entry{}) { - for key, val := range netInfoVal.Field(j).Interface().(map[string]*node.Entry) { - keyfieldName := fieldName + ":" + key - fieldSource = val.Source() - fieldVal = val.Print() - nodeList.Output = append(nodeList.Output, - fmt.Sprintf("%s:=:%s:=:%s:=:%s", n.Id.Print(), keyfieldName, fieldSource, fieldVal)) - } - } - - } - } - } else if nType.Field(i).Type.Kind() == reflect.Ptr { - nestInfoType := reflect.TypeOf(nVal.Field(i).Interface()) - nestInfoVal := reflect.ValueOf(nVal.Field(i).Interface()) - // nestConfType := reflect.TypeOf(nConfField.Type.Elem().FieldByName()) - for j := 0; j < nestInfoType.Elem().NumField(); j++ { - nestConfField, ok := nConfField.Type.Elem().FieldByName(nestInfoType.Elem().Field(j).Name) - if ok { - fieldName = nestConfField.Tag.Get("lopt") - } else { - fieldName = nestInfoType.Elem().Field(j).Name - } - if nestInfoType.Elem().Field(j).Type == reflect.TypeOf(node.Entry{}) { - entr := nestInfoVal.Elem().Field(j).Interface().(node.Entry) - fieldSource = entr.Source() - fieldVal = entr.Print() - nodeList.Output = append(nodeList.Output, - fmt.Sprintf("%s:=:%s:=:%s:=:%s", n.Id.Print(), fieldName, fieldSource, fieldVal)) - } else if nestInfoType.Elem().Field(j).Type == reflect.TypeOf(map[string]*node.Entry{}) { - for key, val := range nestInfoVal.Elem().Field(j).Interface().(map[string]*node.Entry) { - fieldName = fieldName + ":" + key - fieldSource = val.Source() - fieldVal = val.Print() - nodeList.Output = append(nodeList.Output, - fmt.Sprintf("%s:=:%s:=:%s:=:%s", n.Id.Print(), fieldName, fieldSource, fieldVal)) - } - } - } - } - + nodeList.Output = append(nodeList.Output, + fmt.Sprintf("%s:=:%s:=:%s:=:%s", "NODE", "FIELD", "PROFILE", "VALUE")) + fields := n.GetFields(wwapiv1.GetNodeList_FullAll == nodeGet.Type) + for _, f := range fields { + nodeList.Output = append(nodeList.Output, + fmt.Sprintf("%s:=:%s:=:%s:=:%s", n.Id.Print(), f.Field, f.Source, f.Value)) } } } diff --git a/internal/pkg/api/node/methods.go b/internal/pkg/api/node/methods.go deleted file mode 100644 index 8943d455..00000000 --- a/internal/pkg/api/node/methods.go +++ /dev/null @@ -1,79 +0,0 @@ -package apinode - -import ( - "fmt" - "reflect" - "strings" - - "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" - "github.com/hpcng/warewulf/internal/pkg/node" -) - -/* -Get the all the fields of a NodeInfo, keys are the lopt of NodeConf. -func GetFields(n interface{}) map[string]*wwapiv1.NodeField { - return getFieldsOf(n, node.NodeConf{}) -} -*/ - -func GetFields(n interface{}) map[string]*wwapiv1.NodeField { - nodeType := reflect.TypeOf(n) - nodeVal := reflect.ValueOf(n) - fieldMap := make(map[string]*wwapiv1.NodeField) - for i := 0; i < nodeType.NumField(); i++ { - switch nodeType.Field(i).Type { - case reflect.TypeOf(node.Entry{}): - var myField wwapiv1.NodeField - entry := nodeVal.Field(i).Interface().(node.Entry) - myField.Source = entry.Source() - myField.Value = entry.Get() - myField.Print = entry.Print() - fieldMap[nodeType.Field(i).Name] = &myField - case reflect.TypeOf([]string{}): - var myField wwapiv1.NodeField - entry := nodeVal.Field(i).Interface().([]string) - if len(entry) == 0 { - myField.Value = node.NoValue - myField.Print = node.NoValue - myField.Source = node.NoValue - } else { - myField.Value = strings.Join(entry, ",") - myField.Print = strings.Join(entry, ",") - myField.Source = node.NoValue - fieldMap[nodeType.Field(i).Name] = &myField - } - case reflect.TypeOf((*node.KernelEntry)(nil)): - entry := nodeVal.Field(i).Elem().Interface().(node.KernelEntry) - kernelMap := GetFields(entry) - for key, val := range kernelMap { - fieldMap["KernelEntry:"+key] = val - } - case reflect.TypeOf((*node.IpmiEntry)(nil)): - entry := nodeVal.Field(i).Elem().Interface().(node.IpmiEntry) - kernelMap := GetFields(entry) - for key, val := range kernelMap { - fieldMap["IpmiEntry:"+key] = val - } - case reflect.TypeOf(map[string]*node.Entry(nil)): - keyMap := nodeVal.Field(i).Interface().(map[string]*node.Entry) - for key, entr := range keyMap { - var myField wwapiv1.NodeField - myField.Source = entr.Source() - myField.Value = entr.Get() - myField.Print = entr.Print() - fieldMap["key:"+key] = &myField - } - case reflect.TypeOf(map[string]*node.NetDevEntry(nil)): - netMap := nodeVal.Field(i).Interface().(map[string]*node.NetDevEntry) - for net, netdev := range netMap { - netMapEntr := GetFields(*netdev) - for key, val := range netMapEntr { - fieldMap["NetDevEntry:"+net+":"+key] = val - } - } - default: - panic(fmt.Sprintf("Can't handle: %s\n", nodeType.Field(i).Type)) - } - } - return fieldMap -} diff --git a/internal/pkg/api/node/node.go b/internal/pkg/api/node/node.go index 13a10c15..f8d76565 100644 --- a/internal/pkg/api/node/node.go +++ b/internal/pkg/api/node/node.go @@ -8,9 +8,9 @@ import ( "os" "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" + warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" "github.com/hpcng/warewulf/internal/pkg/node" "github.com/hpcng/warewulf/internal/pkg/util" - warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" "github.com/hpcng/warewulf/internal/pkg/wwlog" "github.com/hpcng/warewulf/pkg/hostlist" "github.com/pkg/errors" @@ -270,15 +270,51 @@ func NodeSetParameterCheck(set *wwapiv1.NodeSetParameter, console bool) (nodeDB wwlog.Error(fmt.Sprintf("%v", err.Error())) return } - wwlog.Verbose("Node: %s, Deleting network device: %s", n.Id.Get(), set.NetdevDelete) delete(n.NetDevs, set.NetdevDelete) } + if set.PartitionDelete != "" { + deletedPart := false + for diskname, disk := range n.Disks { + if _, ok := disk.Partitions[set.PartitionDelete]; ok { + wwlog.Verbose("Node: %s, on disk %, deleting partition: %s", n.Id.Get(), diskname, set.PartitionDelete) + deletedPart = true + delete(disk.Partitions, set.PartitionDelete) + } + if !deletedPart { + wwlog.Error(fmt.Sprintf("%v", err.Error())) + err = fmt.Errorf("partition doesn't exist: %s", set.PartitionDelete) + return + } + } + } + if set.DiskDelete != "" { + if _, ok := n.Disks[set.DiskDelete]; !ok { + err = fmt.Errorf("disk doesn't exist: %s", set.DiskDelete) + wwlog.Error(fmt.Sprintf("%v", err.Error())) + return + } + wwlog.Verbose("Node: %s, deleting disk: %s", n.Id.Get(), set.DiskDelete) + delete(n.Disks, set.DiskDelete) + } + if set.FilesystemDelete != "" { + if _, ok := n.FileSystems[set.FilesystemDelete]; !ok { + err = fmt.Errorf("filesystem doesn't exist: %s", set.FilesystemDelete) + wwlog.Error(fmt.Sprintf("%v", err.Error())) + return + } + wwlog.Verbose("Node: %s, deleting filesystem: %s", n.Id.Get(), set.FilesystemDelete) + delete(n.FileSystems, set.FilesystemDelete) + } for _, key := range nodeConf.TagsDel { + wwlog.Debug("deleting tag %s", key) delete(n.Tags, key) } - for _, key := range nodeConf.Ipmi.TagsDel { - delete(n.Ipmi.Tags, key) + if nodeConf.Ipmi != nil { + for _, key := range nodeConf.Ipmi.TagsDel { + wwlog.Debug("deleting Ipmi tag %s", key) + delete(n.Ipmi.Tags, key) + } } for net := range nodeConf.NetDevs { for _, key := range nodeConf.NetDevs[net].TagsDel { diff --git a/internal/pkg/api/profile/list.go b/internal/pkg/api/profile/list.go index a06a4c42..dd153240 100644 --- a/internal/pkg/api/profile/list.go +++ b/internal/pkg/api/profile/list.go @@ -2,7 +2,6 @@ package apiprofile import ( "fmt" - "reflect" "sort" "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" @@ -30,99 +29,14 @@ func ProfileList(ShowOpt *wwapiv1.GetProfileList) (profileList wwapiv1.ProfileLi sort.Slice(profiles, func(i, j int) bool { return profiles[i].Id.Get() < profiles[j].Id.Get() }) - if ShowOpt.ShowAll { + if ShowOpt.ShowAll || ShowOpt.ShowFullAll { for _, p := range profiles { profileList.Output = append(profileList.Output, fmt.Sprintf("%s=%s=%s=%s", "PROFILE", "FIELD", "PROFILE", "VALUE")) - nType := reflect.TypeOf(p) - nVal := reflect.ValueOf(p) - nConfType := reflect.TypeOf(node.NodeConf{}) - for i := 0; i < nType.NumField(); i++ { - var fieldName, fieldSource, fieldVal string - nConfField, ok := nConfType.FieldByName(nType.Field(i).Name) - if ok { - fieldName = nConfField.Tag.Get("lopt") - } else { - fieldName = nType.Field(i).Name - } - if nType.Field(i).Type == reflect.TypeOf(node.Entry{}) { - entr := nVal.Field(i).Interface().(node.Entry) - fieldSource = entr.Source() - fieldVal = entr.Print() - profileList.Output = append(profileList.Output, - fmt.Sprintf("%s=%s=%s=%s", p.Id.Print(), fieldName, fieldSource, fieldVal)) - } else if nType.Field(i).Type == reflect.TypeOf(map[string]*node.Entry{}) { - entrMap := nVal.Field(i).Interface().(map[string]*node.Entry) - for key, val := range entrMap { - profileList.Output = append(profileList.Output, - fmt.Sprintf("%s=%s=%s=%s", p.Id.Print(), key, val.Source(), val.Print())) - } - } else if nType.Field(i).Type == reflect.TypeOf(map[string]*node.NetDevEntry{}) { - netDevs := nVal.Field(i).Interface().(map[string]*node.NetDevEntry) - for netName, netWork := range netDevs { - netInfoType := reflect.TypeOf(*netWork) - netInfoVal := reflect.ValueOf(*netWork) - netConfType := reflect.TypeOf(node.NetDevs{}) - for j := 0; j < netInfoType.NumField(); j++ { - netConfField, ok := netConfType.FieldByName(netInfoType.Field(j).Name) - if ok { - if netConfField.Tag.Get("lopt") != "nettagadd" { - fieldName = netName + ":" + netConfField.Tag.Get("lopt") - } else { - fieldName = netName + ":tag" - } - } else { - fieldName = netName + ":" + netInfoType.Field(j).Name - } - if netInfoType.Field(j).Type == reflect.TypeOf(node.Entry{}) { - entr := netInfoVal.Field(j).Interface().(node.Entry) - fieldSource = entr.Source() - fieldVal = entr.Print() - // only print fields with lopt - if netConfField.Tag.Get("lopt") != "" { - profileList.Output = append(profileList.Output, - fmt.Sprintf("%s=%s=%s=%s", p.Id.Print(), fieldName, fieldSource, fieldVal)) - } - } else if netInfoType.Field(j).Type == reflect.TypeOf(map[string]*node.Entry{}) { - for key, val := range netInfoVal.Field(j).Interface().(map[string]*node.Entry) { - keyfieldName := fieldName + ":" + key - fieldSource = val.Source() - fieldVal = val.Print() - profileList.Output = append(profileList.Output, - fmt.Sprintf("%s=%s=%s=%s", p.Id.Print(), keyfieldName, fieldSource, fieldVal)) - } - } - - } - } - } else if nType.Field(i).Type.Kind() == reflect.Ptr { - nestInfoType := reflect.TypeOf(nVal.Field(i).Interface()) - nestInfoVal := reflect.ValueOf(nVal.Field(i).Interface()) - // nestConfType := reflect.TypeOf(nConfField.Type.Elem().FieldByName()) - for j := 0; j < nestInfoType.Elem().NumField(); j++ { - nestConfField, ok := nConfField.Type.Elem().FieldByName(nestInfoType.Elem().Field(j).Name) - if ok { - fieldName = nestConfField.Tag.Get("lopt") - } else { - fieldName = nestInfoType.Elem().Field(j).Name - } - if nestInfoType.Elem().Field(j).Type == reflect.TypeOf(node.Entry{}) { - entr := nestInfoVal.Elem().Field(j).Interface().(node.Entry) - fieldSource = entr.Source() - fieldVal = entr.Print() - profileList.Output = append(profileList.Output, - fmt.Sprintf("%s=%s=%s=%s", p.Id.Print(), fieldName, fieldSource, fieldVal)) - } else if nestInfoType.Elem().Field(j).Type == reflect.TypeOf(map[string]*node.Entry{}) { - for key, val := range nestInfoVal.Elem().Field(j).Interface().(map[string]*node.Entry) { - fieldName = fieldName + ":" + key - fieldSource = val.Source() - fieldVal = val.Print() - profileList.Output = append(profileList.Output, - fmt.Sprintf("%s=%s=%s=%s", p.Id.Print(), fieldName, fieldSource, fieldVal)) - } - } - } - } + fields := p.GetFields(ShowOpt.ShowFullAll) + for _, f := range fields { + profileList.Output = append(profileList.Output, + fmt.Sprintf("%s=%s=%s=%s", p.Id.Print(), f.Field, f.Source, f.Value)) } } } else { diff --git a/internal/pkg/api/profile/profile.go b/internal/pkg/api/profile/profile.go index 5b0a50c3..e6ca6c68 100644 --- a/internal/pkg/api/profile/profile.go +++ b/internal/pkg/api/profile/profile.go @@ -14,7 +14,7 @@ import ( ) // NodeSet is the wwapiv1 implmentation for updating nodeinfo fields. -func ProfileSet(set *wwapiv1.NodeSetParameter) (err error) { +func ProfileSet(set *wwapiv1.ProfileSetParameter) (err error) { if set == nil { return fmt.Errorf("NodeAddParameter is nil") } @@ -31,7 +31,7 @@ func ProfileSet(set *wwapiv1.NodeSetParameter) (err error) { // Output to the console if console is true. // TODO: Determine if the console switch does wwlog or not. // - console may end up being textOutput? -func ProfileSetParameterCheck(set *wwapiv1.NodeSetParameter, console bool) (nodeDB node.NodeYaml, profileCount uint, err error) { +func ProfileSetParameterCheck(set *wwapiv1.ProfileSetParameter, console bool) (nodeDB node.NodeYaml, profileCount uint, err error) { if set == nil { err = fmt.Errorf("profile set parameter is nil") if console { @@ -40,7 +40,7 @@ func ProfileSetParameterCheck(set *wwapiv1.NodeSetParameter, console bool) (node } } - if set.NodeNames == nil { + if set.ProfileNames == nil { err = fmt.Errorf("profile set parameter: ProfileNames is nil") if console { fmt.Printf("%v\n", err) @@ -62,7 +62,7 @@ func ProfileSetParameterCheck(set *wwapiv1.NodeSetParameter, console bool) (node // Note: This does not do expansion on the nodes. - if set.AllNodes || (len(set.NodeNames) == 0) { + if set.AllProfiles || (len(set.ProfileNames) == 0) { if console { fmt.Printf("\n*** WARNING: This command will modify all profiles! ***\n\n") } @@ -82,12 +82,8 @@ func ProfileSetParameterCheck(set *wwapiv1.NodeSetParameter, console bool) (node } for _, p := range profiles { - if util.InSlice(set.NodeNames, p.Id.Get()) { + if util.InSlice(set.ProfileNames, p.Id.Get()) { wwlog.Verbose("Evaluating profile: %s", p.Id.Get()) - // Fix issue: https://github.com/hpcng/warewulf/issues/661 - if p.Id.Get() == "default" && len(p.NetDevs) == 0 { - set.NetdevDelete = p.Id.Get() - } p.SetFrom(&pConf) if set.NetdevDelete != "" { if _, ok := p.NetDevs[set.NetdevDelete]; !ok { @@ -98,6 +94,40 @@ func ProfileSetParameterCheck(set *wwapiv1.NodeSetParameter, console bool) (node wwlog.Verbose("Profile: %s, Deleting network device: %s", p.Id.Get(), set.NetdevDelete) delete(p.NetDevs, set.NetdevDelete) } + if set.PartitionDelete != "" { + deletedPart := false + for diskname, disk := range p.Disks { + if _, ok := disk.Partitions[set.PartitionDelete]; ok { + wwlog.Verbose("Node: %s, on disk %, deleting partition: %s", p.Id.Get(), diskname, set.PartitionDelete) + deletedPart = true + delete(disk.Partitions, set.PartitionDelete) + } + if !deletedPart { + wwlog.Error(fmt.Sprintf("%v", err.Error())) + err = fmt.Errorf("partition doesn't exist: %s", set.PartitionDelete) + return + } + } + } + if set.DiskDelete != "" { + if _, ok := p.Disks[set.DiskDelete]; !ok { + err = fmt.Errorf("disk doesn't exist: %s", set.DiskDelete) + wwlog.Error(fmt.Sprintf("%v", err.Error())) + return + } + wwlog.Verbose("Node: %s, deleting disk: %s", p.Id.Get(), set.DiskDelete) + delete(p.Disks, set.DiskDelete) + } + if set.FilesystemDelete != "" { + if _, ok := p.FileSystems[set.FilesystemDelete]; !ok { + err = fmt.Errorf("disk doesn't exist: %s", set.FilesystemDelete) + wwlog.Error(fmt.Sprintf("%v", err.Error())) + return + } + wwlog.Verbose("Node: %s, deleting filesystem: %s", p.Id.Get(), set.FilesystemDelete) + delete(p.FileSystems, set.FilesystemDelete) + } + for _, key := range pConf.TagsDel { delete(p.Tags, key) } @@ -127,7 +157,7 @@ func ProfileSetParameterCheck(set *wwapiv1.NodeSetParameter, console bool) (node Adds a new profile with the given name */ -func AddProfile(nsp *wwapiv1.NodeSetParameter) error { +func AddProfile(nsp *wwapiv1.ProfileSetParameter) error { if nsp == nil { return fmt.Errorf("NodeSetParameter is nill") } @@ -137,8 +167,8 @@ func AddProfile(nsp *wwapiv1.NodeSetParameter) error { return errors.Wrap(err, "Could not open database") } - if util.InSlice(nodeDB.ListAllProfiles(), nsp.NodeNames[0]) { - return errors.New(fmt.Sprintf("profile with name %s allready exists", nsp.NodeNames[0])) + if util.InSlice(nodeDB.ListAllProfiles(), nsp.ProfileNames[0]) { + return errors.New(fmt.Sprintf("profile with name %s allready exists", nsp.ProfileNames[0])) } var nodeConf node.NodeConf @@ -147,7 +177,7 @@ func AddProfile(nsp *wwapiv1.NodeSetParameter) error { return errors.Wrap(err, "failed to decode nodeConf") } - n, err := nodeDB.AddProfile(nsp.NodeNames[0]) + n, err := nodeDB.AddProfile(nsp.ProfileNames[0]) if err != nil { return errors.Wrap(err, "failed to add node") } diff --git a/internal/pkg/api/routes/v1/routes.proto b/internal/pkg/api/routes/v1/routes.proto index 336eba66..83b844c5 100644 --- a/internal/pkg/api/routes/v1/routes.proto +++ b/internal/pkg/api/routes/v1/routes.proto @@ -118,6 +118,7 @@ message GetNodeList { Network = 2; Long = 3; All = 4; + FullAll = 5; } ListType type = 7; repeated string Nodes = 8; @@ -131,7 +132,8 @@ message NodeList { // Request a profile list view message GetProfileList { bool ShowAll = 1; - repeated string Profiles = 2; + bool ShowFullAll = 2; + repeated string Profiles = 3; } // Get the formated output as string message ProfileList { @@ -169,13 +171,26 @@ message NodeDeleteParameter { // by Warewulf. message NodeSetParameter { string nodeConfYaml = 1; - string container = 2; - string netdevDelete = 14; - bool allNodes = 27; - bool force = 31; - repeated string nodeNames = 39; + string netdevDelete = 2; + string diskDelete = 3; + string partitionDelete = 4; + string filesystemDelete = 5; + bool allNodes = 6; + bool force = 7; + repeated string nodeNames = 8; +} +// ProfileSetParameter contains all fields for updating aspects of profiles managed. Basically a copy of NodeSetParameters +// by Warewulf. +message ProfileSetParameter { + string nodeConfYaml = 1; + string netdevDelete = 2; + string diskDelete = 3; + string partitionDelete = 4; + string filesystemDelete = 5; + bool allProfiles = 6; + bool force = 7; + repeated string profileNames = 8; } - // NodeStatus contains information about the imaging status per node. message NodeStatus { string nodeName = 1; // Name (Id) of the node. diff --git a/internal/pkg/api/routes/wwapiv1/routes.pb.go b/internal/pkg/api/routes/wwapiv1/routes.pb.go index e3dbce97..f13faca9 100644 --- a/internal/pkg/api/routes/wwapiv1/routes.pb.go +++ b/internal/pkg/api/routes/wwapiv1/routes.pb.go @@ -33,6 +33,7 @@ const ( GetNodeList_Network GetNodeList_ListType = 2 GetNodeList_Long GetNodeList_ListType = 3 GetNodeList_All GetNodeList_ListType = 4 + GetNodeList_FullAll GetNodeList_ListType = 5 ) // Enum value maps for GetNodeList_ListType. @@ -43,6 +44,7 @@ var ( 2: "Network", 3: "Long", 4: "All", + 5: "FullAll", } GetNodeList_ListType_value = map[string]int32{ "Simple": 0, @@ -50,6 +52,7 @@ var ( "Network": 2, "Long": 3, "All": 4, + "FullAll": 5, } ) @@ -1047,8 +1050,9 @@ type GetProfileList struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - ShowAll bool `protobuf:"varint,1,opt,name=ShowAll,proto3" json:"ShowAll,omitempty"` - Profiles []string `protobuf:"bytes,2,rep,name=Profiles,proto3" json:"Profiles,omitempty"` + ShowAll bool `protobuf:"varint,1,opt,name=ShowAll,proto3" json:"ShowAll,omitempty"` + ShowFullAll bool `protobuf:"varint,2,opt,name=ShowFullAll,proto3" json:"ShowFullAll,omitempty"` + Profiles []string `protobuf:"bytes,3,rep,name=Profiles,proto3" json:"Profiles,omitempty"` } func (x *GetProfileList) Reset() { @@ -1090,6 +1094,13 @@ func (x *GetProfileList) GetShowAll() bool { return false } +func (x *GetProfileList) GetShowFullAll() bool { + if x != nil { + return x.ShowFullAll + } + return false +} + func (x *GetProfileList) GetProfiles() []string { if x != nil { return x.Profiles @@ -1350,12 +1361,14 @@ type NodeSetParameter struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - NodeConfYaml string `protobuf:"bytes,1,opt,name=nodeConfYaml,proto3" json:"nodeConfYaml,omitempty"` - Container string `protobuf:"bytes,2,opt,name=container,proto3" json:"container,omitempty"` - NetdevDelete string `protobuf:"bytes,14,opt,name=netdevDelete,proto3" json:"netdevDelete,omitempty"` - AllNodes bool `protobuf:"varint,27,opt,name=allNodes,proto3" json:"allNodes,omitempty"` - Force bool `protobuf:"varint,31,opt,name=force,proto3" json:"force,omitempty"` - NodeNames []string `protobuf:"bytes,39,rep,name=nodeNames,proto3" json:"nodeNames,omitempty"` + NodeConfYaml string `protobuf:"bytes,1,opt,name=nodeConfYaml,proto3" json:"nodeConfYaml,omitempty"` + NetdevDelete string `protobuf:"bytes,2,opt,name=netdevDelete,proto3" json:"netdevDelete,omitempty"` + DiskDelete string `protobuf:"bytes,3,opt,name=diskDelete,proto3" json:"diskDelete,omitempty"` + PartitionDelete string `protobuf:"bytes,4,opt,name=partitionDelete,proto3" json:"partitionDelete,omitempty"` + FilesystemDelete string `protobuf:"bytes,5,opt,name=filesystemDelete,proto3" json:"filesystemDelete,omitempty"` + AllNodes bool `protobuf:"varint,6,opt,name=allNodes,proto3" json:"allNodes,omitempty"` + Force bool `protobuf:"varint,7,opt,name=force,proto3" json:"force,omitempty"` + NodeNames []string `protobuf:"bytes,8,rep,name=nodeNames,proto3" json:"nodeNames,omitempty"` } func (x *NodeSetParameter) Reset() { @@ -1397,16 +1410,30 @@ func (x *NodeSetParameter) GetNodeConfYaml() string { return "" } -func (x *NodeSetParameter) GetContainer() string { +func (x *NodeSetParameter) GetNetdevDelete() string { if x != nil { - return x.Container + return x.NetdevDelete } return "" } -func (x *NodeSetParameter) GetNetdevDelete() string { +func (x *NodeSetParameter) GetDiskDelete() string { if x != nil { - return x.NetdevDelete + return x.DiskDelete + } + return "" +} + +func (x *NodeSetParameter) GetPartitionDelete() string { + if x != nil { + return x.PartitionDelete + } + return "" +} + +func (x *NodeSetParameter) GetFilesystemDelete() string { + if x != nil { + return x.FilesystemDelete } return "" } @@ -1432,6 +1459,111 @@ func (x *NodeSetParameter) GetNodeNames() []string { return nil } +// ProfileSetParameter contains all fields for updating aspects of profiles managed. Basically a copy of NodeSetParameters +// by Warewulf. +type ProfileSetParameter struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeConfYaml string `protobuf:"bytes,1,opt,name=nodeConfYaml,proto3" json:"nodeConfYaml,omitempty"` + NetdevDelete string `protobuf:"bytes,2,opt,name=netdevDelete,proto3" json:"netdevDelete,omitempty"` + DiskDelete string `protobuf:"bytes,3,opt,name=diskDelete,proto3" json:"diskDelete,omitempty"` + PartitionDelete string `protobuf:"bytes,4,opt,name=partitionDelete,proto3" json:"partitionDelete,omitempty"` + FilesystemDelete string `protobuf:"bytes,5,opt,name=filesystemDelete,proto3" json:"filesystemDelete,omitempty"` + AllProfiles bool `protobuf:"varint,6,opt,name=allProfiles,proto3" json:"allProfiles,omitempty"` + Force bool `protobuf:"varint,7,opt,name=force,proto3" json:"force,omitempty"` + ProfileNames []string `protobuf:"bytes,8,rep,name=profileNames,proto3" json:"profileNames,omitempty"` +} + +func (x *ProfileSetParameter) Reset() { + *x = ProfileSetParameter{} + if protoimpl.UnsafeEnabled { + mi := &file_routes_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProfileSetParameter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProfileSetParameter) ProtoMessage() {} + +func (x *ProfileSetParameter) ProtoReflect() protoreflect.Message { + mi := &file_routes_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProfileSetParameter.ProtoReflect.Descriptor instead. +func (*ProfileSetParameter) Descriptor() ([]byte, []int) { + return file_routes_proto_rawDescGZIP(), []int{22} +} + +func (x *ProfileSetParameter) GetNodeConfYaml() string { + if x != nil { + return x.NodeConfYaml + } + return "" +} + +func (x *ProfileSetParameter) GetNetdevDelete() string { + if x != nil { + return x.NetdevDelete + } + return "" +} + +func (x *ProfileSetParameter) GetDiskDelete() string { + if x != nil { + return x.DiskDelete + } + return "" +} + +func (x *ProfileSetParameter) GetPartitionDelete() string { + if x != nil { + return x.PartitionDelete + } + return "" +} + +func (x *ProfileSetParameter) GetFilesystemDelete() string { + if x != nil { + return x.FilesystemDelete + } + return "" +} + +func (x *ProfileSetParameter) GetAllProfiles() bool { + if x != nil { + return x.AllProfiles + } + return false +} + +func (x *ProfileSetParameter) GetForce() bool { + if x != nil { + return x.Force + } + return false +} + +func (x *ProfileSetParameter) GetProfileNames() []string { + if x != nil { + return x.ProfileNames + } + return nil +} + // NodeStatus contains information about the imaging status per node. type NodeStatus struct { state protoimpl.MessageState @@ -1448,7 +1580,7 @@ type NodeStatus struct { func (x *NodeStatus) Reset() { *x = NodeStatus{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[22] + mi := &file_routes_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1461,7 +1593,7 @@ func (x *NodeStatus) String() string { func (*NodeStatus) ProtoMessage() {} func (x *NodeStatus) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[22] + mi := &file_routes_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1474,7 +1606,7 @@ func (x *NodeStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeStatus.ProtoReflect.Descriptor instead. func (*NodeStatus) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{22} + return file_routes_proto_rawDescGZIP(), []int{23} } func (x *NodeStatus) GetNodeName() string { @@ -1524,7 +1656,7 @@ type NodeStatusResponse struct { func (x *NodeStatusResponse) Reset() { *x = NodeStatusResponse{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[23] + mi := &file_routes_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1537,7 +1669,7 @@ func (x *NodeStatusResponse) String() string { func (*NodeStatusResponse) ProtoMessage() {} func (x *NodeStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[23] + mi := &file_routes_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1550,7 +1682,7 @@ func (x *NodeStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeStatusResponse.ProtoReflect.Descriptor instead. func (*NodeStatusResponse) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{23} + return file_routes_proto_rawDescGZIP(), []int{24} } func (x *NodeStatusResponse) GetNodeStatus() []*NodeStatus { @@ -1574,7 +1706,7 @@ type VersionResponse struct { func (x *VersionResponse) Reset() { *x = VersionResponse{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[24] + mi := &file_routes_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1587,7 +1719,7 @@ func (x *VersionResponse) String() string { func (*VersionResponse) ProtoMessage() {} func (x *VersionResponse) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[24] + mi := &file_routes_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1600,7 +1732,7 @@ func (x *VersionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VersionResponse.ProtoReflect.Descriptor instead. func (*VersionResponse) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{24} + return file_routes_proto_rawDescGZIP(), []int{25} } func (x *VersionResponse) GetApiPrefix() string { @@ -1636,7 +1768,7 @@ type CanWriteConfig struct { func (x *CanWriteConfig) Reset() { *x = CanWriteConfig{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[25] + mi := &file_routes_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1649,7 +1781,7 @@ func (x *CanWriteConfig) String() string { func (*CanWriteConfig) ProtoMessage() {} func (x *CanWriteConfig) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[25] + mi := &file_routes_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1662,7 +1794,7 @@ func (x *CanWriteConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use CanWriteConfig.ProtoReflect.Descriptor instead. func (*CanWriteConfig) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{25} + return file_routes_proto_rawDescGZIP(), []int{26} } func (x *CanWriteConfig) GetCanWriteConfig() bool { @@ -1802,151 +1934,179 @@ var file_routes_proto_rawDesc = []byte{ 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x6e, - 0x6f, 0x64, 0x65, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, + 0x6f, 0x64, 0x65, 0x73, 0x22, 0xa6, 0x01, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x6f, 0x64, 0x65, - 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x40, + 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x4d, 0x0a, 0x08, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x70, 0x6d, 0x69, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x4c, 0x6f, 0x6e, 0x67, 0x10, 0x03, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x6c, 0x6c, 0x10, 0x04, - 0x22, 0x22, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, - 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x4f, 0x75, - 0x74, 0x70, 0x75, 0x74, 0x22, 0x46, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x6c, - 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x6c, 0x6c, - 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x22, 0x25, 0x0a, 0x0b, - 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x4f, - 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x4f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x22, 0x7e, 0x0a, 0x10, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x50, 0x61, - 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, - 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, - 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x66, - 0x6f, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, - 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, - 0x6d, 0x65, 0x73, 0x22, 0x48, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x59, 0x61, 0x6d, 0x6c, 0x12, - 0x28, 0x0a, 0x0f, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x4d, 0x61, 0x70, 0x59, 0x61, - 0x6d, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, - 0x6e, 0x66, 0x4d, 0x61, 0x70, 0x59, 0x61, 0x6d, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, - 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0x5d, 0x0a, - 0x13, 0x4e, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, - 0x65, 0x74, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, - 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, - 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0xc8, 0x01, 0x0a, - 0x10, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x65, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, - 0x72, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, - 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, - 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x64, 0x65, 0x76, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x65, 0x74, 0x64, 0x65, - 0x76, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x6c, 0x6c, 0x4e, 0x6f, - 0x64, 0x65, 0x73, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x6c, 0x6c, 0x4e, 0x6f, - 0x64, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x1f, 0x20, 0x01, + 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x75, 0x6c, 0x6c, 0x41, 0x6c, 0x6c, 0x10, 0x05, 0x22, 0x22, 0x0a, + 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x4f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x22, 0x68, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4c, + 0x69, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x6c, 0x6c, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x6c, 0x6c, 0x12, 0x20, 0x0a, + 0x0b, 0x53, 0x68, 0x6f, 0x77, 0x46, 0x75, 0x6c, 0x6c, 0x41, 0x6c, 0x6c, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0b, 0x53, 0x68, 0x6f, 0x77, 0x46, 0x75, 0x6c, 0x6c, 0x41, 0x6c, 0x6c, 0x12, + 0x1a, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x22, 0x25, 0x0a, 0x0b, 0x50, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x4f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x4f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x22, 0x7e, 0x0a, 0x10, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, + 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x6f, + 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, + 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x68, 0x61, 0x73, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, + 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, + 0x65, 0x73, 0x22, 0x48, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x59, 0x61, 0x6d, 0x6c, 0x12, 0x28, + 0x0a, 0x0f, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x4d, 0x61, 0x70, 0x59, 0x61, 0x6d, + 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, + 0x66, 0x4d, 0x61, 0x70, 0x59, 0x61, 0x6d, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0x5d, 0x0a, 0x13, + 0x4e, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, + 0x74, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, 0x64, - 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x27, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, - 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x86, 0x01, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, 0x65, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, - 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, 0x6e, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, - 0x69, 0x70, 0x61, 0x64, 0x64, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x70, - 0x61, 0x64, 0x64, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x73, 0x65, 0x65, 0x6e, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x73, 0x65, 0x65, 0x6e, - 0x22, 0x4a, 0x0a, 0x12, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, 0x77, 0x61, - 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x79, 0x0a, 0x0f, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x1c, 0x0a, 0x09, 0x61, 0x70, 0x69, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x61, 0x70, 0x69, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x1e, 0x0a, - 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, + 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, + 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0xa0, 0x02, 0x0a, 0x10, + 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x65, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, + 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, + 0x59, 0x61, 0x6d, 0x6c, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x64, 0x65, 0x76, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x65, 0x74, 0x64, + 0x65, 0x76, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x69, 0x73, 0x6b, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x69, + 0x73, 0x6b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x70, 0x61, 0x72, 0x74, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x12, 0x2a, 0x0a, 0x10, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x66, 0x69, + 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1a, + 0x0a, 0x08, 0x61, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x08, 0x61, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, + 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, + 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x08, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0xaf, + 0x02, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, + 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x6f, + 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x65, + 0x74, 0x64, 0x65, 0x76, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x6e, 0x65, 0x74, 0x64, 0x65, 0x76, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1e, + 0x0a, 0x0a, 0x64, 0x69, 0x73, 0x6b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x64, 0x69, 0x73, 0x6b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x28, + 0x0a, 0x0f, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x2a, 0x0a, 0x10, 0x66, 0x69, 0x6c, 0x65, + 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x10, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x6c, 0x6c, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x61, 0x6c, 0x6c, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x22, 0x0a, 0x0c, + 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, + 0x22, 0x86, 0x01, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x1a, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, + 0x74, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x73, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x70, 0x61, 0x64, 0x64, 0x72, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x70, 0x61, 0x64, 0x64, 0x72, 0x12, 0x1a, 0x0a, + 0x08, 0x6c, 0x61, 0x73, 0x74, 0x73, 0x65, 0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x08, 0x6c, 0x61, 0x73, 0x74, 0x73, 0x65, 0x65, 0x6e, 0x22, 0x4a, 0x0a, 0x12, 0x4e, 0x6f, 0x64, + 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x34, 0x0a, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, + 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x79, 0x0a, 0x0f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x70, 0x69, 0x50, + 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x70, 0x69, + 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0f, 0x77, 0x61, 0x72, 0x65, 0x77, 0x75, + 0x6c, 0x66, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x77, 0x61, 0x72, 0x65, 0x77, 0x75, 0x6c, 0x66, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x77, 0x61, 0x72, 0x65, 0x77, 0x75, 0x6c, 0x66, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x38, 0x0a, 0x0e, 0x43, 0x61, 0x6e, 0x57, 0x72, - 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x61, 0x6e, - 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0e, 0x63, 0x61, 0x6e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x32, 0xa6, 0x08, 0x0a, 0x05, 0x57, 0x57, 0x41, 0x70, 0x69, 0x12, 0x73, 0x0a, 0x0e, 0x43, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x21, 0x2e, - 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x65, 0x72, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, - 0x1a, 0x1f, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x3a, 0x01, 0x2a, - 0x12, 0x64, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x12, 0x22, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, - 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, - 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x2a, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x70, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x22, 0x2e, 0x77, 0x77, 0x61, 0x70, - 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x6d, - 0x70, 0x6f, 0x72, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1f, 0x2e, - 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x22, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x3a, 0x01, 0x2a, 0x12, 0x5f, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x1a, 0x1f, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x76, 0x31, 0x2f, - 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x6d, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x68, 0x6f, 0x77, 0x12, 0x20, 0x2e, 0x77, 0x77, 0x61, - 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, - 0x68, 0x6f, 0x77, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1f, 0x2e, 0x77, + 0x22, 0x38, 0x0a, 0x0e, 0x43, 0x61, 0x6e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x61, 0x6e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x63, 0x61, 0x6e, 0x57, + 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0xa6, 0x08, 0x0a, 0x05, 0x57, + 0x57, 0x41, 0x70, 0x69, 0x12, 0x73, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x21, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x42, 0x75, 0x69, 0x6c, 0x64, + 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1f, 0x2e, 0x77, 0x77, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x17, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x3a, 0x01, 0x2a, 0x12, 0x64, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x22, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, - 0x72, 0x53, 0x68, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x77, 0x12, 0x56, 0x0a, 0x07, 0x4e, 0x6f, 0x64, 0x65, - 0x41, 0x64, 0x64, 0x12, 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, - 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, - 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x13, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x0d, 0x22, 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x3a, 0x01, 0x2a, - 0x12, 0x55, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1d, - 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x16, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x2a, 0x08, 0x2f, - 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x4d, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x4c, - 0x69, 0x73, 0x74, 0x12, 0x13, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, - 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x1a, 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, + 0x72, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, + 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, + 0x2a, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, + 0x70, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x6d, 0x70, 0x6f, + 0x72, 0x74, 0x12, 0x22, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1f, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x22, + 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x3a, 0x01, + 0x2a, 0x12, 0x5f, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4c, 0x69, + 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1f, 0x2e, 0x77, 0x77, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x12, 0x6d, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, + 0x68, 0x6f, 0x77, 0x12, 0x20, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x68, 0x6f, 0x77, 0x50, 0x61, 0x72, 0x61, + 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1f, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x68, 0x6f, 0x77, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, + 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x6f, + 0x77, 0x12, 0x56, 0x0a, 0x07, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x12, 0x1a, 0x2e, 0x77, + 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x50, + 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, 0x2f, 0x76, - 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x59, 0x0a, 0x07, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x65, - 0x74, 0x12, 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, - 0x65, 0x53, 0x65, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1a, 0x2e, - 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x10, 0x22, 0x0b, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x65, 0x74, 0x3a, 0x01, - 0x2a, 0x12, 0x57, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x13, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4e, - 0x61, 0x6d, 0x65, 0x73, 0x1a, 0x1c, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, - 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x2f, - 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x4e, 0x0a, 0x07, 0x56, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, - 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, - 0x12, 0x08, 0x2f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x29, 0x5a, 0x27, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x77, 0x77, 0x61, 0x70, 0x69, 0x76, 0x31, 0x3b, 0x77, 0x77, - 0x61, 0x70, 0x69, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x13, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, 0x22, 0x08, 0x2f, 0x76, + 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x55, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, + 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1d, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, + 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x10, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x2a, 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, + 0x12, 0x4d, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x13, 0x2e, 0x77, + 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, + 0x73, 0x1a, 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, + 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x10, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x12, + 0x59, 0x0a, 0x07, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x65, 0x74, 0x12, 0x1a, 0x2e, 0x77, 0x77, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x65, 0x74, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, + 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x22, 0x0b, 0x2f, 0x76, 0x31, 0x2f, + 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x65, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x57, 0x0a, 0x0a, 0x4e, 0x6f, + 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x13, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, + 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x1a, 0x1c, 0x2e, + 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x4e, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, + 0x31, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, 0x2f, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x42, 0x29, 0x5a, 0x27, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, + 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x77, + 0x77, 0x61, 0x70, 0x69, 0x76, 0x31, 0x3b, 0x77, 0x77, 0x61, 0x70, 0x69, 0x76, 0x31, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1962,7 +2122,7 @@ func file_routes_proto_rawDescGZIP() []byte { } var file_routes_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_routes_proto_msgTypes = make([]protoimpl.MessageInfo, 32) +var file_routes_proto_msgTypes = make([]protoimpl.MessageInfo, 33) var file_routes_proto_goTypes = []interface{}{ (GetNodeList_ListType)(0), // 0: wwapi.v1.GetNodeList.ListType (*NodeDBHash)(nil), // 1: wwapi.v1.NodeDBHash @@ -1987,29 +2147,30 @@ var file_routes_proto_goTypes = []interface{}{ (*NodeYaml)(nil), // 20: wwapi.v1.NodeYaml (*NodeDeleteParameter)(nil), // 21: wwapi.v1.NodeDeleteParameter (*NodeSetParameter)(nil), // 22: wwapi.v1.NodeSetParameter - (*NodeStatus)(nil), // 23: wwapi.v1.NodeStatus - (*NodeStatusResponse)(nil), // 24: wwapi.v1.NodeStatusResponse - (*VersionResponse)(nil), // 25: wwapi.v1.VersionResponse - (*CanWriteConfig)(nil), // 26: wwapi.v1.CanWriteConfig - nil, // 27: wwapi.v1.NetDev.FieldEntry - nil, // 28: wwapi.v1.NetDev.TagsEntry - nil, // 29: wwapi.v1.NodeInfo.FieldsEntry - nil, // 30: wwapi.v1.NodeInfo.NetDevsEntry - nil, // 31: wwapi.v1.NodeInfo.TagsEntry - nil, // 32: wwapi.v1.NodeInfo.KeysEntry - (*empty.Empty)(nil), // 33: google.protobuf.Empty + (*ProfileSetParameter)(nil), // 23: wwapi.v1.ProfileSetParameter + (*NodeStatus)(nil), // 24: wwapi.v1.NodeStatus + (*NodeStatusResponse)(nil), // 25: wwapi.v1.NodeStatusResponse + (*VersionResponse)(nil), // 26: wwapi.v1.VersionResponse + (*CanWriteConfig)(nil), // 27: wwapi.v1.CanWriteConfig + nil, // 28: wwapi.v1.NetDev.FieldEntry + nil, // 29: wwapi.v1.NetDev.TagsEntry + nil, // 30: wwapi.v1.NodeInfo.FieldsEntry + nil, // 31: wwapi.v1.NodeInfo.NetDevsEntry + nil, // 32: wwapi.v1.NodeInfo.TagsEntry + nil, // 33: wwapi.v1.NodeInfo.KeysEntry + (*empty.Empty)(nil), // 34: google.protobuf.Empty } var file_routes_proto_depIdxs = []int32{ 5, // 0: wwapi.v1.ContainerListResponse.containers:type_name -> wwapi.v1.ContainerInfo - 27, // 1: wwapi.v1.NetDev.Field:type_name -> wwapi.v1.NetDev.FieldEntry - 28, // 2: wwapi.v1.NetDev.Tags:type_name -> wwapi.v1.NetDev.TagsEntry - 29, // 3: wwapi.v1.NodeInfo.Fields:type_name -> wwapi.v1.NodeInfo.FieldsEntry - 30, // 4: wwapi.v1.NodeInfo.NetDevs:type_name -> wwapi.v1.NodeInfo.NetDevsEntry - 31, // 5: wwapi.v1.NodeInfo.Tags:type_name -> wwapi.v1.NodeInfo.TagsEntry - 32, // 6: wwapi.v1.NodeInfo.Keys:type_name -> wwapi.v1.NodeInfo.KeysEntry + 28, // 1: wwapi.v1.NetDev.Field:type_name -> wwapi.v1.NetDev.FieldEntry + 29, // 2: wwapi.v1.NetDev.Tags:type_name -> wwapi.v1.NetDev.TagsEntry + 30, // 3: wwapi.v1.NodeInfo.Fields:type_name -> wwapi.v1.NodeInfo.FieldsEntry + 31, // 4: wwapi.v1.NodeInfo.NetDevs:type_name -> wwapi.v1.NodeInfo.NetDevsEntry + 32, // 5: wwapi.v1.NodeInfo.Tags:type_name -> wwapi.v1.NodeInfo.TagsEntry + 33, // 6: wwapi.v1.NodeInfo.Keys:type_name -> wwapi.v1.NodeInfo.KeysEntry 13, // 7: wwapi.v1.NodeListResponse.nodes:type_name -> wwapi.v1.NodeInfo 0, // 8: wwapi.v1.GetNodeList.type:type_name -> wwapi.v1.GetNodeList.ListType - 23, // 9: wwapi.v1.NodeStatusResponse.nodeStatus:type_name -> wwapi.v1.NodeStatus + 24, // 9: wwapi.v1.NodeStatusResponse.nodeStatus:type_name -> wwapi.v1.NodeStatus 11, // 10: wwapi.v1.NetDev.FieldEntry.value:type_name -> wwapi.v1.NodeField 11, // 11: wwapi.v1.NetDev.TagsEntry.value:type_name -> wwapi.v1.NodeField 11, // 12: wwapi.v1.NodeInfo.FieldsEntry.value:type_name -> wwapi.v1.NodeField @@ -2019,25 +2180,25 @@ var file_routes_proto_depIdxs = []int32{ 2, // 16: wwapi.v1.WWApi.ContainerBuild:input_type -> wwapi.v1.ContainerBuildParameter 3, // 17: wwapi.v1.WWApi.ContainerDelete:input_type -> wwapi.v1.ContainerDeleteParameter 4, // 18: wwapi.v1.WWApi.ContainerImport:input_type -> wwapi.v1.ContainerImportParameter - 33, // 19: wwapi.v1.WWApi.ContainerList:input_type -> google.protobuf.Empty + 34, // 19: wwapi.v1.WWApi.ContainerList:input_type -> google.protobuf.Empty 7, // 20: wwapi.v1.WWApi.ContainerShow:input_type -> wwapi.v1.ContainerShowParameter 19, // 21: wwapi.v1.WWApi.NodeAdd:input_type -> wwapi.v1.NodeAddParameter 21, // 22: wwapi.v1.WWApi.NodeDelete:input_type -> wwapi.v1.NodeDeleteParameter 10, // 23: wwapi.v1.WWApi.NodeList:input_type -> wwapi.v1.NodeNames 22, // 24: wwapi.v1.WWApi.NodeSet:input_type -> wwapi.v1.NodeSetParameter 10, // 25: wwapi.v1.WWApi.NodeStatus:input_type -> wwapi.v1.NodeNames - 33, // 26: wwapi.v1.WWApi.Version:input_type -> google.protobuf.Empty + 34, // 26: wwapi.v1.WWApi.Version:input_type -> google.protobuf.Empty 6, // 27: wwapi.v1.WWApi.ContainerBuild:output_type -> wwapi.v1.ContainerListResponse - 33, // 28: wwapi.v1.WWApi.ContainerDelete:output_type -> google.protobuf.Empty + 34, // 28: wwapi.v1.WWApi.ContainerDelete:output_type -> google.protobuf.Empty 6, // 29: wwapi.v1.WWApi.ContainerImport:output_type -> wwapi.v1.ContainerListResponse 6, // 30: wwapi.v1.WWApi.ContainerList:output_type -> wwapi.v1.ContainerListResponse 8, // 31: wwapi.v1.WWApi.ContainerShow:output_type -> wwapi.v1.ContainerShowResponse 14, // 32: wwapi.v1.WWApi.NodeAdd:output_type -> wwapi.v1.NodeListResponse - 33, // 33: wwapi.v1.WWApi.NodeDelete:output_type -> google.protobuf.Empty + 34, // 33: wwapi.v1.WWApi.NodeDelete:output_type -> google.protobuf.Empty 14, // 34: wwapi.v1.WWApi.NodeList:output_type -> wwapi.v1.NodeListResponse 14, // 35: wwapi.v1.WWApi.NodeSet:output_type -> wwapi.v1.NodeListResponse - 24, // 36: wwapi.v1.WWApi.NodeStatus:output_type -> wwapi.v1.NodeStatusResponse - 25, // 37: wwapi.v1.WWApi.Version:output_type -> wwapi.v1.VersionResponse + 25, // 36: wwapi.v1.WWApi.NodeStatus:output_type -> wwapi.v1.NodeStatusResponse + 26, // 37: wwapi.v1.WWApi.Version:output_type -> wwapi.v1.VersionResponse 27, // [27:38] is the sub-list for method output_type 16, // [16:27] is the sub-list for method input_type 16, // [16:16] is the sub-list for extension type_name @@ -2316,7 +2477,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeStatus); i { + switch v := v.(*ProfileSetParameter); i { case 0: return &v.state case 1: @@ -2328,7 +2489,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeStatusResponse); i { + switch v := v.(*NodeStatus); i { case 0: return &v.state case 1: @@ -2340,7 +2501,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VersionResponse); i { + switch v := v.(*NodeStatusResponse); i { case 0: return &v.state case 1: @@ -2352,6 +2513,18 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VersionResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routes_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CanWriteConfig); i { case 0: return &v.state @@ -2370,7 +2543,7 @@ func file_routes_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_routes_proto_rawDesc, NumEnums: 1, - NumMessages: 32, + NumMessages: 33, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/pkg/node/flags.go b/internal/pkg/node/flags.go index e15b769c..2b5488d5 100644 --- a/internal/pkg/node/flags.go +++ b/internal/pkg/node/flags.go @@ -17,46 +17,48 @@ must be called, as the commandline parser returns e.g. netip.IP objects which mu back to strings. */ func (nodeConf *NodeConf) CreateFlags(baseCmd *cobra.Command, excludeList []string) (converters []func() error) { - nodeInfoType := reflect.TypeOf(nodeConf) - nodeInfoVal := reflect.ValueOf(nodeConf) + /* + nodeInfoType := reflect.TypeOf(nodeConf) + nodeInfoVal := reflect.ValueOf(nodeConf) + */ + return RecursiveCreateFlags(nodeConf, baseCmd, excludeList) +} + +func RecursiveCreateFlags(obj interface{}, baseCmd *cobra.Command, excludeList []string) (converters []func() error) { // now iterate of every field + nodeInfoType := reflect.TypeOf(obj) + nodeInfoVal := reflect.ValueOf(obj) for i := 0; i < nodeInfoVal.Elem().NumField(); i++ { if nodeInfoType.Elem().Field(i).Tag.Get("comment") != "" && !util.InSlice(excludeList, nodeInfoType.Elem().Field(i).Tag.Get("lopt")) { field := nodeInfoVal.Elem().Field(i) converters = append(converters, createFlags(baseCmd, excludeList, nodeInfoType.Elem().Field(i), &field)...) } else if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.Ptr { - nestType := reflect.TypeOf(nodeInfoVal.Elem().Field(i).Interface()) - nestVal := reflect.ValueOf(nodeInfoVal.Elem().Field(i).Interface()) - for j := 0; j < nestType.Elem().NumField(); j++ { - field := nestVal.Elem().Field(j) - converters = append(converters, createFlags(baseCmd, excludeList, nestType.Elem().Field(j), &field)...) - } - } else if nodeInfoType.Elem().Field(i).Type == reflect.TypeOf(map[string]*NetDevs(nil)) { - netMap := nodeInfoVal.Elem().Field(i).Interface().(map[string]*NetDevs) - // add a default network so that it can hold values - key := "default" - if len(netMap) == 0 { - netMap[key] = new(NetDevs) - } else { - for keyIt := range netMap { - key = keyIt - break + newConv := RecursiveCreateFlags(nodeInfoVal.Elem().Field(i).Interface(), baseCmd, excludeList) + converters = append(converters, newConv...) + + } else if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.Map && + nodeInfoType.Elem().Field(i).Type != reflect.TypeOf(map[string]string{}) { + // add a map with key UNDEF so that it can hold values N.B. UNDEF can never be added through command line + key := reflect.ValueOf("UNDEF") + if nodeInfoVal.Elem().Field(i).Len() == 0 { + if nodeInfoVal.Elem().Field(i).IsNil() { + nodeInfoVal.Elem().Field(i).Set(reflect.MakeMap(nodeInfoType.Elem().Field(i).Type)) } + newPtr := reflect.New(nodeInfoType.Elem().Field(i).Type.Elem().Elem()) + nodeInfoVal.Elem().Field(i).SetMapIndex(key, newPtr) + } else { + key = nodeInfoVal.Elem().Field(i).MapKeys()[0] } - netType := reflect.TypeOf(netMap[key]) - netVal := reflect.ValueOf(netMap[key]) - for j := 0; j < netType.Elem().NumField(); j++ { - field := netVal.Elem().Field(j) - converters = append(converters, createFlags(baseCmd, excludeList, netType.Elem().Field(j), &field)...) - } + newConv := RecursiveCreateFlags(nodeInfoVal.Elem().Field(i).MapIndex(key).Interface(), baseCmd, excludeList) + converters = append(converters, newConv...) } } return converters } /* -Helper function to create the different PerisitantFlags() for different types. +Helper function to create the different PersistentFlags() for different types. */ func createFlags(baseCmd *cobra.Command, excludeList []string, myType reflect.StructField, myVal *reflect.Value) (converters []func() error) { diff --git a/internal/pkg/node/list.go b/internal/pkg/node/list.go new file mode 100644 index 00000000..4a728ece --- /dev/null +++ b/internal/pkg/node/list.go @@ -0,0 +1,79 @@ +package node + +import ( + "reflect" +) + +/* +struct to hold the fields of GetFields +*/ +type NodeFields struct { + Field string + Source string + Value string +} + +/* +Get all the info out of NodeInfo. If emptyFields is set true, all fields +are shown not only the ones with effective values +*/ +func (node *NodeInfo) GetFields(emptyFields bool) (output []NodeFields) { + return recursiveFields(node, emptyFields, "") +} + +/* +Internal function which travels through all fields of a NodeInfo and for this +reason needs tb called via interface{} +*/ +func recursiveFields(obj interface{}, emptyFields bool, prefix string) (output []NodeFields) { + valObj := reflect.ValueOf(obj) + typeObj := reflect.TypeOf(obj) + for i := 0; i < typeObj.Elem().NumField(); i++ { + if typeObj.Elem().Field(i).Type == reflect.TypeOf(Entry{}) { + myField := valObj.Elem().Field(i).Interface().(Entry) + if emptyFields || myField.Get() != "" { + output = append(output, NodeFields{ + Field: prefix + typeObj.Elem().Field(i).Name, + Source: myField.Source(), + Value: myField.Print(), + }) + } + } else if typeObj.Elem().Field(i).Type == reflect.TypeOf(map[string]*Entry{}) { + for key, val := range valObj.Elem().Field(i).Interface().(map[string]*Entry) { + if emptyFields || val.Get() != "" { + output = append(output, NodeFields{ + Field: prefix + typeObj.Elem().Field(i).Name + "[" + key + "]", + Source: val.Source(), + Value: val.Print(), + }) + } + } + if valObj.Elem().Field(i).Len() == 0 && emptyFields { + output = append(output, NodeFields{ + Field: prefix + typeObj.Elem().Field(i).Name + "[]", + }) + } + } else if typeObj.Elem().Field(i).Type.Kind() == reflect.Map { + mapIter := valObj.Elem().Field(i).MapRange() + for mapIter.Next() { + nestedOut := recursiveFields(mapIter.Value().Interface(), emptyFields, prefix+typeObj.Elem().Field(i).Name+"["+mapIter.Key().String()+"].") + if len(nestedOut) == 0 { + output = append(output, NodeFields{ + Field: prefix + typeObj.Elem().Field(i).Name + "[" + mapIter.Key().String() + "]", + }) + } else { + output = append(output, nestedOut...) + } + } + if valObj.Elem().Field(i).Len() == 0 && emptyFields { + output = append(output, NodeFields{ + Field: prefix + typeObj.Elem().Field(i).Name + "[]", + }) + } + } else if typeObj.Elem().Field(i).Type.Kind() == reflect.Ptr { + nestedOut := recursiveFields(valObj.Elem().Field(i).Interface(), emptyFields, prefix+typeObj.Elem().Field(i).Name+".") + output = append(output, nestedOut...) + } + } + return +} diff --git a/internal/pkg/node/methods.go b/internal/pkg/node/methods.go index b92bb361..88ee41ac 100644 --- a/internal/pkg/node/methods.go +++ b/internal/pkg/node/methods.go @@ -399,12 +399,15 @@ func GetByName(node interface{}, name string) (string, error) { /* Check if the Netdev is empty, so has no values set */ -func (dev *NetDevs) Empty() bool { - if dev == nil { +func ObjectIsEmpty(obj interface{}) bool { + if obj == nil { return true } - varType := reflect.TypeOf(*dev) - varVal := reflect.ValueOf(*dev) + varType := reflect.TypeOf(obj) + varVal := reflect.ValueOf(obj) + if varType.Kind() == reflect.Ptr && !varVal.IsNil() { + return ObjectIsEmpty(varVal.Elem().Interface()) + } if varVal.IsZero() { return true } @@ -418,6 +421,10 @@ func (dev *NetDevs) Empty() bool { if len(varVal.Field(i).Interface().(map[string]string)) != 0 { return false } + } else if varType.Field(i).Type.Kind() == reflect.Ptr { + if !ObjectIsEmpty(varVal.Field(i).Interface()) { + return false + } } } return true diff --git a/internal/pkg/node/methods_test.go b/internal/pkg/node/methods_test.go index d17f1529..56e33356 100644 --- a/internal/pkg/node/methods_test.go +++ b/internal/pkg/node/methods_test.go @@ -7,25 +7,25 @@ func Test_Empty(t *testing.T) { var netdevPtr *NetDevs t.Run("test for empty", func(t *testing.T) { - if netdev.Empty() != true { + if ObjectIsEmpty(netdev) != true { t.Errorf("netdev must be empty") } }) t.Run("test for non empty", func(t *testing.T) { netdev.Device = "foo" - if netdev.Empty() == true { + if ObjectIsEmpty(netdev) == true { t.Errorf("netdev must be non empty") } }) t.Run("test for nil pointer", func(t *testing.T) { - if netdevPtr.Empty() != true { + if ObjectIsEmpty(netdevPtr) != true { t.Errorf("netdev must be empty") } }) t.Run("test for pointer assigned", func(t *testing.T) { netdev.Ipaddr = "10.10.10.1" netdevPtr = &netdev - if netdevPtr.Empty() == true { + if ObjectIsEmpty(netdevPtr) == true { t.Errorf("netdev must be empty") } }) diff --git a/internal/pkg/node/transformers.go b/internal/pkg/node/transformers.go index 2dff47ea..1ea8fb7a 100644 --- a/internal/pkg/node/transformers.go +++ b/internal/pkg/node/transformers.go @@ -256,63 +256,47 @@ func recursiveFlatten(strct interface{}) { } /* -Create a string slice, where every element represents a yaml entry +Create a string slice, where every element represents a yaml entry, used for node/profile edit +in order to get a summary of all available elements */ -func (nodeConf *NodeConf) UnmarshalConf(excludeList []string) (lines []string) { - nodeInfoType := reflect.TypeOf(nodeConf) - nodeInfoVal := reflect.ValueOf(nodeConf) +func UnmarshalConf(obj interface{}, excludeList []string) (lines []string) { + objType := reflect.TypeOf(obj) // now iterate of every field - for i := 0; i < nodeInfoVal.Elem().NumField(); i++ { - if nodeInfoType.Elem().Field(i).Tag.Get("lopt") != "" { - if ymlStr, ok := getYamlString(nodeInfoType.Elem().Field(i), excludeList); ok { + for i := 0; i < objType.NumField(); i++ { + if objType.Field(i).Tag.Get("comment") != "" { + if ymlStr, ok := getYamlString(objType.Field(i), excludeList); ok { lines = append(lines, ymlStr...) } - } else if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.Ptr { - nestType := reflect.TypeOf(nodeInfoVal.Elem().Field(i).Interface()) - if ymlStr, ok := getYamlString(nodeInfoType.Elem().Field(i), excludeList); ok { - lines = append(lines, ymlStr...) + } + if objType.Field(i).Type.Kind() == reflect.Ptr && objType.Field(i).Tag.Get("yaml") != "" { + typeLine := objType.Field(i).Tag.Get("yaml") + if len(strings.Split(typeLine, ",")) > 1 { + typeLine = strings.Split(typeLine, ",")[0] + ":" } - for j := 0; j < nestType.Elem().NumField(); j++ { - if nestType.Elem().Field(j).Tag.Get("lopt") != "" && - !util.InSlice(excludeList, nestType.Elem().Field(j).Tag.Get("lopt")) { - if ymlStr, ok := getYamlString(nestType.Elem().Field(j), excludeList); ok { - for _, str := range ymlStr { - lines = append(lines, " "+str) - } - } - } + lines = append(lines, typeLine) + nestedLine := UnmarshalConf(reflect.New(objType.Field(i).Type.Elem()).Elem().Interface(), excludeList) + for _, ln := range nestedLine { + lines = append(lines, " "+ln) } - } else if nodeInfoType.Elem().Field(i).Type == reflect.TypeOf(map[string]*NetDevs(nil)) { - netMap := nodeInfoVal.Elem().Field(i).Interface().(map[string]*NetDevs) - // add a default network so that it can hold values - key := "default" - if len(netMap) == 0 { - netMap[key] = new(NetDevs) - } else { - for keyIt := range netMap { - key = keyIt - break - } + } else if objType.Field(i).Type.Kind() == reflect.Map && objType.Field(i).Type.Elem().Kind() == reflect.Ptr { + typeLine := objType.Field(i).Tag.Get("yaml") + if len(strings.Split(typeLine, ",")) > 1 { + typeLine = strings.Split(typeLine, ",")[0] + ":" } - if ymlStr, ok := getYamlString(nodeInfoType.Elem().Field(i), excludeList); ok { - lines = append(lines, ymlStr[0]+":", " "+key+":") - netType := reflect.TypeOf(netMap[key]) - for j := 0; j < netType.Elem().NumField(); j++ { - if ymlStr, ok := getYamlString(netType.Elem().Field(j), excludeList); ok { - for _, str := range ymlStr { - lines = append(lines, " "+str) - } - } - } // lines - } // this - } //not - } //do + lines = append(lines, typeLine, " element:") + nestedLine := UnmarshalConf(reflect.New(objType.Field(i).Type.Elem().Elem()).Elem().Interface(), excludeList) + for _, ln := range nestedLine { + lines = append(lines, " "+ln) + } + } + } return lines } /* Get the string of the yaml tag */ + func getYamlString(myType reflect.StructField, excludeList []string) ([]string, bool) { ymlStr := myType.Tag.Get("yaml") if len(strings.Split(ymlStr, ",")) > 1 { @@ -320,11 +304,15 @@ func getYamlString(myType reflect.StructField, excludeList []string) ([]string, } if util.InSlice(excludeList, ymlStr) { return []string{""}, false - } else if myType.Tag.Get("lopt") == "" && myType.Type.Kind() == reflect.String { + } else if myType.Tag.Get("comment") == "" && myType.Type.Kind() == reflect.String { return []string{""}, false } if myType.Type.Kind() == reflect.String { - ymlStr += ": string" + fieldType := myType.Tag.Get("type") + if fieldType == "" { + fieldType = "string" + } + ymlStr += ": " + fieldType return []string{ymlStr}, true } else if myType.Type == reflect.TypeOf([]string{}) { return []string{ymlStr + ":", " - string"}, true @@ -347,7 +335,6 @@ func (nodeConf *NodeConf) SetLopt(lopt string, value string) (found bool) { nodeInfoVal := reflect.ValueOf(nodeConf) // try to find the normal fields, networks come later for i := 0; i < nodeInfoVal.Elem().NumField(); i++ { - //fmt.Println(nodeInfoType.Elem().Field(i).Tag.Get("lopt"), lopt) if nodeInfoType.Elem().Field(i).Tag.Get("lopt") == lopt { if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.String { wwlog.Verbose("Found lopt %s mapping to %s, setting to %s\n",