wwctl node args and completions
Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/warewulf/warewulf/internal/pkg/image"
|
||||
"github.com/warewulf/warewulf/internal/pkg/kernel"
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
"github.com/warewulf/warewulf/internal/pkg/overlay"
|
||||
)
|
||||
|
||||
func NodeKernelVersion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
@@ -51,18 +52,37 @@ func ProfileKernelVersion(cmd *cobra.Command, args []string, toComplete string)
|
||||
func Images(num int) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if num <= 0 || len(args) < num {
|
||||
sources, _ := image.ListSources()
|
||||
return sources, cobra.ShellCompDirectiveNoFileComp
|
||||
} else {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
if sources, err := image.ListSources(); err != nil {
|
||||
return sources, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
func Profiles(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
func Nodes(num int) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if num <= 0 || len(args) < num {
|
||||
if registry, err := node.New(); err == nil {
|
||||
return registry.ListAllNodes(), cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
registry, _ := node.New()
|
||||
return registry.ListAllProfiles(), cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
func Profiles(num int) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if num <= 0 || len(args) < num {
|
||||
if registry, err := node.New(); err == nil {
|
||||
return registry.ListAllProfiles(), cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
func Overlays(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
list := overlay.FindOverlays()
|
||||
return list, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
package add
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
|
||||
"github.com/warewulf/warewulf/internal/app/wwctl/flags"
|
||||
"github.com/warewulf/warewulf/internal/pkg/image"
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
"github.com/warewulf/warewulf/internal/pkg/overlay"
|
||||
)
|
||||
|
||||
// Holds the variables which are needed in CobraRunE
|
||||
@@ -29,42 +25,26 @@ func GetCommand() *cobra.Command {
|
||||
Aliases: []string{"new", "create"},
|
||||
RunE: CobraRunE(&vars),
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
ValidArgsFunction: cobra.FixedCompletions(nil, cobra.ShellCompDirectiveNoFileComp),
|
||||
}
|
||||
vars.nodeConf.CreateFlags(baseCmd)
|
||||
vars.nodeAdd.CreateAddFlags(baseCmd)
|
||||
flags.AddContainer(baseCmd, &(vars.nodeConf.Profile.ImageName))
|
||||
// register the command line completions
|
||||
if err := baseCmd.RegisterFlagCompletionFunc("image", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
list, _ := image.ListSources()
|
||||
return list, cobra.ShellCompDirectiveNoFileComp
|
||||
}); err != nil {
|
||||
log.Println(err)
|
||||
if err := baseCmd.RegisterFlagCompletionFunc("image", completions.Images(0)); err != nil { // no limit
|
||||
panic(err)
|
||||
}
|
||||
if err := baseCmd.RegisterFlagCompletionFunc("kernelversion", completions.NodeKernelVersion); err != nil {
|
||||
log.Println(err)
|
||||
panic(err)
|
||||
}
|
||||
if err := baseCmd.RegisterFlagCompletionFunc("runtime", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
list := overlay.FindOverlays()
|
||||
return list, cobra.ShellCompDirectiveNoFileComp
|
||||
}); err != nil {
|
||||
log.Println(err)
|
||||
if err := baseCmd.RegisterFlagCompletionFunc("runtime", completions.Overlays); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := baseCmd.RegisterFlagCompletionFunc("wwinit", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
list := overlay.FindOverlays()
|
||||
return list, cobra.ShellCompDirectiveNoFileComp
|
||||
}); err != nil {
|
||||
log.Println(err)
|
||||
if err := baseCmd.RegisterFlagCompletionFunc("wwinit", completions.Overlays); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := baseCmd.RegisterFlagCompletionFunc("profile", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
var list []string
|
||||
nodeDB, _ := node.New()
|
||||
profiles, _ := nodeDB.FindAllProfiles()
|
||||
for _, profile := range profiles {
|
||||
list = append(list, profile.Id())
|
||||
}
|
||||
return list, cobra.ShellCompDirectiveNoFileComp
|
||||
}); err != nil {
|
||||
log.Println(err)
|
||||
if err := baseCmd.RegisterFlagCompletionFunc("profile", completions.Profiles(0)); err != nil { // no limit
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// GetRootCommand returns the root cobra.Command for the application.
|
||||
|
||||
@@ -2,6 +2,7 @@ package console
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -12,6 +13,7 @@ var (
|
||||
Long: "Start a new IPMI console for NODENAME.",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: CobraRunE,
|
||||
ValidArgsFunction: completions.Nodes(0), // no limit
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package delete
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -14,15 +14,7 @@ var (
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: CobraRunE,
|
||||
Aliases: []string{"rm", "del", "remove"},
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
nodeDB, _ := node.New()
|
||||
nodes := nodeDB.ListAllNodes()
|
||||
return nodes, cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
ValidArgsFunction: completions.Nodes(0), // no limit
|
||||
}
|
||||
SetYes bool
|
||||
SetForce bool // no hash checking, so always using force
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/warewulf/warewulf/internal/pkg/hostlist"
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
"github.com/warewulf/warewulf/internal/pkg/util"
|
||||
"github.com/warewulf/warewulf/internal/pkg/warewulfd"
|
||||
@@ -34,6 +35,8 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
for nodeID := range registry.Nodes {
|
||||
args = append(args, nodeID)
|
||||
}
|
||||
} else {
|
||||
args = hostlist.Expand(args)
|
||||
}
|
||||
wwlog.Debug("node list: %v", args)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package edit
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -12,20 +12,7 @@ var (
|
||||
Short: "Edit node(s) with editor",
|
||||
Long: "This command opens an editor for the given nodes.",
|
||||
RunE: CobraRunE,
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
nodeDB, _ := node.New()
|
||||
nodes, _ := nodeDB.FindAllNodes()
|
||||
var node_names []string
|
||||
for _, node := range nodes {
|
||||
node_names = append(node_names, node.Id())
|
||||
}
|
||||
|
||||
return node_names, cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
ValidArgsFunction: completions.Nodes(0), // no limit
|
||||
}
|
||||
NoHeader bool
|
||||
)
|
||||
|
||||
@@ -8,18 +8,10 @@ import (
|
||||
)
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
args = append(args, ".*")
|
||||
}
|
||||
filterList := wwapiv1.NodeList{
|
||||
Output: args,
|
||||
}
|
||||
nodeListMsg := apinode.FilteredNodes(&filterList)
|
||||
/*
|
||||
nodeMap := make(map[string]*node.NodeConf)
|
||||
// got proper yaml back
|
||||
_ = yaml.Unmarshal([]byte(nodeListMsg.NodeConfMapYaml), nodeMap)
|
||||
*/
|
||||
wwlog.Info(nodeListMsg.NodeConfMapYaml)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package export
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -12,19 +12,7 @@ var (
|
||||
Short: "Export nodes as yaml to stdout",
|
||||
Long: "This command exports the given nodes as yaml to stdout.",
|
||||
RunE: CobraRunE,
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
nodeDB, _ := node.New()
|
||||
nodes, _ := nodeDB.FindAllNodes()
|
||||
var node_names []string
|
||||
for _, node := range nodes {
|
||||
node_names = append(node_names, node.Id())
|
||||
}
|
||||
return node_names, cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
ValidArgsFunction: completions.Nodes(0), // no limit
|
||||
}
|
||||
NoHeader bool
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ var (
|
||||
Short: "Import node(s) from yaml file",
|
||||
Long: "This command imports all the nodes defined in a file. It will overwrite nodes with same name.",
|
||||
RunE: CobraRunE,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Aliases: []string{"import"},
|
||||
}
|
||||
ImportCVS bool
|
||||
|
||||
@@ -13,9 +13,6 @@ import (
|
||||
|
||||
func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) {
|
||||
return func(cmd *cobra.Command, args []string) (err error) {
|
||||
if len(args) > 0 && strings.Contains(args[0], ",") {
|
||||
args = strings.FieldsFunc(args[0], func(r rune) bool { return r == ',' })
|
||||
}
|
||||
req := wwapiv1.GetNodeList{
|
||||
Nodes: args,
|
||||
Type: wwapiv1.GetNodeList_Simple,
|
||||
|
||||
@@ -2,7 +2,7 @@ package list
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
|
||||
)
|
||||
|
||||
type variables struct {
|
||||
@@ -22,21 +22,9 @@ func GetCommand() *cobra.Command {
|
||||
Short: "List nodes",
|
||||
Long: "This command lists all configured nodes. Optionally, it will list only\n" +
|
||||
"nodes matching a PATTERN.",
|
||||
RunE: CobraRunE(&vars),
|
||||
Aliases: []string{"ls"},
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
nodeDB, _ := node.New()
|
||||
nodes, _ := nodeDB.FindAllNodes()
|
||||
var node_names []string
|
||||
for _, node := range nodes {
|
||||
node_names = append(node_names, node.Id())
|
||||
}
|
||||
return node_names, cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
RunE: CobraRunE(&vars),
|
||||
Aliases: []string{"ls"},
|
||||
ValidArgsFunction: completions.Nodes(0), // no limit
|
||||
}
|
||||
baseCmd.PersistentFlags().BoolVarP(&vars.showNet, "net", "n", false, "Show node network configurations")
|
||||
baseCmd.PersistentFlags().BoolVarP(&vars.showIpmi, "ipmi", "i", false, "Show node IPMI configurations")
|
||||
|
||||
@@ -2,7 +2,7 @@ package sensors
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
|
||||
)
|
||||
|
||||
type variables struct {
|
||||
@@ -21,19 +21,7 @@ func GetCommand() *cobra.Command {
|
||||
Long: "Show IPMI sensor information for nodes matching PATTERN.",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: CobraRunE(&vars),
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
nodeDB, _ := node.New()
|
||||
nodes, _ := nodeDB.FindAllNodes()
|
||||
var node_names []string
|
||||
for _, node := range nodes {
|
||||
node_names = append(node_names, node.Id())
|
||||
}
|
||||
return node_names, cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
ValidArgsFunction: completions.Nodes(0), // no limit
|
||||
}
|
||||
powerCmd.PersistentFlags().BoolVarP(&vars.Full, "full", "F", false, "show detailed output.")
|
||||
powerCmd.PersistentFlags().BoolVarP(&vars.Showcmd, "show", "s", false, "only show command which will be executed")
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
package set
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
|
||||
"github.com/warewulf/warewulf/internal/app/wwctl/flags"
|
||||
"github.com/warewulf/warewulf/internal/pkg/image"
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
"github.com/warewulf/warewulf/internal/pkg/overlay"
|
||||
)
|
||||
|
||||
type variables struct {
|
||||
@@ -31,15 +27,7 @@ func GetCommand() *cobra.Command {
|
||||
Aliases: []string{"modify"},
|
||||
Args: cobra.MinimumNArgs(1), // require pattern as a mandatory arg
|
||||
RunE: CobraRunE(&vars),
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
nodeDB, _ := node.New()
|
||||
nodes := nodeDB.ListAllNodes()
|
||||
return nodes, cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
ValidArgsFunction: completions.Nodes(0), // no limit
|
||||
}
|
||||
|
||||
vars.nodeConf.CreateFlags(baseCmd)
|
||||
@@ -50,37 +38,20 @@ func GetCommand() *cobra.Command {
|
||||
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("image", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
list, _ := image.ListSources()
|
||||
return list, cobra.ShellCompDirectiveNoFileComp
|
||||
}); err != nil {
|
||||
log.Println(err)
|
||||
if err := baseCmd.RegisterFlagCompletionFunc("image", completions.Images(0)); err != nil { // no limit
|
||||
panic(err)
|
||||
}
|
||||
if err := baseCmd.RegisterFlagCompletionFunc("kernelversion", completions.NodeKernelVersion); err != nil {
|
||||
log.Println(err)
|
||||
panic(err)
|
||||
}
|
||||
if err := baseCmd.RegisterFlagCompletionFunc("runtime", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
list := overlay.FindOverlays()
|
||||
return list, cobra.ShellCompDirectiveNoFileComp
|
||||
}); err != nil {
|
||||
log.Println(err)
|
||||
if err := baseCmd.RegisterFlagCompletionFunc("runtime", completions.Overlays); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := baseCmd.RegisterFlagCompletionFunc("wwinit", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
list := overlay.FindOverlays()
|
||||
return list, cobra.ShellCompDirectiveNoFileComp
|
||||
}); err != nil {
|
||||
log.Println(err)
|
||||
if err := baseCmd.RegisterFlagCompletionFunc("wwinit", completions.Overlays); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := baseCmd.RegisterFlagCompletionFunc("profile", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
var list []string
|
||||
nodeDB, _ := node.New()
|
||||
profiles, _ := nodeDB.FindAllProfiles()
|
||||
for _, profile := range profiles {
|
||||
list = append(list, profile.Id())
|
||||
}
|
||||
return list, cobra.ShellCompDirectiveNoFileComp
|
||||
}); err != nil {
|
||||
log.Println(err)
|
||||
if err := baseCmd.RegisterFlagCompletionFunc("profile", completions.Profiles(0)); err != nil { // no limit
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return baseCmd
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package nodestatus
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
|
||||
)
|
||||
|
||||
var (
|
||||
baseCmd = &cobra.Command{
|
||||
@@ -9,7 +12,7 @@ var (
|
||||
Short: "View the provisioning status of nodes",
|
||||
Long: "View and monitor the status of nodes as they are provisioned and check in.",
|
||||
RunE: CobraRunE,
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
ValidArgsFunction: completions.Nodes(0), // no limit
|
||||
}
|
||||
SetWatch bool
|
||||
SetUpdate int
|
||||
|
||||
@@ -4,77 +4,113 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func toInt(i string) int {
|
||||
ret, _ := strconv.Atoi(i)
|
||||
return ret
|
||||
}
|
||||
|
||||
func isDigit(s string) bool {
|
||||
r := []rune(s)
|
||||
|
||||
for i := 0; i < len(r); i++ {
|
||||
if !unicode.IsDigit(r[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func expand_iterate(list []string) ([]string, int) {
|
||||
var ret []string
|
||||
var count int
|
||||
|
||||
for _, i := range list {
|
||||
|
||||
bracketIndex1 := strings.Index(i, "[")
|
||||
bracketIndex2 := strings.Index(i, "]")
|
||||
|
||||
if bracketIndex1 >= 0 && bracketIndex1 < bracketIndex2 {
|
||||
prefix := i[:bracketIndex1]
|
||||
suffix := i[bracketIndex2+1:]
|
||||
ranges := strings.Split(i[bracketIndex1+1:bracketIndex2], ",")
|
||||
count++
|
||||
|
||||
for _, r := range ranges {
|
||||
iterate := strings.Split(r, "-")
|
||||
|
||||
if len(iterate) == 1 {
|
||||
if !isDigit(iterate[0]) {
|
||||
return ret, 0
|
||||
} else {
|
||||
ret = append(ret, prefix+iterate[0]+suffix)
|
||||
}
|
||||
} else if len(iterate) == 2 {
|
||||
if !isDigit(iterate[0]) || !isDigit(iterate[1]) {
|
||||
return ret, 0
|
||||
} else {
|
||||
sigfigures := len(iterate[0])
|
||||
for i := toInt(iterate[0]); i <= toInt(iterate[1]); i++ {
|
||||
ret = append(ret, fmt.Sprintf(`%s%.`+fmt.Sprintf("%d", sigfigures)+`d%s`, prefix, i, suffix))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret, count
|
||||
}
|
||||
|
||||
// Expand takes a slice of host strings, possibly containing comma-separated
|
||||
// values and bracketed ranges (e.g. "node[01-03]") and returns a fully expanded
|
||||
// slice of host names.
|
||||
func Expand(list []string) []string {
|
||||
ret := list
|
||||
// First, split each input string on commas that occur outside brackets.
|
||||
var preList []string
|
||||
for _, s := range list {
|
||||
parts := splitTopLevel(s)
|
||||
preList = append(preList, parts...)
|
||||
}
|
||||
|
||||
expanded := preList
|
||||
for {
|
||||
loop, count := expand_iterate(ret)
|
||||
|
||||
onceExpanded, count := expandOnce(expanded)
|
||||
if count == 0 {
|
||||
break
|
||||
}
|
||||
ret = loop
|
||||
expanded = onceExpanded
|
||||
}
|
||||
|
||||
return ret
|
||||
return expanded
|
||||
}
|
||||
|
||||
// expandOnce performs a single round of bracket expansion.
|
||||
func expandOnce(hosts []string) ([]string, int) {
|
||||
var result []string
|
||||
var expansionCount int
|
||||
|
||||
for _, host := range hosts {
|
||||
bracketStart := strings.Index(host, "[")
|
||||
bracketEnd := strings.Index(host, "]")
|
||||
|
||||
if bracketStart >= 0 && bracketStart < bracketEnd {
|
||||
prefix := host[:bracketStart]
|
||||
suffix := host[bracketEnd+1:]
|
||||
// Extract the content between brackets and split on commas.
|
||||
ranges := strings.Split(host[bracketStart+1:bracketEnd], ",")
|
||||
expansionCount++
|
||||
|
||||
for _, rng := range ranges {
|
||||
parts := strings.Split(rng, "-")
|
||||
|
||||
if len(parts) == 1 {
|
||||
if !isDigit(parts[0]) {
|
||||
// Abort expansion on invalid input.
|
||||
return result, 0
|
||||
}
|
||||
result = append(result, prefix+parts[0]+suffix)
|
||||
} else if len(parts) == 2 {
|
||||
if !isDigit(parts[0]) || !isDigit(parts[1]) {
|
||||
return result, 0
|
||||
}
|
||||
sigFigures := len(parts[0])
|
||||
startNum := toInt(parts[0])
|
||||
endNum := toInt(parts[1])
|
||||
for num := startNum; num <= endNum; num++ {
|
||||
result = append(result, fmt.Sprintf("%s%0*d%s", prefix, sigFigures, num, suffix))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No brackets; keep the string as is.
|
||||
result = append(result, host)
|
||||
}
|
||||
}
|
||||
|
||||
return result, expansionCount
|
||||
}
|
||||
|
||||
// splitTopLevel splits s on commas that are not inside square brackets.
|
||||
func splitTopLevel(s string) []string {
|
||||
var parts []string
|
||||
depth := 0
|
||||
start := 0
|
||||
for i, ch := range s {
|
||||
switch ch {
|
||||
case '[':
|
||||
depth++
|
||||
case ']':
|
||||
if depth > 0 {
|
||||
depth--
|
||||
}
|
||||
case ',':
|
||||
if depth == 0 {
|
||||
parts = append(parts, s[start:i])
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
parts = append(parts, s[start:])
|
||||
return parts
|
||||
}
|
||||
|
||||
// toInt converts a numeric string to an integer. Assumes valid input.
|
||||
func toInt(s string) int {
|
||||
num, _ := strconv.Atoi(s)
|
||||
return num
|
||||
}
|
||||
|
||||
// isDigit returns true if s consists solely of ASCII digits.
|
||||
func isDigit(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] < '0' || s[i] > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -6,28 +6,48 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Single(t *testing.T) {
|
||||
assert.Equal(t, []string{"node1"}, Expand([]string{"node1"}))
|
||||
}
|
||||
func TestHostList(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
input []string
|
||||
output []string
|
||||
}{
|
||||
"single": {
|
||||
input: []string{"node1"},
|
||||
output: []string{"node1"},
|
||||
},
|
||||
"multiple": {
|
||||
input: []string{"node1", "node2"},
|
||||
output: []string{"node1", "node2"},
|
||||
},
|
||||
"range": {
|
||||
input: []string{"node[1-2]"},
|
||||
output: []string{"node1", "node2"},
|
||||
},
|
||||
"internal comma": {
|
||||
input: []string{"node[1,2]"},
|
||||
output: []string{"node1", "node2"},
|
||||
},
|
||||
"mixed range comma": {
|
||||
input: []string{"node[1,2-3]"},
|
||||
output: []string{"node1", "node2", "node3"},
|
||||
},
|
||||
"external comma": {
|
||||
input: []string{"node1,node2"},
|
||||
output: []string{"node1", "node2"},
|
||||
},
|
||||
"mixed external comma with range": {
|
||||
input: []string{"n[1-3],n5,n[7-8,10]"},
|
||||
output: []string{"n1", "n2", "n3", "n5", "n7", "n8", "n10"},
|
||||
},
|
||||
"leading zeroes": {
|
||||
input: []string{"n[01-03]"},
|
||||
output: []string{"n01", "n02", "n03"},
|
||||
},
|
||||
}
|
||||
|
||||
func Test_Multiple(t *testing.T) {
|
||||
assert.Equal(t, []string{"node1", "node2"}, Expand([]string{"node1", "node2"}))
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.output, Expand(tt.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Range(t *testing.T) {
|
||||
assert.Equal(t, []string{"node1", "node2"}, Expand([]string{"node[1-2]"}))
|
||||
}
|
||||
|
||||
func Test_Internal_Comma(t *testing.T) {
|
||||
assert.Equal(t, []string{"node1", "node2"}, Expand([]string{"node[1,2]"}))
|
||||
}
|
||||
|
||||
func Test_Mixed_Range_Comma(t *testing.T) {
|
||||
assert.Equal(t, []string{"node1", "node2", "node3"}, Expand([]string{"node[1,2-3]"}))
|
||||
}
|
||||
|
||||
// not currently supported
|
||||
//
|
||||
// func Test_External_Comma(t *testing.T) {
|
||||
// assert.Equal(t, []string{"node1", "node2"}, Expand([]string{"node1,node2"}))
|
||||
// }
|
||||
|
||||
Reference in New Issue
Block a user