commit af1b350bac0db5f0bb5533a4b19e85474c4538aa
parent 772d314ed11a786242b5f4acff88268c87808a7b
Author: Sean Enck <sean@ttypty.com>
Date: Sat, 16 Jul 2022 10:48:19 -0400
WIP on restructure
Diffstat:
14 files changed, 273 insertions(+), 119 deletions(-)
diff --git a/Makefile b/Makefile
@@ -11,7 +11,7 @@ SOURCE := $(shell find . -type f -name "*.go")
all: $(TARGETS)
-$(TARGETS): $(SOURCE)
+$(TARGETS): $(SOURCE) go.*
go build -ldflags '-X main.version=$(VERSION) -X main.libExec=$(LIBEXEC) -X main.mainExe=$(MAIN)' -trimpath -buildmode=pie -mod=readonly -modcacherw -o $@ cmd/$(shell basename $@)/main.go
$(TESTDIR): $(TARGETS)
diff --git a/cmd/lb/main.go b/cmd/lb/main.go
@@ -11,13 +11,10 @@ import (
"strings"
"time"
- "github.com/enckse/lockbox/internal"
"github.com/enckse/lockbox/internal/cli"
"github.com/enckse/lockbox/internal/clipboard"
-)
-
-const (
- postStep = "post"
+ "github.com/enckse/lockbox/internal/misc"
+ "github.com/enckse/lockbox/internal/store"
)
var (
@@ -25,25 +22,17 @@ var (
libExec = ""
)
-type (
- // Dump represents the output structure from a JSON dump.
- Dump struct {
- Path string `json:"path,omitempty"`
- Value string `json:"value"`
- }
-)
-
func getEntry(store string, args []string, idx int) string {
if len(args) != idx+1 {
- internal.Die("invalid entry given", errors.New("specific entry required"))
+ misc.Die("invalid entry given", errors.New("specific entry required"))
}
- return filepath.Join(store, args[idx]) + internal.Extension
+ return filepath.Join(store, args[idx]) + store.Extension
}
func getExecutable() string {
exe, err := os.Executable()
if err != nil {
- internal.Die("unable to get exe", err)
+ misc.Die("unable to get exe", err)
}
return exe
}
@@ -51,7 +40,7 @@ func getExecutable() string {
func main() {
args := os.Args
if len(args) < 2 {
- internal.Die("missing arguments", errors.New("requires subcommand"))
+ misc.Die("missing arguments", errors.New("requires subcommand"))
}
command := args[1]
store := internal.GetStore()
@@ -61,13 +50,13 @@ func main() {
searchTerm := ""
if isFind {
if len(args) < 3 {
- internal.Die("find requires an argument to search for", errors.New("search term required"))
+ misc.Die("find requires an argument to search for", errors.New("search term required"))
}
searchTerm = args[2]
}
files, err := internal.List(store, true)
if err != nil {
- internal.Die("unable to list files", err)
+ misc.Die("unable to list files", err)
}
for _, f := range files {
if isFind {
@@ -84,16 +73,16 @@ func main() {
idx := 2
switch len(args) {
case 2:
- internal.Die("insert missing required arguments", errors.New("entry required"))
+ misc.Die("insert missing required arguments", errors.New("entry required"))
case 3:
case 4:
options = cli.ParseArgs(args[2])
if !options.Multi {
- internal.Die("multi-line insert must be after 'insert'", errors.New("invalid command"))
+ misc.Die("multi-line insert must be after 'insert'", errors.New("invalid command"))
}
idx = 3
default:
- internal.Die("too many arguments", errors.New("insert can only perform one operation"))
+ misc.Die("too many arguments", errors.New("insert can only perform one operation"))
}
isPipe := internal.IsInputFromPipe()
entry := getEntry(store, args, idx)
@@ -107,7 +96,7 @@ func main() {
dir := filepath.Dir(entry)
if !internal.PathExists(dir) {
if err := os.MkdirAll(dir, 0755); err != nil {
- internal.Die("failed to create directory structure", err)
+ misc.Die("failed to create directory structure", err)
}
}
}
@@ -115,32 +104,32 @@ func main() {
if !options.Multi && !isPipe {
input, err := internal.ConfirmInputsMatch("password")
if err != nil {
- internal.Die("password input failed", err)
+ misc.Die("password input failed", err)
}
password = input
} else {
input, err := internal.Stdin(false)
if err != nil {
- internal.Die("failed to read stdin", err)
+ misc.Die("failed to read stdin", err)
}
password = input
}
if password == "" {
- internal.Die("empty password provided", errors.New("password can NOT be empty"))
+ misc.Die("empty password provided", errors.New("password can NOT be empty"))
}
l, err := internal.NewLockbox(internal.LockboxOptions{File: entry})
if err != nil {
- internal.Die("unable to make lockbox model instance", err)
+ misc.Die("unable to make lockbox model instance", err)
}
if err := l.Encrypt([]byte(password)); err != nil {
- internal.Die("failed to save password", err)
+ misc.Die("failed to save password", err)
}
fmt.Println("")
internal.Hooks(internal.InsertHook, internal.PostHookStep)
case "rm":
entry := getEntry(store, args, 2)
if !internal.PathExists(entry) {
- internal.Die("does not exists", errors.New("can not delete unknown entry"))
+ misc.Die("does not exists", errors.New("can not delete unknown entry"))
}
if confirm("remove entry") {
os.Remove(entry)
@@ -165,13 +154,13 @@ func main() {
if inEntry == getEntry(store, []string{"***"}, 0) {
all, err := internal.List(store, false)
if err != nil {
- internal.Die("unable to get all files", err)
+ misc.Die("unable to get all files", err)
}
entries = all
} else {
matches, err := filepath.Glob(inEntry)
if err != nil {
- internal.Die("bad glob", err)
+ misc.Die("bad glob", err)
}
entries = matches
}
@@ -179,33 +168,33 @@ func main() {
isGlob := len(entries) > 1
if isGlob {
if !isShow {
- internal.Die("cannot glob to clipboard", errors.New("bad glob request"))
+ misc.Die("cannot glob to clipboard", errors.New("bad glob request"))
}
sort.Strings(entries)
}
startColor, endColor, err := internal.GetColor(internal.ColorRed)
if err != nil {
- internal.Die("unable to get color for terminal", err)
+ misc.Die("unable to get color for terminal", err)
}
dumpData := []Dump{}
clip := clipboard.Commands{}
if !isShow {
clip, err = clipboard.NewCommands()
if err != nil {
- internal.Die("unable to get clipboard", err)
+ misc.Die("unable to get clipboard", err)
}
}
for _, entry := range entries {
if !internal.PathExists(entry) {
- internal.Die("invalid entry", errors.New("entry not found"))
+ misc.Die("invalid entry", errors.New("entry not found"))
}
l, err := internal.NewLockbox(internal.LockboxOptions{File: entry})
if err != nil {
- internal.Die("unable to make lockbox model instance", err)
+ misc.Die("unable to make lockbox model instance", err)
}
decrypt, err := l.Decrypt()
if err != nil {
- internal.Die("unable to decrypt", err)
+ misc.Die("unable to decrypt", err)
}
value := strings.TrimSpace(string(decrypt))
dump := Dump{}
@@ -245,7 +234,7 @@ func main() {
}
b, err := json.MarshalIndent(d, "", " ")
if err != nil {
- internal.Die("failed to marshal dump item", err)
+ misc.Die("failed to marshal dump item", err)
}
fmt.Println(string(b))
}
@@ -255,11 +244,11 @@ func main() {
idx := 0
val, err := internal.Stdin(false)
if err != nil {
- internal.Die("unable to read value to clear", err)
+ misc.Die("unable to read value to clear", err)
}
clip, err := clipboard.NewCommands()
if err != nil {
- internal.Die("unable to get paste command", err)
+ misc.Die("unable to get paste command", err)
}
var args []string
if len(clip.Paste) > 1 {
@@ -290,7 +279,7 @@ func main() {
c.Stdout = os.Stdout
c.Stderr = os.Stderr
if err := c.Run(); err != nil {
- internal.Die("bad command", err)
+ misc.Die("bad command", err)
}
}
}
@@ -298,7 +287,7 @@ func main() {
func confirm(prompt string) bool {
yesNo, err := internal.ConfirmYesNoPrompt(prompt)
if err != nil {
- internal.Die("failed to get response", err)
+ misc.Die("failed to get response", err)
}
return yesNo
}
diff --git a/internal/clipboard/clip.go b/internal/clipboard/clip.go
@@ -8,7 +8,7 @@ import (
"path/filepath"
"strings"
- "github.com/enckse/lockbox/internal"
+ "github.com/enckse/lockbox/internal/misc"
)
const (
@@ -89,7 +89,7 @@ func pipeTo(command, value string, wait bool, args ...string) {
cmd := exec.Command(command, args...)
stdin, err := cmd.StdinPipe()
if err != nil {
- internal.Die("unable to get stdin pipe", err)
+ misc.Die("unable to get stdin pipe", err)
}
go func() {
@@ -105,6 +105,6 @@ func pipeTo(command, value string, wait bool, args ...string) {
ran = cmd.Start()
}
if ran != nil {
- internal.Die("failed to run command", ran)
+ misc.Die("failed to run command", ran)
}
}
diff --git a/internal/colors/colors.go b/internal/colors/colors.go
@@ -1,40 +1,46 @@
-package internal
+package colors
import (
"errors"
-)
-
-type (
- // Color are terminal colors for dumb terminal coloring.
- Color int
+ "github.com/enckse/lockbox/internal/inputs"
)
const (
termBeginRed = "\033[1;31m"
termEndRed = "\033[0m"
- // ColorRed will get red terminal coloring.
- ColorRed = iota
+ // Red will get red terminal coloring.
+ Red = iota
+)
+
+type (
+ // Color are terminal colors for dumb terminal coloring.
+ Color int
+ // Terminal represents terminal coloring information.
+ Terminal struct {
+ Start string
+ End string
+ }
)
-// GetColor will retrieve start/end terminal coloration indicators.
-func GetColor(color Color) (string, string, error) {
- if color != ColorRed {
- return "", "", errors.New("bad color")
+// NewTerminal will retrieve start/end terminal coloration indicators.
+func NewTerminal(color Color) (Terminal, error) {
+ if color != Red {
+ return Terminal{}, errors.New("bad color")
}
- interactive, err := IsInteractive()
+ interactive, err := inputs.IsInteractive()
if err != nil {
- return "", "", err
+ return Terminal{}, err
}
colors := interactive
if colors {
- isColored, err := isYesNoEnv(false, "LOCKBOX_NOCOLOR")
+ isColored, err := inputs.IsColorEnabled()
if err != nil {
- return "", "", err
+ return Terminal{}, err
}
colors = !isColored
}
if colors {
- return termBeginRed, termEndRed, nil
+ return Terminal{Start: termBeginRed, End: termEndRed}, nil
}
- return "", "", nil
+ return Terminal{}, nil
}
diff --git a/internal/colors/colors_test.go b/internal/colors/colors_test.go
@@ -0,0 +1,64 @@
+package colors
+
+import (
+ "os"
+ "testing"
+)
+
+func TestHasColoring(t *testing.T) {
+ os.Setenv("LOCKBOX_INTERACTIVE", "yes")
+ os.Setenv("LOCKBOX_NOCOLOR", "no")
+ term, err := NewTerminal(Red)
+ if err != nil {
+ t.Errorf("color was valid: %v", err)
+ }
+ if term.Start != termBeginRed || term.End != termEndRed {
+ t.Error("bad resulting color")
+ }
+}
+
+func TestBadColor(t *testing.T) {
+ _, err := NewTerminal(Color(5))
+ if err == nil || err.Error() != "bad color" {
+ t.Errorf("invalid color error: %v", err)
+ }
+}
+
+func TestNoColoring(t *testing.T) {
+ os.Setenv("LOCKBOX_INTERACTIVE", "no")
+ os.Setenv("LOCKBOX_NOCOLOR", "yes")
+ term, err := NewTerminal(Red)
+ if err != nil {
+ t.Errorf("color was valid: %v", err)
+ }
+ if term.Start != "" || term.End != "" {
+ t.Error("should have no color")
+ }
+ os.Setenv("LOCKBOX_INTERACTIVE", "yes")
+ os.Setenv("LOCKBOX_NOCOLOR", "yes")
+ term, err = NewTerminal(Red)
+ if err != nil {
+ t.Errorf("color was valid: %v", err)
+ }
+ if term.Start != "" || term.End != "" {
+ t.Error("should have no color")
+ }
+ os.Setenv("LOCKBOX_INTERACTIVE", "no")
+ os.Setenv("LOCKBOX_NOCOLOR", "no")
+ term, err = NewTerminal(Red)
+ if err != nil {
+ t.Errorf("color was valid: %v", err)
+ }
+ if term.Start != "" || term.End != "" {
+ t.Error("should have no color")
+ }
+ os.Setenv("LOCKBOX_INTERACTIVE", "yes")
+ os.Setenv("LOCKBOX_NOCOLOR", "no")
+ term, err = NewTerminal(Red)
+ if err != nil {
+ t.Errorf("color was valid: %v", err)
+ }
+ if term.Start == "" || term.End == "" {
+ t.Error("should have color")
+ }
+}
diff --git a/internal/dump/dump.go b/internal/dump/dump.go
@@ -0,0 +1,17 @@
+package dump
+
+import (
+ "encoding/json"
+)
+
+type (
+ // ExportEntity represents the output structure from a JSON dump.
+ ExportEntity struct {
+ Path string `json:"path,omitempty"`
+ Value string `json:"value"`
+ }
+)
+
+func Marshal(entities []ExportEntity) ([]byte, error) {
+ return json.MarshalIndent(entities, "", " ")
+}
diff --git a/internal/encrypt/encrypt.go b/internal/encrypt/encrypt.go
@@ -12,6 +12,7 @@ import (
"github.com/google/shlex"
"golang.org/x/crypto/nacl/secretbox"
+ "github.com/enckse/lockbox/internal/inputs"
)
const (
@@ -116,7 +117,7 @@ func (l Lockbox) Encrypt(datum []byte) error {
}
data := datum
if data == nil {
- b, err := getStdin(false)
+ b, err := inputs.RawStdin(false)
if err != nil {
return err
}
diff --git a/internal/encrypt/encrypt_test.go b/internal/encrypt/encrypt_test.go
@@ -4,16 +4,21 @@ import (
"os"
"path/filepath"
"testing"
+ "github.com/enckse/lockbox/internal/misc"
)
func setupData(t *testing.T) string {
os.Setenv("LOCKBOX_KEYMODE", "")
os.Setenv("LOCKBOX_KEY", "")
- if !PathExists("bin") {
+ if misc.PathExists("bin") {
+ if err := os.RemoveAll("bin"); err != nil {
+ t.Errorf("unable to cleanup dir: %v")
+ }
+ }
+
if err := os.MkdirAll("bin", 0755); err != nil {
t.Errorf("failed to setup bin directory: %v", err)
}
- }
return filepath.Join("bin", "test.lb")
}
diff --git a/internal/hooks/hooks.go b/internal/hooks/hooks.go
@@ -1,32 +1,33 @@
-package internal
+package hooks
import (
"errors"
"os"
"os/exec"
"path/filepath"
+ "github.com/enckse/lockbox/internal/misc"
)
type (
// HookAction are specific steps that may call a hook.
- HookAction string
+ Action string
// HookStep is the step, during command execution, when the hook was called.
- HookStep string
+ Step string
)
const (
// RemoveHook is called when a store entry is removed.
- RemoveHook HookAction = "remove"
+ Remove Action = "remove"
// InsertHook is called when a store entry is inserted.
- InsertHook HookAction = "insert"
+ Insert Action = "insert"
// PostHookStep is a hook running at the end of a command.
- PostHookStep HookStep = "post"
+ PostStep Step = "post"
)
-// Hooks executes any configured hooks.
-func Hooks(action HookAction, step HookStep) error {
+// Run executes any configured hooks.
+func Run(action Action, step Step) error {
hookDir := os.Getenv("LOCKBOX_HOOKDIR")
- if !PathExists(hookDir) {
+ if !misc.PathExists(hookDir) {
return nil
}
dirs, err := os.ReadDir(hookDir)
diff --git a/internal/inputs/inputs.go b/internal/inputs/inputs.go
@@ -1,4 +1,4 @@
-package internal
+package inputs
import (
"bufio"
@@ -23,6 +23,10 @@ func isYesNoEnv(defaultValue bool, env string) (bool, error) {
return false, fmt.Errorf("invalid yes/no env value for %s", env)
}
+func IsColorEnabled() (bool, error) {
+ return isYesNoEnv(false, "LOCKBOX_NOCOLOR")
+}
+
// IsInteractive indicates if running as a user UI experience.
func IsInteractive() (bool, error) {
return isYesNoEnv(true, "LOCKBOX_INTERACTIVE")
@@ -104,6 +108,11 @@ func ConfirmYesNoPrompt(prompt string) (bool, error) {
return resp == "Y" || resp == "y", nil
}
+// RawStdin will get raw stdin data.
+func RawStdin() ([]byte, error) {
+ return getStdin(false)
+}
+
func getStdin(one bool) ([]byte, error) {
scanner := bufio.NewScanner(os.Stdin)
var b bytes.Buffer
diff --git a/internal/misc/utils.go b/internal/misc/utils.go
@@ -0,0 +1,31 @@
+package misc
+
+import (
+ "fmt"
+ "os"
+)
+
+// LogError will log an error to stderr.
+func LogError(message string, err error) {
+ msg := message
+ if err != nil {
+ msg = fmt.Sprintf("%s (%v)", msg, err)
+ }
+ fmt.Fprintln(os.Stderr, msg)
+}
+
+// Die will print messages and exit.
+func Die(message string, err error) {
+ LogError(message, err)
+ os.Exit(1)
+}
+
+// PathExists indicates if a path exists.
+func PathExists(path string) bool {
+ if _, err := os.Stat(path); err != nil {
+ if os.IsNotExist(err) {
+ return false
+ }
+ }
+ return true
+}
diff --git a/internal/store/store.go b/internal/store/store.go
@@ -1,4 +1,4 @@
-package internal
+package store
import (
"errors"
@@ -7,6 +7,7 @@ import (
"path/filepath"
"sort"
"strings"
+ "github.com/enckse/lockbox/internal/misc"
)
const (
@@ -14,25 +15,36 @@ const (
Extension = ".lb"
)
-// GetStore gets the lockbox directory.
-func GetStore() string {
+type (
+ // FileSystem represents a filesystem store.
+ FileSystem struct {
+ path string
+ }
+ ViewOptions struct {
+ Display bool
+ }
+
+)
+
+// NewFileSystemStore gets the lockbox directory (filesystem-based) store.
+func NewFileSystemStore() string {
return os.Getenv("LOCKBOX_STORE")
}
-// List will get all lockbox files in a directory store.
-func List(store string, display bool) ([]string, error) {
+// List will get all lockbox files in a store.
+func (s FileSystem) List(options ViewOptions) ([]string, error) {
var results []string
- if !PathExists(store) {
- return nil, errors.New("store does not exists")
+ if !misc.PathExists(s.path) {
+ return nil, errors.New("store does not exist")
}
- err := filepath.Walk(store, func(path string, info fs.FileInfo, err error) error {
+ err := filepath.Walk(s.path, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
if strings.HasSuffix(path, Extension) {
usePath := path
- if display {
- usePath = strings.TrimPrefix(usePath, store)
+ if options.Display {
+ usePath = strings.TrimPrefix(usePath, s.path)
usePath = strings.TrimPrefix(usePath, "/")
usePath = strings.TrimSuffix(usePath, Extension)
}
@@ -44,7 +56,7 @@ func List(store string, display bool) ([]string, error) {
if err != nil {
return nil, err
}
- if display {
+ if options.Display {
sort.Strings(results)
}
return results, nil
diff --git a/internal/store/store_test.go b/internal/store/store_test.go
@@ -0,0 +1,50 @@
+package store
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+ "github.com/enckse/lockbox/internal/misc"
+)
+
+func TestListErrors(t *testing.T) {
+ _, err := FileSystem{path: "aaa"}.List(ViewOptions{})
+ if err == nil || err.Error() != "store does not exist" {
+ t.Errorf("invalid store error: %v", err)
+ }
+}
+
+func TestList(t *testing.T) {
+ testStore := "bin"
+ if misc.PathExists(testStore) {
+ if err := os.RemoveAll(testStore); err != nil {
+ t.Errorf("invalid error on remove: %v", err)
+ }
+ }
+ if err := os.MkdirAll(filepath.Join(testStore, "sub"), 0755); err != nil {
+ t.Errorf("unable to makedir: %v", err)
+ }
+ for _, path := range []string{"test", "test2", "aaa", "sub/aaaaajk", "sub/12lkjafav"} {
+ if err := os.WriteFile(filepath.Join(testStore, path+Extension), []byte(""), 0644); err != nil {
+ t.Errorf("failed to write %s: %v", path, err)
+ }
+ }
+ s := FileSystem{path: testStore}
+ res, err := s.List(ViewOptions{})
+ if err != nil {
+ t.Errorf("unable to list: %v", err)
+ }
+ if len(res) != 5 {
+ t.Error("mismatched results")
+ }
+ res, err = s.List(ViewOptions{Display: true})
+ if err != nil {
+ t.Errorf("unable to list: %v", err)
+ }
+ if len(res) != 5 {
+ t.Error("mismatched results")
+ }
+ if res[0] != "aaa" || res[1] != "sub/12lkjafav" || res[2] != "sub/aaaaajk" || res[3] != "test" || res[4] != "test2" {
+ t.Errorf("not sorted: %v", res)
+ }
+}
diff --git a/internal/utils.go b/internal/utils.go
@@ -1,31 +0,0 @@
-package internal
-
-import (
- "fmt"
- "os"
-)
-
-// LogError will log an error to stderr.
-func LogError(message string, err error) {
- msg := message
- if err != nil {
- msg = fmt.Sprintf("%s (%v)", msg, err)
- }
- fmt.Fprintln(os.Stderr, msg)
-}
-
-// Die will print messages and exit.
-func Die(message string, err error) {
- LogError(message, err)
- os.Exit(1)
-}
-
-// PathExists indicates if a path exists.
-func PathExists(path string) bool {
- if _, err := os.Stat(path); err != nil {
- if os.IsNotExist(err) {
- return false
- }
- }
- return true
-}