stubbing pdsh

This commit is contained in:
brent saner 2025-08-17 02:58:24 -04:00
parent e797a14911
commit 5a62622892
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
20 changed files with 432 additions and 130 deletions

6
go.mod
View File

@ -6,11 +6,11 @@ require (
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/djherbis/times v1.6.0 github.com/djherbis/times v1.6.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/shirou/gopsutil/v4 v4.25.6 github.com/shirou/gopsutil/v4 v4.25.7
golang.org/x/sync v0.16.0 golang.org/x/sync v0.16.0
golang.org/x/sys v0.34.0 golang.org/x/sys v0.35.0
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8 honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8
r00t2.io/goutils v1.9.2 r00t2.io/goutils v1.9.4
) )
require ( require (

8
go.sum
View File

@ -19,6 +19,8 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
@ -35,9 +37,15 @@ golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8 h1:FW42yWB1sGClqswyHIB68wo0+oPrav1IuQ+Tdy8Qp8E= honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8 h1:FW42yWB1sGClqswyHIB68wo0+oPrav1IuQ+Tdy8Qp8E=
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8/go.mod h1:44w9OfBSQ9l3o59rc2w3AnABtE44bmtNnRMNC7z+oKE= honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8/go.mod h1:44w9OfBSQ9l3o59rc2w3AnABtE44bmtNnRMNC7z+oKE=
r00t2.io/goutils v1.9.2 h1:1rcDgJ3MorWVBmZSvLpbAUNC+J+ctRfJQq5Wliucjww= r00t2.io/goutils v1.9.2 h1:1rcDgJ3MorWVBmZSvLpbAUNC+J+ctRfJQq5Wliucjww=
r00t2.io/goutils v1.9.2/go.mod h1:76AxpXUeL10uFklxRB11kQsrtj2AKiNm8AwG1bNoBCA= r00t2.io/goutils v1.9.2/go.mod h1:76AxpXUeL10uFklxRB11kQsrtj2AKiNm8AwG1bNoBCA=
r00t2.io/goutils v1.9.3 h1:pR9Ggu5JBpVjfrqNBrZg9bZpKan0TCcwt3MXrSdkhLo=
r00t2.io/goutils v1.9.3/go.mod h1:76AxpXUeL10uFklxRB11kQsrtj2AKiNm8AwG1bNoBCA=
r00t2.io/goutils v1.9.4 h1:+Bm72mKhgXs6DRtU3P4sBjqUNwAKAFfdF9lx5bomwQY=
r00t2.io/goutils v1.9.4/go.mod h1:76AxpXUeL10uFklxRB11kQsrtj2AKiNm8AwG1bNoBCA=

View File

@ -1,16 +1,27 @@
/* /*
Package pdsh (!! WIP !!) provides PDSH-compatible functionality for parsing group files. Package pdsh (!! WIP !!) provides PDSH-compatible functionality for parsing group/gender/etc. files/sources.
Note that this library will *only* source and parse PDSH-compatible host/group files, Note that this library will *only* generate the host list/etc.,
it will not actually connect to anything. it will not actually connect to anything.
It simply provides ways of returning lists of hosts using generation rules/patterns. It simply provides ways of returning lists of hosts using generation rules/patterns.
Said another way, it does not implement any of PDSH's "rcmd" modules, only the "misc" modules.
Currently, the only supported PDSH module is `misc/dshgroup` but additional/all other (As a hint, you can implement SSH connections via [golang.org/x/crypto/ssh] in goroutine'd functions
using this package to generate the target addresses, etc.)
Currently, the only supported PDSH module is misc/dshgroup (as [r00t2.io/sysutils/pdsh/dshgroup]) but additional/all other
host list modules are planned. host list modules are planned.
For details, see: This package deviates slightly from PDSH in some areas; allowing for more loose or more strict behavior occasionally.
Whenever a deviation is offered, this package allows for configuring the generator to behave exactly like PDSH instead
(if the deviating behavior is enabled by default).
- https://github.com/chaos/pdsh/ For details, see the [chaos/pdsh GitHub], the associated [MAN page source], and/or the [rendered MAN page] (via ManKier).
- https://github.com/chaos/pdsh/blob/master/doc/pdsh.1.in You may also want to see the ManKier rendered MAN pages for the [pdsh package].
[chaos/pdsh GitHub]: https://github.com/chaos/pdsh/
[MAN page source]: https://github.com/chaos/pdsh/blob/master/doc/pdsh.1.in
[rendered MAN page]: https://www.mankier.com/1/pdsh
[pdsh package]: https://www.mankier.com/package/pdsh
*/ */
package pdsh package pdsh

View File

@ -1,4 +1,4 @@
package pdsh package dshgroup
import ( import (
"regexp" "regexp"
@ -14,5 +14,5 @@ const (
var ( var (
dshGrpDefGrpDir string = "/etc/dsh/group" dshGrpDefGrpDir string = "/etc/dsh/group"
dshGrpInclPtrn *remap.ReMap = &remap.ReMap{Regexp: regexp.MustCompile(`^\s*#include\s+(?P<incl>.+)$`)} dshGrpInclPtrn *remap.ReMap = &remap.ReMap{Regexp: regexp.MustCompile(`^\s*#include\s+(?P<incl>.+)$`)}
dshGrpSubTokenPtrn *remap.ReMap = &remap.ReMap{Regexp: regexp.MustCompile(`^(?P<start_pad>0*)(?P<start>[1-9]+[0-9]*)?(?:-(?P<end_pad>0*)(?P<end>[1-9]+[0-9]*))?$`)} dshGrpSubTokenPtrn *remap.ReMap = &remap.ReMap{Regexp: regexp.MustCompile(`^(?P<start_pad>0+)?(?P<start>[0-9]+)(-(?P<end_pad>0+)?(?P<end>[0-9]+))?$`)}
) )

30
pdsh/dshgroup/docs.go Normal file
View File

@ -0,0 +1,30 @@
/*
Package dshgroup implements so-called "DSH (Dancer's SHell) Group" files.
It is equivalent to PDSH's [misc/dshgroup] module. ([source])
Be sure to read the [HOSTLIST EXPRESSIONS] section in the MAN page.
# Notable Differences
* This package allows for *never* reading the DSHGROUP_PATH env var (PDSH always reads it) via the "NoEnv" option.
* This package allows for not adding /etc/dsh/group/<group> files by default via the "NoDefault" option.
* This package allows for not adding ~/.dsh/group/<group> files by default via the "NoHome" option.
* This package allows for a "ForceLegacy" mode, disabled by default, that DISABLES the PDSH
extension for "#include <path/group>" extension.
If ForceLegacy is enabled, "#include ..." lines will be treated as comment lines (ignored) instead.
* This package allows for whitespace between group patterns. This can be disabled by the "StrictWhitespace" option.
# TODO/WIP/Not Yet Implemented
This package is not yet complete.
[misc/dshgroup]: https://www.mankier.com/1/pdsh#dshgroup_module_options
[source]: https://github.com/chaos/pdsh/blob/master/src/modules/dshgroup.c
[HOSTLIST EXPRESSIONS]: https://www.mankier.com/1/pdsh#Hostlist_Expressions
*/
package dshgroup

View File

@ -1,10 +1,11 @@
package pdsh package dshgroup
import ( import (
"errors" "errors"
) )
var ( var (
ErrEmptyDshGroupTok error = errors.New("empty dsh group pattern token")
ErrInvalidDshGrpSyntax error = errors.New("invalid dsh group file syntax") ErrInvalidDshGrpSyntax error = errors.New("invalid dsh group file syntax")
ErrInvalidDshGrpPtrn error = errors.New("invalid dsh group pattern syntax") ErrInvalidDshGrpPtrn error = errors.New("invalid dsh group pattern syntax")
) )

View File

@ -1,4 +1,4 @@
package pdsh package dshgroup
import ( import (
"io/fs" "io/fs"
@ -170,5 +170,7 @@ For example, assuming the group name `<GROUP>`, the following files will be chec
*/ */
func (d *DshGroupLister) GroupedHosts(dedupe bool, searchPaths ...string) (groupedHosts map[string][]string, err error) { func (d *DshGroupLister) GroupedHosts(dedupe bool, searchPaths ...string) (groupedHosts map[string][]string, err error) {
// TODO
return return
} }

View File

@ -1,4 +1,4 @@
package pdsh package dshgroup
import ( import (
"bufio" "bufio"
@ -13,8 +13,10 @@ import (
/* /*
ParseDshPtrn parses ptrn using the DSH group pattern ptrn as according to `HOSTLIST EXPRESSSIONS` in pdsh(1). ParseDshPtrn parses ptrn using the DSH group pattern ptrn as according to `HOSTLIST EXPRESSSIONS` in pdsh(1).
`#include` directives are explicitly skipped; this only parses actual generation pattern strings. `#include` directives are explicitly skipped; this only parses actual generation pattern strings.
The returning generator may either be iterated over with `range` or have `Hosts()` called explicitly. // TODO
*/ */
func ParseDshPtrn(ptrn string) (hostList []string, err error) { func ParseDshPtrn(ptrn string) (generator *DshGrpGenerator, err error) {
var r rune var r rune
var pos int var pos int
@ -24,7 +26,10 @@ func ParseDshPtrn(ptrn string) (hostList []string, err error) {
var tok dshGrpToken var tok dshGrpToken
var strBuf *bytes.Buffer = new(bytes.Buffer) var strBuf *bytes.Buffer = new(bytes.Buffer)
var tokBuf *bytes.Buffer = new(bytes.Buffer) var tokBuf *bytes.Buffer = new(bytes.Buffer)
var parser *dshGrpGenerator = &dshGrpGenerator{
// TODO: users can be specified per-pattern.
generator = &DshGrpGenerator{
tokens: make([]dshGrpToken, 0), tokens: make([]dshGrpToken, 0),
tokenized: make([]string, 0), tokenized: make([]string, 0),
text: ptrn, text: ptrn,
@ -50,39 +55,44 @@ func ParseDshPtrn(ptrn string) (hostList []string, err error) {
if inToken { if inToken {
// Nested [...[ // Nested [...[
err = &PtrnParseErr{ err = &PtrnParseErr{
pos: uint(pos), pos: uint(pos),
ptrn: ptrn, ptrn: ptrn,
r: r, r: r,
err: ErrInvalidDshGrpSyntax, err: ErrInvalidDshGrpSyntax,
inToken: inToken,
} }
return return
} }
parser.tokenized = append(parser.tokenized, strBuf.String()) generator.tokenized = append(generator.tokenized, strBuf.String())
strBuf.Reset() strBuf.Reset()
inToken = true inToken = true
case ']': case ']':
if !inToken { if !inToken {
// Nested ]...] // Nested ]...]
err = &PtrnParseErr{ err = &PtrnParseErr{
pos: uint(pos), pos: uint(pos),
ptrn: ptrn, ptrn: ptrn,
r: r, r: r,
err: ErrInvalidDshGrpSyntax, err: ErrInvalidDshGrpSyntax,
inToken: inToken,
} }
return return
} }
tokStr = tokBuf.String() tokStr = tokBuf.String()
if tok, err = parseDshGrpToken(tokStr); err != nil { if tok, err = parseDshGrpToken(tokStr); err != nil {
err = &PtrnParseErr{ err = &PtrnParseErr{
pos: uint(pos), pos: uint(pos),
ptrn: ptrn, ptrn: ptrn,
r: r, r: r,
err: err, err: err,
inToken: inToken,
} }
return return
} }
parser.tokens = append(parser.tokens, tok) generator.tokens = append(generator.tokens, tok)
tokBuf.Reset() tokBuf.Reset()
// Don't forget the empty element placeholder.
generator.tokenized = append(generator.tokenized, "")
inToken = false inToken = false
default: default:
if inToken { if inToken {
@ -90,30 +100,33 @@ func ParseDshPtrn(ptrn string) (hostList []string, err error) {
if !(0x30 <= r && r <= 0x39) && (r != 0x2d) && (r != 0x2c) { if !(0x30 <= r && r <= 0x39) && (r != 0x2d) && (r != 0x2c) {
// It's not a valid token. (The actual syntax is validated in parseDshGrpToken and parseDshGrpSubtoken) // It's not a valid token. (The actual syntax is validated in parseDshGrpToken and parseDshGrpSubtoken)
err = &PtrnParseErr{ err = &PtrnParseErr{
pos: uint(pos), pos: uint(pos),
ptrn: ptrn, ptrn: ptrn,
r: r, r: r,
err: ErrInvalidDshGrpSyntax, err: ErrInvalidDshGrpSyntax,
inToken: inToken,
} }
return return
} }
tokBuf.WriteRune(r) tokBuf.WriteRune(r)
} else { } else {
// TODO: confirm if inline comments and/or trailing/leading whitespace are handled by pdsh?
if strings.TrimSpace(string(r)) == "" || r == '#' { if strings.TrimSpace(string(r)) == "" || r == '#' {
// Whitespace is "invalid" (treat it as the end of the pattern). // Whitespace is "invalid" (treat it as the end of the pattern).
// Same for end-of-line octothorpes. // Same for end-of-line octothorpes.
if tokBuf.Len() > 0 { if tokBuf.Len() > 0 {
// This should never happen. // This should never happen.
err = &PtrnParseErr{ err = &PtrnParseErr{
pos: uint(pos), pos: uint(pos),
ptrn: ptrn, ptrn: ptrn,
r: r, r: r,
err: ErrInvalidDshGrpSyntax, err: ErrInvalidDshGrpSyntax,
inToken: inToken,
} }
return return
} }
if strBuf.Len() > 0 { if strBuf.Len() > 0 {
parser.tokenized = append(parser.tokenized, strBuf.String()) generator.tokenized = append(generator.tokenized, strBuf.String())
} }
break break
} }
@ -122,12 +135,13 @@ func ParseDshPtrn(ptrn string) (hostList []string, err error) {
(r != 0x2d) && // '-' (r != 0x2d) && // '-'
(r != 0x2e) && // '.' (r != 0x2e) && // '.'
!(0x41 <= r && r <= 0x5a) && // 'A' through 'Z' (inclusive) !(0x41 <= r && r <= 0x5a) && // 'A' through 'Z' (inclusive)
!(0x6a <= r && r <= 0x7a) { // 'a' through 'z' (inclusive) !(0x61 <= r && r <= 0x7a) { // 'a' through 'z' (inclusive)
err = &PtrnParseErr{ err = &PtrnParseErr{
pos: uint(pos), pos: uint(pos),
ptrn: ptrn, ptrn: ptrn,
r: r, r: r,
err: ErrInvalidDshGrpPtrn, err: ErrInvalidDshGrpPtrn,
inToken: inToken,
} }
return return
} }
@ -154,6 +168,10 @@ func parseDshGrpToken(tokenStr string) (token dshGrpToken, err error) {
var sub dshGrpSubtoken var sub dshGrpSubtoken
s = strings.TrimSpace(tokenStr) s = strings.TrimSpace(tokenStr)
if s == "" {
err = ErrEmptyDshGroupTok
return
}
st = strings.Split(s, ",") st = strings.Split(s, ",")
token = dshGrpToken{ token = dshGrpToken{
token: tokenStr, token: tokenStr,
@ -190,21 +208,14 @@ func parseDshGrpSubtoken(subTokenStr string) (subtoken dshGrpSubtoken, err error
if vals = matches["start_pad"]; vals != nil && len(vals) == 1 { if vals = matches["start_pad"]; vals != nil && len(vals) == 1 {
startPad = vals[0] startPad = vals[0]
} }
/*
Due to a... particular quirk in the regex that I'm too tired to fix,
the start_pad may be e.g. "0" (or "00", etc.) and start may be "" if the range starts *at* 0
(or 00, 000, etc.).
*/
if vals = matches["start"]; vals != nil && len(vals) == 1 { if vals = matches["start"]; vals != nil && len(vals) == 1 {
if u64, err = strconv.ParseUint(vals[0], 10, 64); err != nil { if u64, err = strconv.ParseUint(vals[0], 10, 64); err != nil {
return return
} }
st.start = uint(u64) st.start = uint(u64)
} else if startPad != "" {
// Yeah, regex bug. So we remove one 0 from startPad, and set st.start to 0.
st.start = 0 // This is implicit, though.
startPad = startPad[:len(startPad)-1]
} }
if vals = matches["end_pad"]; vals != nil && len(vals) == 1 { if vals = matches["end_pad"]; vals != nil && len(vals) == 1 {
endPad = vals[0] endPad = vals[0]
} }

View File

@ -0,0 +1,59 @@
package dshgroup
import (
`log`
`testing`
`github.com/davecgh/go-spew/spew`
)
func TestParseDshPtrn(t *testing.T) {
var err error
var idx int
var s string
var generator *DshGrpGenerator
var hostList []string
var tgtList []string = []string{
"0foo1bar46004quux", "0foo1bar46005quux", "0foo1bar46006quux", "0foo1bar46007quux", "0foo1bar46008quux", "0foo1bar46009quux",
"0foo1bar4615quux", "0foo1bar47004quux", "0foo1bar47005quux", "0foo1bar47006quux", "0foo1bar47007quux", "0foo1bar47008quux",
"0foo1bar47009quux", "0foo1bar4715quux", "0foo2bar46004quux", "0foo2bar46005quux", "0foo2bar46006quux", "0foo2bar46007quux",
"0foo2bar46008quux", "0foo2bar46009quux", "0foo2bar4615quux", "0foo2bar47004quux", "0foo2bar47005quux", "0foo2bar47006quux",
"0foo2bar47007quux", "0foo2bar47008quux", "0foo2bar47009quux", "0foo2bar4715quux", "0foo3bar46004quux", "0foo3bar46005quux",
"0foo3bar46006quux", "0foo3bar46007quux", "0foo3bar46008quux", "0foo3bar46009quux", "0foo3bar4615quux", "0foo3bar47004quux",
"0foo3bar47005quux", "0foo3bar47006quux", "0foo3bar47007quux", "0foo3bar47008quux", "0foo3bar47009quux", "0foo3bar4715quux",
"1foo1bar46004quux", "1foo1bar46005quux", "1foo1bar46006quux", "1foo1bar46007quux", "1foo1bar46008quux", "1foo1bar46009quux",
"1foo1bar4615quux", "1foo1bar47004quux", "1foo1bar47005quux", "1foo1bar47006quux", "1foo1bar47007quux", "1foo1bar47008quux",
"1foo1bar47009quux", "1foo1bar4715quux", "1foo2bar46004quux", "1foo2bar46005quux", "1foo2bar46006quux", "1foo2bar46007quux",
"1foo2bar46008quux", "1foo2bar46009quux", "1foo2bar4615quux", "1foo2bar47004quux", "1foo2bar47005quux", "1foo2bar47006quux",
"1foo2bar47007quux", "1foo2bar47008quux", "1foo2bar47009quux", "1foo2bar4715quux", "1foo3bar46004quux", "1foo3bar46005quux",
"1foo3bar46006quux", "1foo3bar46007quux", "1foo3bar46008quux", "1foo3bar46009quux", "1foo3bar4615quux", "1foo3bar47004quux",
"1foo3bar47005quux", "1foo3bar47006quux", "1foo3bar47007quux", "1foo3bar47008quux", "1foo3bar47009quux", "1foo3bar4715quux",
"2foo1bar46004quux", "2foo1bar46005quux", "2foo1bar46006quux", "2foo1bar46007quux", "2foo1bar46008quux", "2foo1bar46009quux",
"2foo1bar4615quux", "2foo1bar47004quux", "2foo1bar47005quux", "2foo1bar47006quux", "2foo1bar47007quux", "2foo1bar47008quux",
"2foo1bar47009quux", "2foo1bar4715quux", "2foo2bar46004quux", "2foo2bar46005quux", "2foo2bar46006quux", "2foo2bar46007quux",
"2foo2bar46008quux", "2foo2bar46009quux", "2foo2bar4615quux", "2foo2bar47004quux", "2foo2bar47005quux", "2foo2bar47006quux",
"2foo2bar47007quux", "2foo2bar47008quux", "2foo2bar47009quux", "2foo2bar4715quux", "2foo3bar46004quux", "2foo3bar46005quux",
"2foo3bar46006quux", "2foo3bar46007quux", "2foo3bar46008quux", "2foo3bar46009quux", "2foo3bar4615quux", "2foo3bar47004quux",
"2foo3bar47005quux", "2foo3bar47006quux", "2foo3bar47007quux", "2foo3bar47008quux", "2foo3bar47009quux", "2foo3bar4715quux",
}
if generator, err = ParseDshPtrn("[0-2]foo[1-3]bar[4][6-7]baz[004-009,15]quux"); err != nil {
t.Fatal(err)
}
_ = spew.Sdump(generator)
hostList = generator.Hosts()
t.Log(hostList)
if len(hostList) != len(tgtList) {
t.Fatalf("Generated list length (%d) does not match target (%d)", len(hostList), len(tgtList))
}
for idx, s = range hostList {
if s != tgtList[idx] {
log.Fatalf("Test vector %d ('%s') does not match generated value '%s'", idx+1, tgtList[idx], s)
}
}
}

View File

@ -0,0 +1,36 @@
package dshgroup
func (d *DshGrpGenerator) Generate() (yieldFunc func(yield func(host string) (done bool))) {
// TODO
return
}
func (d *DshGrpGenerator) Hosts() (hostList []string) {
// TODO
return
}
func (d *DshGrpGenerator) Host() (host string) {
// TODO
return
}
func (d *DshGrpGenerator) Next() (done bool) {
// TODO
return
}
func (d *DshGrpGenerator) Reset() {
// TODO
return
}

View File

@ -1,4 +1,4 @@
package pdsh package dshgroup
import ( import (
"fmt" "fmt"
@ -8,8 +8,8 @@ import (
func (p *PtrnParseErr) Error() (errStr string) { func (p *PtrnParseErr) Error() (errStr string) {
errStr = fmt.Sprintf( errStr = fmt.Sprintf(
"Parse error in pattern '%s', position %d rune '%s': %v", "Parse error in pattern '%s', position %d rune '%s' (%#x) (in token: %v): %v",
p.ptrn, p.pos, string(p.r), p.err, p.ptrn, p.pos, string(p.r), p.r, p.inToken, p.err,
) )
return return

90
pdsh/dshgroup/types.go Normal file
View File

@ -0,0 +1,90 @@
package dshgroup
// TODO: This... doesn't really have much usefulness, does it?
/*
type (
HostLister interface {
// Hosts returns ALL hsots (where applicable) that are considered/generated for a Lister.
Hosts() (hosts []string, err error)
}
)
*/
type (
/*
DshGroupLister behaves like the host list generator
for pdsh(1)'s "dshgroup module options" (the `misc/dshgroup`
module for pdsh).
*/
DshGroupLister struct {
/*
NoEnv, if true, will *not* use DSHGROUP_PATH (force-defaulting to /etc/dsh/group/,
but see NoDefault).
*/
NoEnv bool
/*
NoDefault, if true, will *not* add the default path `/etc/dsh/group/`
to the search paths.
If NoDefault is false, this path is only added if DSHGROUP_PATH is not defined
(or, if it IS defined, if NoEnv is true).
*/
NoDefault bool
// NoHome, if true, will *not* add the `~/.dsh/group/` path to the search paths.
NoHome bool
/*
ForceLegacy, if true, will disable the PDSH `#include <PATH|GROUP>` modification --
treating the source as a traditional DSH group file instead (e.g. `#include ...`
is treated as just a comment).
*/
ForceLegacy bool
// StrictWhitespace follows the same behavior as PDSH regarding no whitespace between patterns.
StrictWhitespace bool
}
)
type (
// DshGrpGenerator generates a list of hosts according to the pdsh "dshgroup" module.
DshGrpGenerator struct {
/*
tokens are interleaved with tokenized and indexed *after*;
in other words, str = <substr0><token0><substr1><token1>...
*/
tokens []dshGrpToken
// tokenized holds the split original text with tokens removed and split where the tokens occur.
tokenized []string
// text holds the original pattern.
text string
}
dshGrpToken struct {
/*
token contains the original range specifier.
Tokens may be e.g.:
* 3: str3
* 3-5: str3, str4, str5
* 3,5: str3, str5
*/
token string
// subtokens hold a split of the individual range specifiers.
subtokens []dshGrpSubtoken
}
dshGrpSubtoken struct {
// start indicates either the single value or the start of the range.
start uint
// end, if 0 or less than start, indicates a single-value range.
end uint
// pad, if non-empty, is a string to add to the beginning of each of the generated substrings for this subtoken.
pad string
}
)
type (
PtrnParseErr struct {
pos uint
ptrn string
r rune
err error
inToken bool
}
)

11
pdsh/genders/docs.go Normal file
View File

@ -0,0 +1,11 @@
/*
Package genders implements the [misc/genders] PDSH module. ([source])
# TODO/WIP/Not Yet Implemented
This package is not yet complete.
[misc/genders]: https://www.mankier.com/1/pdsh#genders_module_options
[source]: https://github.com/chaos/pdsh/blob/master/src/modules/genders.c
*/
package genders

11
pdsh/machines/docs.go Normal file
View File

@ -0,0 +1,11 @@
/*
Package machines implements the [misc/machines] PDSH module. ([source])
# TODO/WIP/Not Yet Implemented
This package is not yet complete.
[misc/machines]: https://www.mankier.com/1/pdsh#machines_module_options
[source]: https://github.com/chaos/pdsh/blob/master/src/modules/machines.c
*/
package machines

11
pdsh/netgroup/docs.go Normal file
View File

@ -0,0 +1,11 @@
/*
Package netgroup implements the [misc/netgroup] PDSH module. ([source])
# TODO/WIP/Not Yet Implemented
This package is not yet complete.
[misc/netgroup]: https://www.mankier.com/1/pdsh#netgroup_module_options
[source]: https://github.com/chaos/pdsh/blob/master/src/modules/netgroup.c
*/
package netgroup

11
pdsh/nodeupdown/docs.go Normal file
View File

@ -0,0 +1,11 @@
/*
Package nodeupdown implements the [misc/nodeupdown] PDSH module. ([source])
# TODO/WIP/Not Yet Implemented
This package is not yet complete.
[misc/nodeupdown]: https://www.mankier.com/1/pdsh#nodeupdown_module_options
[source]: https://github.com/chaos/pdsh/blob/master/src/modules/nodeupdown.c
*/
package nodeupdown

11
pdsh/slurm/docs.go Normal file
View File

@ -0,0 +1,11 @@
/*
Package slurm implements the [misc/slurm] PDSH module. ([source])
# TODO/WIP/Not Yet Implemented
This package is not yet complete.
[misc/slurm]: https://www.mankier.com/1/pdsh#slurm_module_options
[source]: https://github.com/chaos/pdsh/blob/master/src/modules/slurm.c
*/
package slurm

11
pdsh/torque/docs.go Normal file
View File

@ -0,0 +1,11 @@
/*
Package torque implements the [misc/torque] PDSH module. ([source])
# TODO/WIP/Not Yet Implemented
This package is not yet complete.
[misc/torque]: https://www.mankier.com/1/pdsh#torque_module_options
[source]: https://github.com/chaos/pdsh/blob/master/src/modules/torque.c
*/
package torque

View File

@ -1,86 +1,60 @@
package pdsh package pdsh
// TODO: This... doesn't really have much usefulness, does it?
/*
type (
HostLister interface {
// Hosts returns ALL hsots (where applicable) that are considered/generated for a Lister.
Hosts() (hosts []string, err error)
}
)
*/
type ( type (
/* /*
DshGroupLister behaves like the host list generator Generator is one of the PDSH host generators/iterators offered by this module.
for pdsh(1)'s "dshgroup module options" (the `misc/dshgroup`
module for pdsh). Note that these generators/iterators are *stateful*, which means they shouldn't
(probably; I'm not your dad) be used concurrently (unless you want some hard-to-debug results)
and all methods advance the generator - so you probably don't want to call both Generate() and
Next()/Host() on the same instance, for example.
*/ */
DshGroupLister struct { Generator interface {
/* /*
NoEnv, if true, will *not* use DSHGROUP_PATH (force-defaulting to /etc/dsh/group/, Generate provides a Go-native iterator (also called a "RangeFunc" or "range over function type")
but see NoDefault). as found in Go 1.23 onwards.
*/
NoEnv bool
/*
NoDefault, if true, will *not* add the default path `/etc/dsh/group/`
to the search paths.
If NoDefault is false, this path is only added if DSHGROUP_PATH is not defined See the assocaied blog entry for details: https://go.dev/blog/range-functions
(or, if it IS defined, if NoEnv is true).
Essentially it allows for e.g.:
for host := range (Generator).Generate() {
// ...
}
which is the "new standard" approach for iteration.
*/ */
NoDefault bool Generate() (yieldFunc func(yield func(host string) (done bool)))
// NoHome, if true, will *not* add the `~/.dsh/group/` path to the search paths.
NoHome bool
/* /*
ForceLegacy, if true, will disable the PDSH `#include <PATH|GROUP>` modification -- Reset is used to reset a Generator, allowing one to "restart" the generation at the beginning.
treating the source as a traditional DSH group file instead (e.g. `#include ...`
is treated as just a comment). Generators in this module are generally single-use, but can be reset/reused with this method.
*/ */
ForceLegacy bool Reset()
} /*
) Hosts returns a complete generated hostlist at once if you'd rather not iterate.
type ( Hosts() *does* perform an iteration in runtime, so the recommendation against concurrency
dshGrpGenerator struct { stands, but it calls Reset() when done generating to allow other methods of a Generator to be used.
/* */
tokens are interleaved with tokenized and indexed *after*; Hosts() (hostList []string)
in other words, str = <substr0><token0><substr1><token1>... /*
*/ Next and Host behave like more "traditional" iterators, e.g. like (database/sql).Row.Next().
tokens []dshGrpToken
// tokenized holds the split original text with tokens removed and split where the tokens occur. Next advances the internal state to the next host, and Host() returns it.
tokenized []string */
// text holds the original pattern. Next() (done bool)
text string /*
} Host returns the current host string (or "" if done).
dshGrpToken struct {
/* Be sure to e.g.:
token contains the original range specifier.
Tokens may be e.g.: for (Generator).Next() {
host := (Generator).Host()
* 3: str3 }
* 3-5: str3, str4, str5
* 3,5: str3, str5 otherwise the Host return value will not change.
*/ */
token string Host() (host string)
// subtokens hold a split of the individual range specifiers.
subtokens []dshGrpSubtoken
}
dshGrpSubtoken struct {
// start indicates either the single value or the start of the range.
start uint
// end, if 0 or less than start, indicates a single-value range.
end uint
// pad, if non-empty, is a string to add to the beginning of each of the generated substrings for this subtoken.
pad string
}
)
type (
PtrnParseErr struct {
pos uint
ptrn string
r rune
err error
} }
) )

14
pdsh/wcoll/docs.go Normal file
View File

@ -0,0 +1,14 @@
/*
Package wcoll implements the "default" [WCOLL] method for PDSH. ([source])
Be sure to read the [HOSTLIST EXPRESSIONS] section in the MAN page.
# TODO/WIP/Not Yet Implemented
This package is not yet complete.
[WCOLL]: https://www.mankier.com/1/pdsh#Environment_Variables
[source]: https://github.com/chaos/pdsh/blob/master/src/pdsh/wcoll.c
[HOSTLIST EXPRESSIONS]: https://www.mankier.com/1/pdsh#Hostlist_Expressions
*/
package wcoll