fixes and additions:

- add ANYDB_PASSWORD env var
- add config file support, including buckets dict
- finalized custom bucket support
- fine tuned info support
This commit is contained in:
2024-12-22 13:07:53 +01:00
committed by T.v.Dein
parent 24240b85f2
commit 8687e084bf
11 changed files with 258 additions and 32 deletions

View File

@@ -95,9 +95,29 @@ anydb import -r backup.json
# you can encrypt entries. anydb asks for a passphrase
# and will do the same when you retrieve the key using the
# get command.
# get command. anydb will ask you interactively for a password
anydb set mypassword -e
# but you can provide it via an environment variable too
ANYDB_PASSWORD=foo anydb set -e secretkey blahblah
# too tiresome to add -e every time you add an entry?
# use a per bucket config
cat ~/.config/anydb/anydb.toml
[buckets.data]
encrypt = true
anydb set foo bar # will be encrypted
# speaking of buckets, you can use different buckets
anydb -b test set foo bar
# and speaking of configs, you can place a config file at these places:
# ~/.config/anydb/anydb.toml
# ~/.anydb.toml
# anydb.toml (current directory)
# or specify one using -c <filename>
# look at example.toml
# using template output mode you can freely design how to print stuff
# here, we print the values in CSV format ONLY if they have some tag
anydb ls -m template -T "{{ if .Tags }}{{ .Key }},{{ .Value }},{{ .Created}}{{ end }}"

View File

@@ -1,7 +1,3 @@
- repl
- mime-type => exec app + value
- custom buckets (like skate: key@bucket or key+bucket)
- encryption per bucket, one key for all entries (in that bucket)
- `-b bucket`, use B for encrypted bucke?
- [edit command](https://github.com/TLINDEN/rpnc/blob/master/command.go#L249)
- env var for password

View File

@@ -1,4 +1,4 @@
.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.40)
.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.42)
.\"
.\" Standard preamble:
.\" ========================================================================
@@ -133,7 +133,7 @@
.\" ========================================================================
.\"
.IX Title "ANYDB 1"
.TH ANYDB 1 "2024-12-18" "1" "User Commands"
.TH ANYDB 1 "2024-12-22" "1" "User Commands"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l

View File

@@ -50,9 +50,11 @@ type DbEntry struct {
}
type BucketInfo struct {
Name string
Keys int
Size int
Name string
Keys int
Size int
Sequence uint64
Stats bolt.BucketStats
}
type DbInfo struct {
@@ -264,6 +266,7 @@ func (db *DB) Get(attr *DbAttr) (*DbEntry, error) {
jsonentry := bucket.Get([]byte(attr.Key))
if jsonentry == nil {
// FIXME: shall we return a key not found error?
return nil
}
@@ -373,16 +376,23 @@ func (db *DB) Info() (*DbInfo, error) {
info := &DbInfo{Path: db.Dbfile}
err := db.DB.View(func(tx *bolt.Tx) error {
err := tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
binfo := BucketInfo{Name: string(name)}
err := tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
stats := bucket.Stats()
binfo := BucketInfo{
Name: string(name),
Sequence: bucket.Sequence(),
Keys: stats.KeyN,
Stats: bucket.Stats(),
}
err := bucket.ForEach(func(key, entry []byte) error {
binfo.Size += len(entry)
binfo.Keys++
binfo.Size += len(entry) + len(key)
return nil
})
if err != nil {
return err
return fmt.Errorf("failed to read keys: %w", err)
}
info.Buckets = append(info.Buckets, binfo)
@@ -391,11 +401,12 @@ func (db *DB) Info() (*DbInfo, error) {
})
if err != nil {
return err
return fmt.Errorf("failed to read from DB: %w", err)
}
return nil
})
return info, err
}

View File

@@ -1,20 +1,114 @@
/*
Copyright © 2024 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cfg
import "github.com/tlinden/anydb/app"
import (
"fmt"
"io"
"os"
"github.com/pelletier/go-toml"
"github.com/tlinden/anydb/app"
"github.com/tlinden/anydb/common"
)
var Version string = "v0.0.4"
type BucketConfig struct {
Encrypt bool
}
type Config struct {
Debug bool
Dbfile string
Dbbucket string
Dbbucket string
Template string
Mode string // wide, table, yaml, json
NoHeaders bool
NoHumanize bool
Encrypt bool
DB *app.DB
File string
Tags []string
Encrypt bool // one entry
Listen string
Buckets map[string]BucketConfig // config file only
Tags []string // internal
DB *app.DB // internal
File string // internal
}
func (conf *Config) GetConfig(files []string) error {
for _, file := range files {
if err := conf.ParseConfigFile(file); err != nil {
return err
}
}
return nil
}
func (conf *Config) ParseConfigFile(file string) error {
if !common.FileExists(file) {
return nil
}
fd, err := os.OpenFile(file, os.O_RDONLY, 0600)
if err != nil {
return fmt.Errorf("failed to open config file %s: %w", file, err)
}
data, err := io.ReadAll(fd)
if err != nil {
return fmt.Errorf("failed to read from config file: %w", err)
}
add := Config{}
err = toml.Unmarshal(data, &add)
if err != nil {
return fmt.Errorf("failed to unmarshall toml: %w", err)
}
// merge new values into existing config
switch {
case add.Debug != conf.Debug:
conf.Debug = add.Debug
case add.Dbfile != "":
conf.Dbfile = add.Dbfile
case add.Dbbucket != "":
conf.Dbbucket = add.Dbbucket
case add.Template != "":
conf.Template = add.Template
case add.NoHeaders != conf.NoHeaders:
conf.NoHeaders = add.NoHeaders
case add.NoHumanize != conf.NoHumanize:
conf.NoHumanize = add.NoHumanize
case add.Encrypt != conf.Encrypt:
conf.Encrypt = add.Encrypt
case add.Listen != "":
conf.Listen = add.Listen
}
// only supported in config files
conf.Buckets = add.Buckets
// determine bucket encryption mode
for name, bucket := range conf.Buckets {
if name == conf.Dbbucket {
conf.Encrypt = bucket.Encrypt
}
}
return nil
}

View File

@@ -65,7 +65,7 @@ func Set(conf *cfg.Config) *cobra.Command {
// encrypt?
if conf.Encrypt {
pass, err := app.AskForPassword()
pass, err := getPassword()
if err != nil {
return err
}
@@ -118,7 +118,7 @@ func Get(conf *cfg.Config) *cobra.Command {
}
if entry.Encrypted {
pass, err := app.AskForPassword()
pass, err := getPassword()
if err != nil {
return err
}
@@ -371,7 +371,26 @@ func Info(conf *cfg.Config) *cobra.Command {
},
}
cmd.PersistentFlags().StringVarP(&conf.Listen, "listen", "l", "localhost:8787", "host:port")
cmd.PersistentFlags().BoolVarP(&conf.NoHumanize, "no-human", "N", false, "do not translate to human readable values")
return cmd
}
func getPassword() ([]byte, error) {
var pass []byte
envpass := os.Getenv("ANYDB_PASSWORD")
if envpass == "" {
readpass, err := app.AskForPassword()
if err != nil {
return nil, err
}
pass = readpass
} else {
pass = []byte(envpass)
}
return pass, nil
}

View File

@@ -22,6 +22,7 @@ import (
"os"
"path/filepath"
"github.com/alecthomas/repr"
"github.com/spf13/cobra"
"github.com/tlinden/anydb/app"
"github.com/tlinden/anydb/cfg"
@@ -45,10 +46,22 @@ func completion(cmd *cobra.Command, mode string) error {
func Execute() {
var (
conf cfg.Config
configfile string
ShowVersion bool
ShowCompletion string
)
home, err := os.UserHomeDir()
if err != nil {
panic(err)
}
SearchConfigs := []string{
filepath.Join(home, ".config", "anydb", "anydb.toml"),
filepath.Join(home, ".anydb.toml"),
"anydb.toml",
}
var rootCmd = &cobra.Command{
Use: "anydb <command> [options]",
Short: "anydb",
@@ -61,6 +74,21 @@ func Execute() {
conf.DB = db
var configs []string
if configfile != "" {
configs = []string{configfile}
} else {
configs = SearchConfigs
}
if err := conf.GetConfig(configs); err != nil {
return err
}
if conf.Debug {
repr.Println(conf)
}
return nil
},
@@ -82,11 +110,6 @@ func Execute() {
},
}
home, err := os.UserHomeDir()
if err != nil {
panic(err)
}
// options
rootCmd.PersistentFlags().BoolVarP(&ShowVersion, "version", "v", false, "Print program version")
rootCmd.PersistentFlags().BoolVarP(&conf.Debug, "debug", "d", false, "Enable debugging")
@@ -94,6 +117,7 @@ func Execute() {
filepath.Join(home, ".config", "anydb", "default.db"), "DB file to use")
rootCmd.PersistentFlags().StringVarP(&conf.Dbbucket, "bucket", "b",
app.BucketData, "use other bucket (default: "+app.BucketData+")")
rootCmd.PersistentFlags().StringVarP(&configfile, "config", "c", "", "toml config file")
rootCmd.AddCommand(Set(&conf))
rootCmd.AddCommand(List(&conf))

36
common/io.go Normal file
View File

@@ -0,0 +1,36 @@
/*
Copyright © 2024 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package common
import "os"
func CleanError(file string, err error) error {
// remove given [backup] file and forward the given error
os.Remove(file)
return err
}
func FileExists(filename string) bool {
info, err := os.Stat(filename)
if err != nil {
// return false on any error
return false
}
return !info.IsDir()
}

1
go.mod
View File

@@ -16,6 +16,7 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/spf13/cobra v1.8.1 // indirect

2
go.sum
View File

@@ -45,6 +45,8 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=

View File

@@ -21,8 +21,10 @@ import (
"fmt"
"io"
"os"
"reflect"
"strings"
"github.com/dustin/go-humanize"
"github.com/tlinden/anydb/app"
"github.com/tlinden/anydb/cfg"
"golang.org/x/term"
@@ -96,12 +98,33 @@ func WriteFile(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.
}
func Info(writer io.Writer, conf *cfg.Config, info *app.DbInfo) error {
// repr.Println(info)
fmt.Fprintf(writer, "Database: %s\n", info.Path)
for _, bucket := range info.Buckets {
if conf.NoHumanize {
fmt.Fprintf(
writer,
"%19s: %s\n%19s: %d\n%19s: %d\n",
"Bucket", bucket.Name,
"Size", bucket.Size,
"Keys", bucket.Keys)
} else {
fmt.Fprintf(
writer,
"%19s: %s\n%19s: %s\n%19s: %d\n",
"Bucket", bucket.Name,
"Size", humanize.Bytes(uint64(bucket.Size)),
"Keys", bucket.Keys)
}
fmt.Fprintf(writer, "Bucket: %s\n Size: %d\n Keys: %d\n\n", bucket.Name, bucket.Size, bucket.Keys)
if conf.Debug {
val := reflect.ValueOf(&bucket.Stats).Elem()
for i := 0; i < val.NumField(); i++ {
fmt.Fprintf(writer, "%19s: %d\n", val.Type().Field(i).Name, val.Field(i))
}
}
fmt.Fprintln(writer)
}
return nil
}