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
|
||||
-- incoprporated separately; https://git.r00t2.io/r00t2/PWGen (import r00t2.io/pwgen)
|
||||
|
||||
- auger needs to be build-constrained to linux.
|
||||
|
||||
- unit tests
|
||||
|
||||
- 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