v1.3.0
ADDED: * auger, some convenience funcs around Augeas.
This commit is contained in:
parent
b64c318a4a
commit
c0c924b75a
2
TODO
2
TODO
@ -4,6 +4,8 @@
|
|||||||
--- https://github.com/hlandau/passlib
|
--- https://github.com/hlandau/passlib
|
||||||
-- incoprporated separately; https://git.r00t2.io/r00t2/PWGen (import r00t2.io/pwgen)
|
-- incoprporated separately; https://git.r00t2.io/r00t2/PWGen (import r00t2.io/pwgen)
|
||||||
|
|
||||||
|
- auger needs to be build-constrained to linux.
|
||||||
|
|
||||||
- unit tests
|
- unit tests
|
||||||
|
|
||||||
- constants/vars for errors
|
- constants/vars for errors
|
||||||
|
9
auger/consts.go
Normal file
9
auger/consts.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package auger
|
||||||
|
|
||||||
|
const (
|
||||||
|
augLensTpl string = "/augeas/load/%v" // A fmt.Sprintf string (single placeholder only) for Lens module roots.
|
||||||
|
augFsTree string = "/files"
|
||||||
|
augFsTpl string = augFsTree + "%v//%v" // A fmt.Sprintf string (first placeholder fspath, second placeholder includeDirective) for files to search for the includeDirective.
|
||||||
|
augInclTfm string = "incl" // The transformer keyword for Augeas includes.
|
||||||
|
augAppendSuffix string = "[last()+1]"
|
||||||
|
)
|
63
auger/funcs.go
Normal file
63
auger/funcs.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package auger
|
||||||
|
|
||||||
|
import (
|
||||||
|
`io/fs`
|
||||||
|
`os`
|
||||||
|
`strings`
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
AugpathToFspath returns the filesystem path from an Augeas path.
|
||||||
|
|
||||||
|
It is *required* and expected that the Augeas standard /files prefix be removed first;
|
||||||
|
if not, it is assumed to be part of the filesystem path.
|
||||||
|
|
||||||
|
If a valid path cannot be determined, fsPath will be empty.
|
||||||
|
*/
|
||||||
|
func AugpathToFspath(augPath string) (fsPath string, err error) {
|
||||||
|
|
||||||
|
var path string
|
||||||
|
var num int
|
||||||
|
var augSplit []string = strings.Split(augPath, "/")
|
||||||
|
|
||||||
|
num = len(augSplit)
|
||||||
|
|
||||||
|
for i := num - 1; i >= 0; i-- {
|
||||||
|
path = strings.Join(augSplit[:i], "/")
|
||||||
|
if !fs.ValidPath(path) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err = os.Stat(path); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fsPath = path
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// dedupePaths is used to reduce new to only unique entries that do not exist in existing.
|
||||||
|
func dedupePaths(new, existing []string) (missing []string) {
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
var m map[string]bool = make(map[string]bool)
|
||||||
|
|
||||||
|
for _, path := range existing {
|
||||||
|
m[path] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range new {
|
||||||
|
if _, ok = m[path]; !ok {
|
||||||
|
missing = append(missing, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
294
auger/funcs_aug.go
Normal file
294
auger/funcs_aug.go
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
package auger
|
||||||
|
|
||||||
|
import (
|
||||||
|
`bufio`
|
||||||
|
`errors`
|
||||||
|
`fmt`
|
||||||
|
`io`
|
||||||
|
`os`
|
||||||
|
`path/filepath`
|
||||||
|
`strings`
|
||||||
|
|
||||||
|
`github.com/davecgh/go-spew/spew`
|
||||||
|
`github.com/google/shlex`
|
||||||
|
`honnef.co/go/augeas`
|
||||||
|
`r00t2.io/sysutils/paths`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Close cleanly closes the underlying Augeas connection.
|
||||||
|
func (a *Aug) Close() {
|
||||||
|
|
||||||
|
a.aug.Close()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interact provides an interactive shell-like interface for debugging purposes to explore the loaded Augeas tree.
|
||||||
|
func (a *Aug) Interact() (err error) {
|
||||||
|
|
||||||
|
var input string
|
||||||
|
var lexed []string
|
||||||
|
var cmd string
|
||||||
|
var arg string
|
||||||
|
var val string
|
||||||
|
var augVal string
|
||||||
|
var augVals []string
|
||||||
|
var buf *bufio.Reader = bufio.NewReader(os.Stdin)
|
||||||
|
|
||||||
|
fmt.Fprint(os.Stderr, "INTERACTIVE MODE\nCmds: get, getall, match, set, quit\n")
|
||||||
|
breakCmd:
|
||||||
|
for {
|
||||||
|
cmd, arg, val = "", "", ""
|
||||||
|
|
||||||
|
fmt.Fprint(os.Stderr, "> ")
|
||||||
|
if input, err = buf.ReadString('\n'); err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
err = nil
|
||||||
|
break breakCmd
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(input) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if lexed, err = shlex.Split(input); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if lexed == nil || len(lexed) == 0 || len(lexed) > 3 {
|
||||||
|
fmt.Fprintf(os.Stderr, "Bad command: %#v\n", lexed)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cmd = lexed[0]
|
||||||
|
switch len(lexed) {
|
||||||
|
case 2:
|
||||||
|
arg = lexed[1]
|
||||||
|
case 3:
|
||||||
|
arg = lexed[1]
|
||||||
|
val = lexed[2]
|
||||||
|
}
|
||||||
|
switch cmd {
|
||||||
|
case "get":
|
||||||
|
if strings.TrimSpace(arg) == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "Missing argument")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if augVal, err = a.aug.Get(arg); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "!! ERROR !!\n%v\n", spew.Sdump(err))
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintln(os.Stderr, augVal)
|
||||||
|
case "getall":
|
||||||
|
if strings.TrimSpace(arg) == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "Missing argument")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if augVals, err = a.aug.GetAll(arg); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "!! ERROR !!\n%v\n", spew.Sdump(err))
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintln(os.Stderr, strings.Join(augVals, "\n"))
|
||||||
|
case "match":
|
||||||
|
if strings.TrimSpace(arg) == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "Missing argument")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if augVals, err = a.aug.Match(arg); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "!! ERROR !!\n%v\n", spew.Sdump(err))
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintln(os.Stderr, strings.Join(augVals, "\n"))
|
||||||
|
case "set":
|
||||||
|
if strings.TrimSpace(arg) == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "Missing argument")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(val) == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "Missing value")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = a.aug.Set(arg, val); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "!! ERROR !!\n%v\n", spew.Sdump(err))
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintln(os.Stderr, "Success!")
|
||||||
|
case "quit":
|
||||||
|
break breakCmd
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "Unknown command: %v\n", cmd)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
RecursiveInclude parses the configuration files belonging to Augeas lens name augLens,
|
||||||
|
searching for all occurrences of includeDirective, loading those files (if they exist),
|
||||||
|
and continuing so forth recursively, loading them into the Augeas file tree.
|
||||||
|
|
||||||
|
If any relative paths are found, they will be assumed to be relative to fsRoot ("/" if empty).
|
||||||
|
For e.g. Nginx, you almost absolutely want to set this to "/etc/nginx", but you really should
|
||||||
|
use absolute paths for every include in your configs if supported by the application; it will
|
||||||
|
lead to much less guesswork and much more accurate recursing/walking.
|
||||||
|
|
||||||
|
Some lens recursively load depending on their respective include directive(s) automatically;
|
||||||
|
some (such as the Nginx lens) do not.
|
||||||
|
|
||||||
|
For example for Nginx, augLens should be "Nginx". RecursiveInclude will then iterate over
|
||||||
|
/augeas/load/Nginx/incl (/augeas/load/<augLens>/incl), parsing each file for includeDirective
|
||||||
|
(the "include" keyword, in Nginx's case), check if it is already loaded in /augeas/load/<augLens>/incl,
|
||||||
|
adding it and reloading if not, and then scanning *that* file for includeDirective, etc.
|
||||||
|
|
||||||
|
An error will be returned if augLens is a nonexistent or not-loaded Augeas lens module.
|
||||||
|
|
||||||
|
Depending on how many files there are and whether globs vs. explicit filepaths are included, this may take a while.
|
||||||
|
*/
|
||||||
|
func (a *Aug) RecursiveInclude(augLens, includeDirective, fsRoot string) (err error) {
|
||||||
|
|
||||||
|
if err = a.addIncl(includeDirective, augLens, fsRoot, nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
addIncl is used by RecursiveInclude.
|
||||||
|
|
||||||
|
includeDirective, augLens, and fsRoot have the same meaning as in RecursiveInclude.
|
||||||
|
|
||||||
|
newInclPaths are new filesystem paths/Augeas-compatible glob patterns to load into the filetree and recurse into.
|
||||||
|
They may be nil, especially if the first run.
|
||||||
|
*/
|
||||||
|
func (a *Aug) addIncl(includeDirective, augLens string, fsRoot string, newInclPaths []string) (err error) {
|
||||||
|
|
||||||
|
var matches []string // Passed around set of Augeas matches.
|
||||||
|
var includes []string // Filepath(s)/glob(s) from fetching includeDirective in lensInclPath. These are internal to the application but are recursed.
|
||||||
|
var lensInclPath string // The path of the included paths in the tree. These are internal to Augeas, not the application.
|
||||||
|
var appendPath string // The path for new Augeas includes.
|
||||||
|
var match []string // A placeholder for iterating when populating includes.
|
||||||
|
var fpath string // A placeholder for finding the path of a conf file that contains an includeDirective.
|
||||||
|
var lensPath string = fmt.Sprintf(augLensTpl, augLens) // The path of the lens (augLens) itself.
|
||||||
|
var augErr *augeas.Error = new(augeas.Error) // We use this to skip "nonexistent" lens.
|
||||||
|
|
||||||
|
if fsRoot == "" {
|
||||||
|
fsRoot = "/"
|
||||||
|
}
|
||||||
|
if err = paths.RealPath(&fsRoot); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for strings.HasSuffix(lensPath, "/") {
|
||||||
|
lensPath = lensPath[:len(lensPath)-1]
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(lensPath, "/"+augInclTfm) {
|
||||||
|
lensPath = strings.TrimSuffix(lensPath, "/"+augInclTfm)
|
||||||
|
}
|
||||||
|
lensInclPath = fmt.Sprintf("%v/%v", lensPath, augInclTfm)
|
||||||
|
appendPath = fmt.Sprintf("%v/%v", lensInclPath, augAppendSuffix)
|
||||||
|
|
||||||
|
// First canonize paths.
|
||||||
|
if newInclPaths != nil && len(newInclPaths) > 0 {
|
||||||
|
// Existing includes. We don't return on an empty lensInclPath because
|
||||||
|
if matches, err = a.aug.Match(lensInclPath); err != nil {
|
||||||
|
if errors.As(err, augErr) && augErr.Code == augeas.NoMatch {
|
||||||
|
err = nil
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for idx, m := range matches {
|
||||||
|
if matches[idx], err = a.aug.Get(m); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize new include(s).
|
||||||
|
for idx, i := range newInclPaths {
|
||||||
|
if !filepath.IsAbs(i) {
|
||||||
|
newInclPaths[idx] = filepath.Join(fsRoot, i)
|
||||||
|
}
|
||||||
|
if err = paths.RealPath(&newInclPaths[idx]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't want to bother adding multiple incl's for the same path(s); it can negatively affect Augeas loads.
|
||||||
|
newInclPaths = dedupePaths(newInclPaths, matches)
|
||||||
|
|
||||||
|
// Add the new path(s) as Augeas include entries.
|
||||||
|
if newInclPaths != nil {
|
||||||
|
for _, fsPath := range newInclPaths {
|
||||||
|
if err = a.aug.Set(appendPath, fsPath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
And then load the new files into the tree.
|
||||||
|
This is done at the end as it takes WAYYY less time to just reload the tree
|
||||||
|
as a whole once you have more than, say, 30 files added at a time.
|
||||||
|
*/
|
||||||
|
if err = a.aug.Load(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We now fetch all values (filepath/globs) that match the includeDirective and recurse with those as new include files.
|
||||||
|
matches = nil
|
||||||
|
if includes, err = a.aug.GetAll(lensInclPath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, fsPath := range includes {
|
||||||
|
// This gets the Augeas filetree path, NOT the FS path...
|
||||||
|
if match, err = a.aug.Match(fmt.Sprintf(augFsTpl, fsPath, includeDirective)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if match == nil || len(match) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
For each directive match, we need to:
|
||||||
|
|
||||||
|
1.) normalize to an FS *file* path (strip augFsTree from the beginning
|
||||||
|
2.) walk backwards (via AugpathToFspath) until we find an actual file
|
||||||
|
3.) get the *dirname* of that file
|
||||||
|
4.) join the value (included file) to #3
|
||||||
|
IF
|
||||||
|
fsRoot == "/"
|
||||||
|
|
||||||
|
This very obviously breaks for applications with arbitrary roots (like Nginx including relative to /etc/nginx).
|
||||||
|
That's why we warn about it in Aug.RecursiveInclude. Caveat emptor.
|
||||||
|
*/
|
||||||
|
for idx, ftreePath := range match {
|
||||||
|
if fpath, err = a.aug.Get(ftreePath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if fsRoot == "/" {
|
||||||
|
if ftreePath, err = AugpathToFspath(
|
||||||
|
strings.TrimSuffix(ftreePath, augFsTree),
|
||||||
|
); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ftreePath != "" {
|
||||||
|
fpath = filepath.Join(filepath.Dir(ftreePath), fpath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match[idx] = fpath
|
||||||
|
}
|
||||||
|
matches = append(matches, match...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches != nil && len(matches) != 0 {
|
||||||
|
if err = a.addIncl(includeDirective, augLens, fsRoot, matches); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
41
auger/funcs_augflags.go
Normal file
41
auger/funcs_augflags.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package auger
|
||||||
|
|
||||||
|
import (
|
||||||
|
`honnef.co/go/augeas`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Eval returns an evaluated set of flags.
|
||||||
|
func (a *AugFlags) Eval() (augFlags augeas.Flag) {
|
||||||
|
|
||||||
|
augFlags = augeas.None
|
||||||
|
|
||||||
|
if a.Backup != nil && *a.Backup {
|
||||||
|
augFlags |= augeas.SaveBackup
|
||||||
|
}
|
||||||
|
if a.NewFile != nil && *a.NewFile {
|
||||||
|
augFlags |= augeas.SaveNewFile
|
||||||
|
}
|
||||||
|
if a.TypeCheck != nil && *a.TypeCheck {
|
||||||
|
augFlags |= augeas.TypeCheck
|
||||||
|
}
|
||||||
|
if a.NoDfltModLoad != nil && *a.NoDfltModLoad {
|
||||||
|
augFlags |= augeas.NoModlAutoload
|
||||||
|
}
|
||||||
|
if a.DryRun != nil && *a.DryRun {
|
||||||
|
augFlags |= augeas.SaveNoop
|
||||||
|
}
|
||||||
|
if a.NoTree != nil && *a.NoTree {
|
||||||
|
augFlags |= augeas.NoLoad
|
||||||
|
}
|
||||||
|
if a.NoAutoModLoad != nil && *a.NoAutoModLoad {
|
||||||
|
augFlags |= augeas.NoModlAutoload
|
||||||
|
}
|
||||||
|
if a.EnableSpan != nil && *a.EnableSpan {
|
||||||
|
augFlags |= augeas.EnableSpan
|
||||||
|
}
|
||||||
|
if a.NoErrClose != nil && *a.NoErrClose {
|
||||||
|
augFlags |= augeas.NoErrClose
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
62
auger/types.go
Normal file
62
auger/types.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package auger
|
||||||
|
|
||||||
|
import (
|
||||||
|
`honnef.co/go/augeas`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Aug is a wrapper around (honnef.co/go/)augeas.Augeas. Remember to call Aug.Close().
|
||||||
|
type Aug struct {
|
||||||
|
aug augeas.Augeas
|
||||||
|
}
|
||||||
|
|
||||||
|
// AugFlags contains flags to pass to the Augeas instance.
|
||||||
|
type AugFlags struct {
|
||||||
|
/*
|
||||||
|
Backup, if true, will enable Augeas backup mode (original files are saved with a .augsave suffix).
|
||||||
|
const: augeas.SaveBackup
|
||||||
|
*/
|
||||||
|
Backup *bool `toml:"Backup,omitempty"`
|
||||||
|
/*
|
||||||
|
NewFile, if true, will create new files (.augnew suffix) instead of overwriting the original file.
|
||||||
|
const: augeas.SaveNewFile
|
||||||
|
*/
|
||||||
|
NewFile *bool `toml:"NewFile,omitempty"`
|
||||||
|
/*
|
||||||
|
TypeCheck, if true, will perform a Lens typecheck.
|
||||||
|
HIGHLY UNRECOMMENDED; WILL INDUCE A HUGE FRONTLOAD.
|
||||||
|
const: augeas.TypeCheck
|
||||||
|
*/
|
||||||
|
TypeCheck *bool `toml:"TypeCheck,omitempty"`
|
||||||
|
/*
|
||||||
|
NoDfltModLoad, if true, will suppress loading the built-in/default modules.
|
||||||
|
Highly unrecommended, as we do not have a current way in the config to define load paths (yet).
|
||||||
|
const: augeas.NoStdinc
|
||||||
|
*/
|
||||||
|
NoDfltModLoad *bool `toml:"NoDfltModLoad,omitempty"`
|
||||||
|
/*
|
||||||
|
DryRun, if true, will make all saves NO-OPs.
|
||||||
|
const: augeas.SaveNoop
|
||||||
|
*/
|
||||||
|
DryRun *bool `toml:"DryRun,omitempty"`
|
||||||
|
/*
|
||||||
|
NoTree, if true, will not load the filetree automatically. Doesn't really affect this program.
|
||||||
|
const: augeas.NoLoad
|
||||||
|
*/
|
||||||
|
NoTree *bool `toml:"NoTree,omitempty"`
|
||||||
|
/*
|
||||||
|
NoAutoModLoad, if true, will supress automatically loading modules.
|
||||||
|
const: augeas.NoModlAutoload
|
||||||
|
*/
|
||||||
|
NoAutoModLoad *bool `toml:"NoAutoModLoad,omitempty"`
|
||||||
|
/*
|
||||||
|
EnableSpan, if true, will track span in input nodes (location information, essentially).
|
||||||
|
See https://augeas.net/docs/api.html#getting-the-span-of-a-node-related-to-a-file
|
||||||
|
const: augeas.EnableSpan
|
||||||
|
*/
|
||||||
|
EnableSpan *bool `toml:"EnableSpan,omitempty"`
|
||||||
|
/*
|
||||||
|
NoErrClose, if true, will suppress automatically closing on error.
|
||||||
|
const: augeas.NoErrClose
|
||||||
|
*/
|
||||||
|
NoErrClose *bool `toml:"NoErrClose,omitempty"`
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user