Add ability to look in system-wide directories for configuration files

This commit is contained in:
Sean E. Russell 2020-03-07 09:03:25 -06:00
parent 9188b14094
commit 9ebcff9b78
7 changed files with 120 additions and 58 deletions

View File

@ -13,6 +13,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
> - **Fixed**: for any bug fixes.
> - **Security**: in case of vulnerabilities.
## [3.6.0] - ???
### Added
- Adds support for system-wide configurations. This improves support for package maintainers.
### Changed
- Log files stored in \$XDG_CACHE_HOME; DATA, CONFIG, CACHE, and RUNTIME are the only directories specified by the FreeDesktop spec.
### Removed
- configdir, logdir, and logfile options in the config file are no longer used. gotop looks for a configuration file, layouts, and colorschemes in the following order: command-line; `pwd`; user-home, and finally a system-wide path. The paths depend on the OS and whether XDG is in use.
## [3.5.1] - ??
### Fixed

View File

@ -17,12 +17,12 @@ import (
docopt "github.com/docopt/docopt.go"
ui "github.com/gizak/termui/v3"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/shibukawa/configdir"
"github.com/xxxserxxx/gotop/v3"
"github.com/xxxserxxx/gotop/v3/colorschemes"
"github.com/xxxserxxx/gotop/v3/layout"
"github.com/xxxserxxx/gotop/v3/logging"
"github.com/xxxserxxx/gotop/v3/utils"
w "github.com/xxxserxxx/gotop/v3/widgets"
)
@ -73,6 +73,7 @@ Options:
-x, --export=PORT Enable metrics for export on the specified port.
-X, --extensions=NAMES Enables the listed extensions. This is a comma-separated list without the .so suffix. The current and config directories will be searched.
--test Runs tests and exits with success/failure code
--print-paths List out the paths that gotop will look for gotop.conf, layouts, color schemes, and extensions
Built-in layouts:
@ -158,6 +159,14 @@ Colorschemes:
if val, _ := args["--test"]; val != nil {
conf.Test = val.(bool)
}
if args["--print-paths"].(bool) {
paths := make([]string, 0)
for _, d := range conf.ConfigDir.QueryFolders(configdir.All) {
paths = append(paths, d.Path)
}
fmt.Println(strings.Join(paths, "\n"))
os.Exit(0)
}
return nil
}
@ -347,12 +356,10 @@ func eventLoop(c gotop.Config, grid *layout.MyGrid) {
}
func makeConfig() gotop.Config {
ld := utils.GetLogDir(appName)
cd := utils.GetConfigDir(appName)
cd := configdir.New("", appName)
cd.LocalPath, _ = filepath.Abs(".")
conf = gotop.Config{
ConfigDir: cd,
LogDir: ld,
LogFile: "errors.log",
GraphHorizontalScale: 7,
HelpVisible: false,
UpdateInterval: time.Second,
@ -371,27 +378,29 @@ func makeConfig() gotop.Config {
func main() {
// Set up default config
conf := makeConfig()
// Parse the config file
cfn := filepath.Join(conf.ConfigDir, "gotop.conf")
if cf, err := os.Open(cfn); err == nil {
err := gotop.Parse(cf, &conf)
if err != nil {
stderrLogger.Fatalf("error parsing config file %v", err)
}
// Find the config file; look in (1) local, (2) user, (3) global
err := conf.Load()
if err != nil {
stderrLogger.Printf("failed to parse config file: %s", err)
}
// Override with command line arguments
err := parseArgs(&conf)
err = parseArgs(&conf)
if err != nil {
stderrLogger.Fatalf("failed to parse cli args: %v", err)
}
logfile, err := logging.New(conf)
if err != nil {
stderrLogger.Fatalf("failed to setup log file: %v", err)
fmt.Printf("failed to setup log file: %v\n", err)
os.Exit(1)
}
defer logfile.Close()
lstream := getLayout(conf)
lstream, err := getLayout(conf)
if err != nil {
fmt.Printf("failed to find layou: %s\n", err)
os.Exit(1)
}
ly := layout.ParseLayout(lstream)
loadExtensions(conf)
@ -439,30 +448,34 @@ func main() {
eventLoop(conf, grid)
}
func getLayout(conf gotop.Config) io.Reader {
func getLayout(conf gotop.Config) (io.Reader, error) {
switch conf.Layout {
case "-":
return os.Stdin
return os.Stdin, nil
case "default":
return strings.NewReader(defaultUI)
return strings.NewReader(defaultUI), nil
case "minimal":
return strings.NewReader(minimalUI)
return strings.NewReader(minimalUI), nil
case "battery":
return strings.NewReader(batteryUI)
return strings.NewReader(batteryUI), nil
case "procs":
return strings.NewReader(procsUI)
return strings.NewReader(procsUI), nil
case "kitchensink":
return strings.NewReader(kitchensink)
return strings.NewReader(kitchensink), nil
default:
fp := filepath.Join(conf.ConfigDir, conf.Layout)
fin, err := os.Open(fp)
if err != nil {
fin, err = os.Open(conf.Layout)
if err != nil {
log.Fatalf("Unable to open layout file %s or ./%s", fp, conf.Layout)
folder := conf.ConfigDir.QueryFolderContainsFile(conf.Layout)
if folder == nil {
paths := make([]string, 0)
for _, d := range conf.ConfigDir.QueryFolders(configdir.Existing) {
paths = append(paths, d.Path)
}
return nil, fmt.Errorf("unable find layout file %s in %s", conf.Layout, strings.Join(paths, ", "))
}
return fin
lo, err := folder.ReadFile(conf.Layout)
if err != nil {
return nil, err
}
return strings.NewReader(string(lo)), nil
}
}
@ -471,18 +484,17 @@ func loadExtensions(conf gotop.Config) {
for _, ex := range conf.Extensions {
exf := ex + ".so"
fn := exf
_, err := os.Stat(fn)
if err != nil && os.IsNotExist(err) {
log.Printf("no plugin %s found in current directory", fn)
fn = filepath.Join(conf.ConfigDir, exf)
_, err = os.Stat(fn)
if err != nil || os.IsNotExist(err) {
hasError = true
log.Printf("no plugin %s found in config directory", fn)
continue
folder := conf.ConfigDir.QueryFolderContainsFile(fn)
if folder == nil {
paths := make([]string, 0)
for _, d := range conf.ConfigDir.QueryFolders(configdir.Existing) {
paths = append(paths, d.Path)
}
log.Printf("unable find extension %s in %s", fn, strings.Join(paths, ", "))
continue
}
p, err := plugin.Open(fn)
fp := filepath.Join(folder.Path, fn)
p, err := plugin.Open(fp)
if err != nil {
hasError = true
log.Printf(err.Error())
@ -494,7 +506,6 @@ func loadExtensions(conf gotop.Config) {
log.Printf(err.Error())
continue
}
initFunc, ok := init.(func())
if !ok {
hasError = true
@ -505,7 +516,12 @@ func loadExtensions(conf gotop.Config) {
}
if hasError {
ui.Close()
fmt.Printf("Error initializing requested plugins; check the log file %s\n", filepath.Join(conf.ConfigDir, conf.LogFile))
folder := conf.ConfigDir.QueryFolderContainsFile(logging.LOGFILE)
if folder == nil {
fmt.Printf("error initializing requested plugins\n")
} else {
fmt.Printf("error initializing requested plugins; check the log file %s\n", filepath.Join(folder.Path, logging.LOGFILE))
}
os.Exit(1)
}
}

View File

@ -3,8 +3,10 @@ package colorschemes
import (
"encoding/json"
"fmt"
"io/ioutil"
"path/filepath"
"strings"
"github.com/shibukawa/configdir"
)
var registry map[string]Colorscheme
@ -15,7 +17,7 @@ func init() {
}
}
func FromName(confDir string, c string) (Colorscheme, error) {
func FromName(confDir configdir.ConfigDir, c string) (Colorscheme, error) {
cs, ok := registry[c]
if !ok {
cs, err := getCustomColorscheme(confDir, c)
@ -34,12 +36,20 @@ func register(name string, c Colorscheme) {
}
// getCustomColorscheme tries to read a custom json colorscheme from <configDir>/<name>.json
func getCustomColorscheme(confDir string, name string) (Colorscheme, error) {
func getCustomColorscheme(confDir configdir.ConfigDir, name string) (Colorscheme, error) {
var cs Colorscheme
filePath := filepath.Join(confDir, name+".json")
dat, err := ioutil.ReadFile(filePath)
fn := name + ".json"
folder := confDir.QueryFolderContainsFile(fn)
if folder == nil {
paths := make([]string, 0)
for _, d := range confDir.QueryFolders(configdir.Existing) {
paths = append(paths, d.Path)
}
return cs, fmt.Errorf("failed to find colorscheme file %s in %s", fn, strings.Join(paths, ", "))
}
dat, err := folder.ReadFile(fn)
if err != nil {
return cs, fmt.Errorf("failed to read colorscheme file: %v", err)
return cs, fmt.Errorf("failed to read colorscheme file %s: %v", filepath.Join(folder.Path, fn), err)
}
err = json.Unmarshal(dat, &cs)
if err != nil {

View File

@ -4,19 +4,20 @@ import (
"bufio"
"fmt"
"io"
"log"
"os"
"strconv"
"strings"
"time"
"github.com/shibukawa/configdir"
"github.com/xxxserxxx/gotop/v3/colorschemes"
"github.com/xxxserxxx/gotop/v3/widgets"
)
// TODO: test, build, release [#119] [#120] [#121]
type Config struct {
ConfigDir string
LogDir string
LogFile string
ConfigDir configdir.ConfigDir
GraphHorizontalScale int
HelpVisible bool
@ -36,7 +37,19 @@ type Config struct {
Test bool
}
func Parse(in io.Reader, conf *Config) error {
func (conf *Config) Load() error {
var in io.Reader
cfn := "gotop.conf"
folder := conf.ConfigDir.QueryFolderContainsFile(cfn)
if folder != nil {
if cf, err := os.Open(cfn); err == nil {
defer cf.Close()
} else {
return err
}
} else {
return nil
}
r := bufio.NewScanner(in)
var lineNo int
for r.Scan() {
@ -50,11 +63,11 @@ func Parse(in io.Reader, conf *Config) error {
default:
return fmt.Errorf("invalid config option %s", key)
case "configdir":
conf.ConfigDir = kv[1]
log.Printf("configdir is deprecated. Ignored configdir=%s", kv[1])
case "logdir":
conf.LogDir = kv[1]
log.Printf("logdir is deprecated. Ignored logdir=%s", kv[1])
case "logfile":
conf.LogFile = kv[1]
log.Printf("logfile is deprecated. Ignored logfile=%s", kv[1])
case "graphhorizontalscale":
iv, err := strconv.Atoi(kv[1])
if err != nil {

1
go.mod
View File

@ -9,6 +9,7 @@ require (
github.com/mattn/go-runewidth v0.0.4
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/prometheus/client_golang v1.4.1
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0
github.com/shirou/gopsutil v2.18.11+incompatible
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect
github.com/stretchr/testify v1.4.0

2
go.sum
View File

@ -83,6 +83,8 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 h1:Xuk8ma/ibJ1fOy4Ee11vHhUFHQNpHhrBneOCNHVXS5w=
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0/go.mod h1:7AwjWCpdPhkSmNAgUv5C7EJ4AbmjEB3r047r3DXWu3Y=
github.com/shirou/gopsutil v2.18.11+incompatible h1:PMFTKnFTr/YTRW5rbLK4vWALV3a+IGXse5nvhSjztmg=
github.com/shirou/gopsutil v2.18.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U=

View File

@ -11,16 +11,22 @@ import (
"github.com/xxxserxxx/gotop/v3"
)
const (
LOGFILE = "errors.log"
)
func New(c gotop.Config) (io.WriteCloser, error) {
// create the log directory
if err := os.MkdirAll(c.LogDir, 0755); err != nil {
return nil, fmt.Errorf("failed to make the log directory: %v", err)
cache := c.ConfigDir.QueryCacheFolder()
err := cache.MkdirAll()
if err != nil && !os.IsExist(err) {
return nil, err
}
w := &RotateWriter{
filename: filepath.Join(c.LogDir, c.LogFile),
filename: filepath.Join(cache.Path, LOGFILE),
maxLogSize: c.MaxLogSize,
}
err := w.rotate()
err = w.rotate()
if err != nil {
return nil, err
}