diff --git a/go.mod b/go.mod index 0b48bc2..214c90a 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,11 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/djherbis/times v1.6.0 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/sys v0.34.0 + golang.org/x/sys v0.35.0 honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8 - r00t2.io/goutils v1.9.2 + r00t2.io/goutils v1.9.4 ) require ( diff --git a/go.sum b/go.sum index 9c76fd4..10433dc 100644 --- a/go.sum +++ b/go.sum @@ -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/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.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/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= 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/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/go.mod h1:44w9OfBSQ9l3o59rc2w3AnABtE44bmtNnRMNC7z+oKE= 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.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= diff --git a/pdsh/docs.go b/pdsh/docs.go index 36420b6..f248d35 100644 --- a/pdsh/docs.go +++ b/pdsh/docs.go @@ -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 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. -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/ - - https://github.com/chaos/pdsh/blob/master/doc/pdsh.1.in +For details, see the [chaos/pdsh GitHub], the associated [MAN page source], and/or the [rendered MAN page] (via ManKier). +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 diff --git a/pdsh/consts.go b/pdsh/dshgroup/consts.go similarity index 75% rename from pdsh/consts.go rename to pdsh/dshgroup/consts.go index d781186..b64021f 100644 --- a/pdsh/consts.go +++ b/pdsh/dshgroup/consts.go @@ -1,4 +1,4 @@ -package pdsh +package dshgroup import ( "regexp" @@ -14,5 +14,5 @@ const ( var ( dshGrpDefGrpDir string = "/etc/dsh/group" dshGrpInclPtrn *remap.ReMap = &remap.ReMap{Regexp: regexp.MustCompile(`^\s*#include\s+(?P.+)$`)} - dshGrpSubTokenPtrn *remap.ReMap = &remap.ReMap{Regexp: regexp.MustCompile(`^(?P0*)(?P[1-9]+[0-9]*)?(?:-(?P0*)(?P[1-9]+[0-9]*))?$`)} + dshGrpSubTokenPtrn *remap.ReMap = &remap.ReMap{Regexp: regexp.MustCompile(`^(?P0+)?(?P[0-9]+)(-(?P0+)?(?P[0-9]+))?$`)} ) diff --git a/pdsh/dshgroup/docs.go b/pdsh/dshgroup/docs.go new file mode 100644 index 0000000..f67721c --- /dev/null +++ b/pdsh/dshgroup/docs.go @@ -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/ files by default via the "NoDefault" option. + + * This package allows for not adding ~/.dsh/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 " 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 diff --git a/pdsh/errs.go b/pdsh/dshgroup/errs.go similarity index 66% rename from pdsh/errs.go rename to pdsh/dshgroup/errs.go index 4eee8cf..097d4c3 100644 --- a/pdsh/errs.go +++ b/pdsh/dshgroup/errs.go @@ -1,10 +1,11 @@ -package pdsh +package dshgroup import ( "errors" ) var ( + ErrEmptyDshGroupTok error = errors.New("empty dsh group pattern token") ErrInvalidDshGrpSyntax error = errors.New("invalid dsh group file syntax") ErrInvalidDshGrpPtrn error = errors.New("invalid dsh group pattern syntax") ) diff --git a/pdsh/funcs_dshgrouplister.go b/pdsh/dshgroup/funcs_dshgrouplister.go similarity index 99% rename from pdsh/funcs_dshgrouplister.go rename to pdsh/dshgroup/funcs_dshgrouplister.go index 90e63bc..ad85ed4 100644 --- a/pdsh/funcs_dshgrouplister.go +++ b/pdsh/dshgroup/funcs_dshgrouplister.go @@ -1,4 +1,4 @@ -package pdsh +package dshgroup import ( "io/fs" @@ -170,5 +170,7 @@ For example, assuming the group name ``, the following files will be chec */ func (d *DshGroupLister) GroupedHosts(dedupe bool, searchPaths ...string) (groupedHosts map[string][]string, err error) { + // TODO + return } diff --git a/pdsh/funcs_dshgrp.go b/pdsh/dshgroup/funcs_dshgrp.go similarity index 79% rename from pdsh/funcs_dshgrp.go rename to pdsh/dshgroup/funcs_dshgrp.go index 807e96b..7341e97 100644 --- a/pdsh/funcs_dshgrp.go +++ b/pdsh/dshgroup/funcs_dshgrp.go @@ -1,4 +1,4 @@ -package pdsh +package dshgroup import ( "bufio" @@ -13,8 +13,10 @@ import ( /* 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. + +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 pos int @@ -24,7 +26,10 @@ func ParseDshPtrn(ptrn string) (hostList []string, err error) { var tok dshGrpToken var strBuf *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), tokenized: make([]string, 0), text: ptrn, @@ -50,39 +55,44 @@ func ParseDshPtrn(ptrn string) (hostList []string, err error) { if inToken { // Nested [...[ err = &PtrnParseErr{ - pos: uint(pos), - ptrn: ptrn, - r: r, - err: ErrInvalidDshGrpSyntax, + pos: uint(pos), + ptrn: ptrn, + r: r, + err: ErrInvalidDshGrpSyntax, + inToken: inToken, } return } - parser.tokenized = append(parser.tokenized, strBuf.String()) + generator.tokenized = append(generator.tokenized, strBuf.String()) strBuf.Reset() inToken = true case ']': if !inToken { // Nested ]...] err = &PtrnParseErr{ - pos: uint(pos), - ptrn: ptrn, - r: r, - err: ErrInvalidDshGrpSyntax, + pos: uint(pos), + ptrn: ptrn, + r: r, + err: ErrInvalidDshGrpSyntax, + inToken: inToken, } return } tokStr = tokBuf.String() if tok, err = parseDshGrpToken(tokStr); err != nil { err = &PtrnParseErr{ - pos: uint(pos), - ptrn: ptrn, - r: r, - err: err, + pos: uint(pos), + ptrn: ptrn, + r: r, + err: err, + inToken: inToken, } return } - parser.tokens = append(parser.tokens, tok) + generator.tokens = append(generator.tokens, tok) tokBuf.Reset() + // Don't forget the empty element placeholder. + generator.tokenized = append(generator.tokenized, "") inToken = false default: if inToken { @@ -90,30 +100,33 @@ func ParseDshPtrn(ptrn string) (hostList []string, err error) { if !(0x30 <= r && r <= 0x39) && (r != 0x2d) && (r != 0x2c) { // It's not a valid token. (The actual syntax is validated in parseDshGrpToken and parseDshGrpSubtoken) err = &PtrnParseErr{ - pos: uint(pos), - ptrn: ptrn, - r: r, - err: ErrInvalidDshGrpSyntax, + pos: uint(pos), + ptrn: ptrn, + r: r, + err: ErrInvalidDshGrpSyntax, + inToken: inToken, } return } tokBuf.WriteRune(r) } else { + // TODO: confirm if inline comments and/or trailing/leading whitespace are handled by pdsh? if strings.TrimSpace(string(r)) == "" || r == '#' { // Whitespace is "invalid" (treat it as the end of the pattern). // Same for end-of-line octothorpes. if tokBuf.Len() > 0 { // This should never happen. err = &PtrnParseErr{ - pos: uint(pos), - ptrn: ptrn, - r: r, - err: ErrInvalidDshGrpSyntax, + pos: uint(pos), + ptrn: ptrn, + r: r, + err: ErrInvalidDshGrpSyntax, + inToken: inToken, } return } if strBuf.Len() > 0 { - parser.tokenized = append(parser.tokenized, strBuf.String()) + generator.tokenized = append(generator.tokenized, strBuf.String()) } break } @@ -122,12 +135,13 @@ func ParseDshPtrn(ptrn string) (hostList []string, err error) { (r != 0x2d) && // '-' (r != 0x2e) && // '.' !(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{ - pos: uint(pos), - ptrn: ptrn, - r: r, - err: ErrInvalidDshGrpPtrn, + pos: uint(pos), + ptrn: ptrn, + r: r, + err: ErrInvalidDshGrpPtrn, + inToken: inToken, } return } @@ -154,6 +168,10 @@ func parseDshGrpToken(tokenStr string) (token dshGrpToken, err error) { var sub dshGrpSubtoken s = strings.TrimSpace(tokenStr) + if s == "" { + err = ErrEmptyDshGroupTok + return + } st = strings.Split(s, ",") token = dshGrpToken{ token: tokenStr, @@ -190,21 +208,14 @@ func parseDshGrpSubtoken(subTokenStr string) (subtoken dshGrpSubtoken, err error if vals = matches["start_pad"]; vals != nil && len(vals) == 1 { 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 u64, err = strconv.ParseUint(vals[0], 10, 64); err != nil { return } 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 { endPad = vals[0] } diff --git a/pdsh/dshgroup/funcs_dshgrp_test.go b/pdsh/dshgroup/funcs_dshgrp_test.go new file mode 100644 index 0000000..2161f99 --- /dev/null +++ b/pdsh/dshgroup/funcs_dshgrp_test.go @@ -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) + } + } +} diff --git a/pdsh/dshgroup/funcs_dshgrpgenerator.go b/pdsh/dshgroup/funcs_dshgrpgenerator.go new file mode 100644 index 0000000..40bebe5 --- /dev/null +++ b/pdsh/dshgroup/funcs_dshgrpgenerator.go @@ -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 +} diff --git a/pdsh/funcs_ptrnparseerr.go b/pdsh/dshgroup/funcs_ptrnparseerr.go similarity index 51% rename from pdsh/funcs_ptrnparseerr.go rename to pdsh/dshgroup/funcs_ptrnparseerr.go index f3150a5..4643b98 100644 --- a/pdsh/funcs_ptrnparseerr.go +++ b/pdsh/dshgroup/funcs_ptrnparseerr.go @@ -1,4 +1,4 @@ -package pdsh +package dshgroup import ( "fmt" @@ -8,8 +8,8 @@ import ( func (p *PtrnParseErr) Error() (errStr string) { errStr = fmt.Sprintf( - "Parse error in pattern '%s', position %d rune '%s': %v", - p.ptrn, p.pos, string(p.r), p.err, + "Parse error in pattern '%s', position %d rune '%s' (%#x) (in token: %v): %v", + p.ptrn, p.pos, string(p.r), p.r, p.inToken, p.err, ) return diff --git a/pdsh/dshgroup/types.go b/pdsh/dshgroup/types.go new file mode 100644 index 0000000..0115f7e --- /dev/null +++ b/pdsh/dshgroup/types.go @@ -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 ` 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 = ... + */ + 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 + } +) diff --git a/pdsh/genders/docs.go b/pdsh/genders/docs.go new file mode 100644 index 0000000..1be40d4 --- /dev/null +++ b/pdsh/genders/docs.go @@ -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 diff --git a/pdsh/machines/docs.go b/pdsh/machines/docs.go new file mode 100644 index 0000000..1e542a2 --- /dev/null +++ b/pdsh/machines/docs.go @@ -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 diff --git a/pdsh/netgroup/docs.go b/pdsh/netgroup/docs.go new file mode 100644 index 0000000..62320d5 --- /dev/null +++ b/pdsh/netgroup/docs.go @@ -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 diff --git a/pdsh/nodeupdown/docs.go b/pdsh/nodeupdown/docs.go new file mode 100644 index 0000000..7b4b4cb --- /dev/null +++ b/pdsh/nodeupdown/docs.go @@ -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 diff --git a/pdsh/slurm/docs.go b/pdsh/slurm/docs.go new file mode 100644 index 0000000..40edf34 --- /dev/null +++ b/pdsh/slurm/docs.go @@ -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 diff --git a/pdsh/torque/docs.go b/pdsh/torque/docs.go new file mode 100644 index 0000000..0a7c038 --- /dev/null +++ b/pdsh/torque/docs.go @@ -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 diff --git a/pdsh/types.go b/pdsh/types.go index 458a88a..e127c3a 100644 --- a/pdsh/types.go +++ b/pdsh/types.go @@ -1,86 +1,60 @@ 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 ( /* - DshGroupLister behaves like the host list generator - for pdsh(1)'s "dshgroup module options" (the `misc/dshgroup` - module for pdsh). + Generator is one of the PDSH host generators/iterators offered by this module. + + 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/, - but see NoDefault). - */ - NoEnv bool - /* - NoDefault, if true, will *not* add the default path `/etc/dsh/group/` - to the search paths. + Generate provides a Go-native iterator (also called a "RangeFunc" or "range over function type") + as found in Go 1.23 onwards. - If NoDefault is false, this path is only added if DSHGROUP_PATH is not defined - (or, if it IS defined, if NoEnv is true). + See the assocaied blog entry for details: https://go.dev/blog/range-functions + + Essentially it allows for e.g.: + + for host := range (Generator).Generate() { + // ... + } + + which is the "new standard" approach for iteration. */ - NoDefault bool - // NoHome, if true, will *not* add the `~/.dsh/group/` path to the search paths. - NoHome bool + Generate() (yieldFunc func(yield func(host string) (done bool))) /* - ForceLegacy, if true, will disable the PDSH `#include ` modification -- - treating the source as a traditional DSH group file instead (e.g. `#include ...` - is treated as just a comment). + Reset is used to reset a Generator, allowing one to "restart" the generation at the beginning. + + Generators in this module are generally single-use, but can be reset/reused with this method. */ - ForceLegacy bool - } -) - -type ( - dshGrpGenerator struct { - /* - tokens are interleaved with tokenized and indexed *after*; - in other words, str = ... - */ - 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 + Reset() + /* + Hosts returns a complete generated hostlist at once if you'd rather not iterate. + + Hosts() *does* perform an iteration in runtime, so the recommendation against concurrency + stands, but it calls Reset() when done generating to allow other methods of a Generator to be used. + */ + Hosts() (hostList []string) + /* + Next and Host behave like more "traditional" iterators, e.g. like (database/sql).Row.Next(). + + Next advances the internal state to the next host, and Host() returns it. + */ + Next() (done bool) + /* + Host returns the current host string (or "" if done). + + Be sure to e.g.: + + for (Generator).Next() { + host := (Generator).Host() + } + + otherwise the Host return value will not change. + */ + Host() (host string) } ) diff --git a/pdsh/wcoll/docs.go b/pdsh/wcoll/docs.go new file mode 100644 index 0000000..abe864f --- /dev/null +++ b/pdsh/wcoll/docs.go @@ -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