go_sysutils/auger/funcs_aug.go
brent saner c0c924b75a
v1.3.0
ADDED:
* auger, some convenience funcs around Augeas.
2024-06-20 05:26:56 -04:00

295 lines
8.8 KiB
Go

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
}