Skip to content

Commit

Permalink
Fix bug in SetPersistence, remove Unstash call from EditThing, add docs
Browse files Browse the repository at this point in the history
  • Loading branch information
sa6mwa committed Mar 23, 2023
1 parent 34e4548 commit 1cf17fb
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 49 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ func main() {
EncryptionKey: anystore.DefaultEncryptionKey,
Key: "configuration",
Thing: &configuration,
DefaultThing: defaultConf,
// Editor: "/usr/bin/emacs",
}); err != nil {
log.Fatal(err)
Expand Down
22 changes: 12 additions & 10 deletions anystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ an AES-128/192/256 encrypted GOB file with HMAC-SHA256 for
authentication and validation of the data. For access from multiple
instances sharing the same map, POSIX syscall.Flock is used to
exclusively lock a lockfile during save. There is no support for
Windows or other non-POSIX systems without flock(2).
Windows or other non-POSIX systems missing flock(2).
Example:
Expand Down Expand Up @@ -107,7 +107,8 @@ variables, using Stash, Unstash and EditThing is simple...
EncryptionKey: anystore.DefaultEncryptionKey,
Key: "configuration",
Thing: &configuration,
}, defaultConf); err != nil {
DefaultThing: defaultConf,
}); err != nil {
log.Fatal(err)
}
Expand All @@ -118,7 +119,7 @@ variables, using Stash, Unstash and EditThing is simple...
Key: "configuration",
Thing: &configuration,
// Editor: "/usr/bin/emacs",
}, defaultConf); err != nil {
}); err != nil {
log.Fatal(err)
}
}
Expand Down Expand Up @@ -161,7 +162,7 @@ const DefaultPersistenceFile string = "~/.config/anystore/anystore.db"
var (
ErrKeyLength error = errors.New("key length must be 16, 24 or 32 (for AES-128, AES-192 or AES-256)")
ErrWroteTooLittle error = errors.New("wrote too few bytes")
ErrHMACValidationFailed error = errors.New("HMAC validation failed (corrupt message or wrong encryption key)")
ErrHMACValidationFailed error = errors.New("HMAC validation failed (corrupt data or wrong encryption key)")
)

// A thread-safe key/value store using string as key and interface{} (any) as
Expand Down Expand Up @@ -310,13 +311,14 @@ func (a *anyStore) SetPersistenceFile(file string) (AnyStore, error) {
file = filepath.Join(dirname, file[2:])
}
dir, _ := filepath.Split(file)
if _, err := os.Stat(file); err != nil {
if errors.Is(err, os.ErrNotExist) {
if err := os.MkdirAll(dir, 0777); err != nil {
return a, err

if dir != "" && dir != "." && dir != ".." {
if _, err := os.Stat(dir); err != nil {
if errors.Is(err, os.ErrNotExist) {
if err := os.MkdirAll(dir, 0777); err != nil {
return a, err
}
}
} else {
return a, err
}
}

Expand Down
17 changes: 13 additions & 4 deletions editstash.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,23 @@ var (
ErrNotATerminal error = errors.New("os.Stdin is not a terminal")
)

// EditThing is an interactive variant of Stash, editing the Thing
// before Stashing it. EditThing does not Unstash before editing, it
// uses the actual Thing. If you need to load Thing from persistence
// before editing, call Unstash prior to EditThing (make sure Thing is
// zero/new/empty before Unstash). EditThing uses encoding/json for
// editing, beware if a user enters "null" on a non-pointer field and
// saves, encoding/json will ignore it effectively using the original
// value without producing an error. Similarily, if a user removes a
// field while editing, the original value will be retained.
//
// Environment variable EDITOR is used as a json editor falling back
// to conf.Editor and finally one of the DefaultEditors.
func EditThing(conf *StashConfig) error {
if !IsUnixTerminal(os.Stdin) {
return ErrNotATerminal
}

if err := Unstash(conf); err != nil {
return err
}

executables := []string{}

envEditor := os.Getenv("EDITOR")
Expand Down Expand Up @@ -106,6 +114,7 @@ func EditThing(conf *StashConfig) error {
goto retryQuestion
}
}

if err := Stash(conf); err != nil {
return err
}
Expand Down
7 changes: 3 additions & 4 deletions examples/edit-stash-2/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
type MyConfig struct {
ListenAddress string
Username string
Token string
Token *string
Endpoints []*Endpoint
}

Expand All @@ -27,13 +27,13 @@ func main() {
defaultConf := &MyConfig{
ListenAddress: "0.0.0.0:1234",
Username: "superuser",
Token: "abc123",
Token: &[]string{"abc123"}[0],
Endpoints: []*Endpoint{
{ID: 1, Name: "Endpoint 1", URL: "https://endpoint1.local"},
{ID: 2, Name: "Endpoint 2", URL: "https://endpoint2.local"},
},
}
file := "~/.myconfigfile.db"
file := "~/.anystore/examples-edit-stash-2.db"

var configuration MyConfig

Expand All @@ -53,7 +53,6 @@ func main() {
EncryptionKey: anystore.DefaultEncryptionKey,
Key: "configuration",
Thing: &configuration,
DefaultThing: defaultConf,
// Editor: "/usr/bin/emacs",
}); err != nil {
log.Fatal(err)
Expand Down
27 changes: 13 additions & 14 deletions examples/edit-stash/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ type Component struct {
func main() {
log.SetFlags(log.Ldate | log.Ltime | log.LUTC | log.Lshortfile)

thingToEdit := &Thing{
actualThing := &Thing{}

defaultThing := &Thing{
Name: &[]string{"Hello World"}[0],
Number: 32,
Description: "There is not much to a Hello World thing.",
Components: []*Component{
{ID: 1, Name: "Component one"},
Expand All @@ -32,24 +33,22 @@ func main() {
},
}

defaultThing := &Thing{
Name: &[]string{"default"}[0],
Description: "the default thing",
Components: []*Component{
{ID: 1, Name: "hello"},
},
}

file := "~/.testing-edit-stash.db"
file := "~/.anystore/examples-edit-stash.db"

if err := anystore.EditThing(&anystore.StashConfig{
conf := &anystore.StashConfig{
File: file,
GZip: true,
EncryptionKey: anystore.DefaultEncryptionKey,
Key: "configuration",
Thing: thingToEdit,
Thing: actualThing,
DefaultThing: defaultThing,
}); err != nil {
}

if err := anystore.Unstash(conf); err != nil {
log.Fatal(err)
}

if err := anystore.EditThing(conf); err != nil {
log.Fatal(err)
}

Expand Down
60 changes: 44 additions & 16 deletions stash.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,55 @@ var (
// on success and failure. If File is an empty string (== "") and Writer is not
// nil, Stash will only write to the io.Writer.
type StashConfig struct {
File string // AnyStore DB file, if empty, use Reader/Writer
Reader io.Reader // If nil, use File for Unstash, if not, prefer Reader over File
Writer io.WriteCloser // If nil, use File for Stash, if not, write to both Writer and File (if File is not an empty string)
GZip bool // GZip data before encryption
EncryptionKey string // 16, 24 or 32 byte long base64-encoded string
Key string // Key name where to store Thing
Thing any // Usually a struct with data, properties, configuration, etc
DefaultThing any // If Unstash get os.ErrNotExist or key is missing, use this as default Thing if not nil
Editor string // Editor to use to edit Thing as JSON
// AnyStore DB file, if empty, use Reader/Writer.
File string

// If nil, use File for Unstash, if not, prefer Reader over File.
Reader io.Reader

// If nil, use File for Stash, if not, write to both Writer and File
// (if File is not an empty string).
Writer io.WriteCloser

// GZip data before encryption.
GZip bool

// 16, 24 or 32 byte long base64-encoded string.
EncryptionKey string

// Key name where to store Thing.
Key string

// Thing is usually a struct with data, properties, configuration,
// etc. Must be a pointer. On Unstash (and EditThing), Thing should
// be zeroed (new/empty) or the result of underlying gob.Decode is
// unpredictable.
Thing any

// If Unstash get os.ErrNotExist or key is missing, use this as
// default Thing if not nil. Must be a pointer.
DefaultThing any

// Editor to use to edit Thing as JSON.
Editor string
}

// "stash, verb. to put (something of future use or value) in a safe or secret
// place"
//
// Unstash loads a "Thing" from a place specified in a StashConfig, usually an
// AnyStore DB file, but the Stash and Unstash functions also support io.Reader
// and io.Writer (io.WriteCloser). Reader/writer is essentially an in-memory
// version of the physical DB file, Unstash does io.ReadAll into memory in order
// to decrypt and de-GOB the data. A previous file-Stash command can be
// Unstashed via the io.Reader. Unstash prefers io.Reader when both
// StashConfig.File and StashConfig.Reader are defined.
// Unstash loads a "Thing" from a place specified in a StashConfig,
// usually an AnyStoreDB file, into the object pointed to by the Thing
// field in conf. Thing should be an uninitialized/new/empty object
// for predictable results, full overwrite of any present value is not
// guaranteed.
//
// The Stash and Unstash functions also support io.Reader and
// io.Writer (io.WriteCloser). Reader/writer is essentially an
// in-memory version of the physical DB file, Unstash does io.ReadAll
// into memory in order to decrypt and de-GOB the data. A previous
// file-Stash command can be Unstashed via the io.Reader. Unstash
// prefers io.Reader when both StashConfig.File and StashConfig.Reader
// are defined.
//
// StashConfig instructs how functions anystore.Stash and anystore.Unstash
// should save/load a "stash". If Reader is not nil and File is not an empty
Expand Down
60 changes: 60 additions & 0 deletions stash_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package anystore_test

import (
"encoding/json"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -582,3 +583,62 @@ func ExampleStash_reader_writer() {
// Output:
// Hello world
}

func ExampleStash() {
type Endpoint struct {
Name string
URL string
}
type MyConfig struct {
Username string
Token *string
Endpoints []Endpoint
}

defaultConfig := &MyConfig{
Username: "anonymous",
Endpoints: []Endpoint{
{
Name: "Default",
URL: "https://localhost:8081/default",
},
},
}

var configuration MyConfig

conf := &anystore.StashConfig{
File: "~/.anystore/stash-example-01.db",
GZip: true,
EncryptionKey: anystore.DefaultEncryptionKey,
Key: "configuration",
Thing: &configuration,
DefaultThing: defaultConfig,
}

// First, load persisted configuration from file. If none found, use
// defaultConfig as values for configuration.

if err := anystore.Unstash(conf); err != nil {
log.Fatal(err)
}

// Override if something has been provided from the command line, etc.

token, ok := os.LookupEnv("TOKEN")
if ok {
configuration.Token = &token
}

// Persist configuration to disk.

if err := anystore.Stash(conf); err != nil {
log.Fatal(err)
}

j, err := json.MarshalIndent(&configuration, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(j))
}

0 comments on commit 1cf17fb

Please sign in to comment.