revert: removes --secrets - to be replaced with encryption
This commit is contained in:
parent
34970ac9d9
commit
4509611185
27 changed files with 132 additions and 269 deletions
28
cmd/dump.go
28
cmd/dump.go
|
|
@ -40,7 +40,6 @@ type dumpEntry struct {
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
Encoding string `json:"encoding,omitempty"`
|
Encoding string `json:"encoding,omitempty"`
|
||||||
Secret bool `json:"secret,omitempty"`
|
|
||||||
ExpiresAt *int64 `json:"expires_at,omitempty"`
|
ExpiresAt *int64 `json:"expires_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,10 +81,6 @@ func dump(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("cannot dump '%s': unsupported encoding '%s'", targetDB, mode)
|
return fmt.Errorf("cannot dump '%s': unsupported encoding '%s'", targetDB, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
includeSecret, err := cmd.Flags().GetBool("secret")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
globPatterns, err := cmd.Flags().GetStringSlice("glob")
|
globPatterns, err := cmd.Flags().GetStringSlice("glob")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot dump '%s': %v", targetDB, err)
|
return fmt.Errorf("cannot dump '%s': %v", targetDB, err)
|
||||||
|
|
@ -100,17 +95,15 @@ func dump(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := DumpOptions{
|
opts := DumpOptions{
|
||||||
Encoding: mode,
|
Encoding: mode,
|
||||||
IncludeSecret: includeSecret,
|
Matchers: matchers,
|
||||||
Matchers: matchers,
|
GlobPatterns: globPatterns,
|
||||||
GlobPatterns: globPatterns,
|
|
||||||
}
|
}
|
||||||
return dumpDatabase(store, strings.TrimPrefix(targetDB, "@"), cmd.OutOrStdout(), opts)
|
return dumpDatabase(store, strings.TrimPrefix(targetDB, "@"), cmd.OutOrStdout(), opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
dumpCmd.Flags().StringP("encoding", "e", "auto", "value encoding: auto, base64, or text")
|
dumpCmd.Flags().StringP("encoding", "e", "auto", "value encoding: auto, base64, or text")
|
||||||
dumpCmd.Flags().Bool("secret", false, "Include entries marked as secret")
|
|
||||||
dumpCmd.Flags().StringSliceP("glob", "g", nil, "Filter keys with glob pattern (repeatable)")
|
dumpCmd.Flags().StringSliceP("glob", "g", nil, "Filter keys with glob pattern (repeatable)")
|
||||||
dumpCmd.Flags().String("glob-sep", "", fmt.Sprintf("Characters treated as separators for globbing (default %q)", defaultGlobSeparatorsDisplay()))
|
dumpCmd.Flags().String("glob-sep", "", fmt.Sprintf("Characters treated as separators for globbing (default %q)", defaultGlobSeparatorsDisplay()))
|
||||||
rootCmd.AddCommand(dumpCmd)
|
rootCmd.AddCommand(dumpCmd)
|
||||||
|
|
@ -132,10 +125,9 @@ func encodeText(entry *dumpEntry, key []byte, v []byte) error {
|
||||||
|
|
||||||
// DumpOptions controls how a store is dumped to NDJSON.
|
// DumpOptions controls how a store is dumped to NDJSON.
|
||||||
type DumpOptions struct {
|
type DumpOptions struct {
|
||||||
Encoding string
|
Encoding string
|
||||||
IncludeSecret bool
|
Matchers []glob.Glob
|
||||||
Matchers []glob.Glob
|
GlobPatterns []string
|
||||||
GlobPatterns []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// dumpDatabase writes entries from dbName to w as NDJSON.
|
// dumpDatabase writes entries from dbName to w as NDJSON.
|
||||||
|
|
@ -159,16 +151,10 @@ func dumpDatabase(store *Store, dbName string, w io.Writer, opts DumpOptions) er
|
||||||
if !globMatch(opts.Matchers, string(key)) {
|
if !globMatch(opts.Matchers, string(key)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
meta := item.UserMeta()
|
|
||||||
isSecret := meta&metaSecret != 0
|
|
||||||
if isSecret && !opts.IncludeSecret {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
expiresAt := item.ExpiresAt()
|
expiresAt := item.ExpiresAt()
|
||||||
if err := item.Value(func(v []byte) error {
|
if err := item.Value(func(v []byte) error {
|
||||||
entry := dumpEntry{
|
entry := dumpEntry{
|
||||||
Key: string(key),
|
Key: string(key),
|
||||||
Secret: isSecret,
|
|
||||||
}
|
}
|
||||||
if expiresAt > 0 {
|
if expiresAt > 0 {
|
||||||
ts := int64(expiresAt)
|
ts := int64(expiresAt)
|
||||||
|
|
|
||||||
12
cmd/get.go
12
cmd/get.go
|
|
@ -74,7 +74,6 @@ func get(cmd *cobra.Command, args []string) error {
|
||||||
store := &Store{}
|
store := &Store{}
|
||||||
|
|
||||||
var v []byte
|
var v []byte
|
||||||
var meta byte
|
|
||||||
trans := TransactionArgs{
|
trans := TransactionArgs{
|
||||||
key: args[0],
|
key: args[0],
|
||||||
readonly: true,
|
readonly: true,
|
||||||
|
|
@ -84,7 +83,6 @@ func get(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
meta = item.UserMeta()
|
|
||||||
v, err = item.ValueCopy(nil)
|
v, err = item.ValueCopy(nil)
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
|
|
@ -94,14 +92,6 @@ func get(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("cannot get '%s': %v", args[0], err)
|
return fmt.Errorf("cannot get '%s': %v", args[0], err)
|
||||||
}
|
}
|
||||||
|
|
||||||
includeSecret, err := cmd.Flags().GetBool("secret")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannot get '%s': %v", args[0], err)
|
|
||||||
}
|
|
||||||
if meta&metaSecret != 0 && !includeSecret {
|
|
||||||
return fmt.Errorf("cannot get '%s': marked as secret, run with --secret", args[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
binary, err := cmd.Flags().GetBool("include-binary")
|
binary, err := cmd.Flags().GetBool("include-binary")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot get '%s': %v", args[0], err)
|
return fmt.Errorf("cannot get '%s': %v", args[0], err)
|
||||||
|
|
@ -238,13 +228,11 @@ var runFlag bool
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
getCmd.Flags().BoolP("include-binary", "b", false, "include binary data in text output")
|
getCmd.Flags().BoolP("include-binary", "b", false, "include binary data in text output")
|
||||||
getCmd.Flags().Bool("secret", false, "display values marked as secret")
|
|
||||||
getCmd.Flags().BoolVarP(&runFlag, "run", "c", false, "execute the result as a shell command")
|
getCmd.Flags().BoolVarP(&runFlag, "run", "c", false, "execute the result as a shell command")
|
||||||
getCmd.Flags().Bool("no-template", false, "directly output template syntax")
|
getCmd.Flags().Bool("no-template", false, "directly output template syntax")
|
||||||
rootCmd.AddCommand(getCmd)
|
rootCmd.AddCommand(getCmd)
|
||||||
|
|
||||||
runCmd.Flags().BoolP("include-binary", "b", false, "include binary data in text output")
|
runCmd.Flags().BoolP("include-binary", "b", false, "include binary data in text output")
|
||||||
runCmd.Flags().Bool("secret", false, "display values marked as secret")
|
|
||||||
runCmd.Flags().Bool("no-template", false, "directly output template syntax")
|
runCmd.Flags().Bool("no-template", false, "directly output template syntax")
|
||||||
rootCmd.AddCommand(runCmd)
|
rootCmd.AddCommand(runCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
cmd/list.go
13
cmd/list.go
|
|
@ -55,7 +55,6 @@ func (e *formatEnum) Type() string { return "format" }
|
||||||
|
|
||||||
var (
|
var (
|
||||||
listBinary bool
|
listBinary bool
|
||||||
listSecret bool
|
|
||||||
listNoKeys bool
|
listNoKeys bool
|
||||||
listNoValues bool
|
listNoValues bool
|
||||||
listTTL bool
|
listTTL bool
|
||||||
|
|
@ -142,7 +141,6 @@ func list(cmd *cobra.Command, args []string) error {
|
||||||
tw.AppendHeader(headerRow(columns))
|
tw.AppendHeader(headerRow(columns))
|
||||||
}
|
}
|
||||||
|
|
||||||
placeholder := "**********"
|
|
||||||
var matchedCount int
|
var matchedCount int
|
||||||
trans := TransactionArgs{
|
trans := TransactionArgs{
|
||||||
key: targetDB,
|
key: targetDB,
|
||||||
|
|
@ -162,11 +160,9 @@ func list(cmd *cobra.Command, args []string) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
matchedCount++
|
matchedCount++
|
||||||
meta := item.UserMeta()
|
|
||||||
isSecret := meta&metaSecret != 0
|
|
||||||
|
|
||||||
var valueStr string
|
var valueStr string
|
||||||
if showValues && (!isSecret || listSecret) {
|
if showValues {
|
||||||
if err := item.Value(func(v []byte) error {
|
if err := item.Value(func(v []byte) error {
|
||||||
valueBuf = append(valueBuf[:0], v...)
|
valueBuf = append(valueBuf[:0], v...)
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -182,11 +178,7 @@ func list(cmd *cobra.Command, args []string) error {
|
||||||
case columnKey:
|
case columnKey:
|
||||||
row = append(row, key)
|
row = append(row, key)
|
||||||
case columnValue:
|
case columnValue:
|
||||||
if isSecret && !listSecret {
|
row = append(row, valueStr)
|
||||||
row = append(row, placeholder)
|
|
||||||
} else {
|
|
||||||
row = append(row, valueStr)
|
|
||||||
}
|
|
||||||
case columnTTL:
|
case columnTTL:
|
||||||
row = append(row, formatExpiry(item.ExpiresAt()))
|
row = append(row, formatExpiry(item.ExpiresAt()))
|
||||||
}
|
}
|
||||||
|
|
@ -310,7 +302,6 @@ func renderTable(tw table.Writer) {
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
listCmd.Flags().BoolVarP(&listBinary, "binary", "b", false, "include binary data in text output")
|
listCmd.Flags().BoolVarP(&listBinary, "binary", "b", false, "include binary data in text output")
|
||||||
listCmd.Flags().BoolVarP(&listSecret, "secret", "S", false, "display values marked as secret")
|
|
||||||
listCmd.Flags().BoolVar(&listNoKeys, "no-keys", false, "suppress the key column")
|
listCmd.Flags().BoolVar(&listNoKeys, "no-keys", false, "suppress the key column")
|
||||||
listCmd.Flags().BoolVar(&listNoValues, "no-values", false, "suppress the value column")
|
listCmd.Flags().BoolVar(&listNoValues, "no-values", false, "suppress the value column")
|
||||||
listCmd.Flags().BoolVarP(&listTTL, "ttl", "t", false, "append a TTL column when entries expire")
|
listCmd.Flags().BoolVarP(&listTTL, "ttl", "t", false, "append a TTL column when entries expire")
|
||||||
|
|
|
||||||
144
cmd/restore.go
144
cmd/restore.go
|
|
@ -32,6 +32,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/dgraph-io/badger/v4"
|
"github.com/dgraph-io/badger/v4"
|
||||||
|
"github.com/gobwas/glob"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -85,82 +86,21 @@ func restore(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
decoder := json.NewDecoder(bufio.NewReaderSize(reader, 8*1024*1024))
|
decoder := json.NewDecoder(bufio.NewReaderSize(reader, 8*1024*1024))
|
||||||
|
|
||||||
wb := db.NewWriteBatch()
|
|
||||||
defer wb.Cancel()
|
|
||||||
|
|
||||||
interactive, err := cmd.Flags().GetBool("interactive")
|
interactive, err := cmd.Flags().GetBool("interactive")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot restore '%s': %v", displayTarget, err)
|
return fmt.Errorf("cannot restore '%s': %v", displayTarget, err)
|
||||||
}
|
}
|
||||||
promptOverwrite := interactive || config.Key.AlwaysPromptOverwrite
|
promptOverwrite := interactive || config.Key.AlwaysPromptOverwrite
|
||||||
|
|
||||||
entryNo := 0
|
restored, err := restoreEntries(decoder, db, restoreOpts{
|
||||||
var restored int
|
matchers: matchers,
|
||||||
var matched bool
|
promptOverwrite: promptOverwrite,
|
||||||
|
})
|
||||||
for {
|
if err != nil {
|
||||||
var entry dumpEntry
|
|
||||||
if err := decoder.Decode(&entry); err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return fmt.Errorf("cannot restore '%s': entry %d: %w", displayTarget, entryNo+1, err)
|
|
||||||
}
|
|
||||||
entryNo++
|
|
||||||
if entry.Key == "" {
|
|
||||||
return fmt.Errorf("cannot restore '%s': entry %d: missing key", displayTarget, entryNo)
|
|
||||||
}
|
|
||||||
if !globMatch(matchers, entry.Key) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if promptOverwrite {
|
|
||||||
exists, err := keyExistsInDB(db, entry.Key)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannot restore '%s': entry %d: %v", displayTarget, entryNo, err)
|
|
||||||
}
|
|
||||||
if exists {
|
|
||||||
fmt.Printf("overwrite '%s'? (y/n)\n", entry.Key)
|
|
||||||
var confirm string
|
|
||||||
if _, err := fmt.Scanln(&confirm); err != nil {
|
|
||||||
return fmt.Errorf("cannot restore '%s': entry %d: %v", displayTarget, entryNo, err)
|
|
||||||
}
|
|
||||||
if strings.ToLower(confirm) != "y" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := decodeEntryValue(entry)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannot restore '%s': entry %d: %w", displayTarget, entryNo, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
entryMeta := byte(0x0)
|
|
||||||
if entry.Secret {
|
|
||||||
entryMeta = metaSecret
|
|
||||||
}
|
|
||||||
|
|
||||||
writeEntry := badger.NewEntry([]byte(entry.Key), value).WithMeta(entryMeta)
|
|
||||||
if entry.ExpiresAt != nil {
|
|
||||||
if *entry.ExpiresAt < 0 {
|
|
||||||
return fmt.Errorf("cannot restore '%s': entry %d: expires_at must be >= 0", displayTarget, entryNo)
|
|
||||||
}
|
|
||||||
writeEntry.ExpiresAt = uint64(*entry.ExpiresAt)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := wb.SetEntry(writeEntry); err != nil {
|
|
||||||
return fmt.Errorf("cannot restore '%s': entry %d: %w", displayTarget, entryNo, err)
|
|
||||||
}
|
|
||||||
restored++
|
|
||||||
matched = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := wb.Flush(); err != nil {
|
|
||||||
return fmt.Errorf("cannot restore '%s': %v", displayTarget, err)
|
return fmt.Errorf("cannot restore '%s': %v", displayTarget, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(matchers) > 0 && !matched {
|
if len(matchers) > 0 && restored == 0 {
|
||||||
return fmt.Errorf("cannot restore '%s': No matches for pattern %s", displayTarget, formatGlobPatterns(globPatterns))
|
return fmt.Errorf("cannot restore '%s': No matches for pattern %s", displayTarget, formatGlobPatterns(globPatterns))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -198,6 +138,76 @@ func decodeEntryValue(entry dumpEntry) ([]byte, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type restoreOpts struct {
|
||||||
|
matchers []glob.Glob
|
||||||
|
promptOverwrite bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreEntries(decoder *json.Decoder, db *badger.DB, opts restoreOpts) (int, error) {
|
||||||
|
wb := db.NewWriteBatch()
|
||||||
|
defer wb.Cancel()
|
||||||
|
|
||||||
|
entryNo := 0
|
||||||
|
restored := 0
|
||||||
|
|
||||||
|
for {
|
||||||
|
var entry dumpEntry
|
||||||
|
if err := decoder.Decode(&entry); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("entry %d: %w", entryNo+1, err)
|
||||||
|
}
|
||||||
|
entryNo++
|
||||||
|
if entry.Key == "" {
|
||||||
|
return 0, fmt.Errorf("entry %d: missing key", entryNo)
|
||||||
|
}
|
||||||
|
if !globMatch(opts.matchers, entry.Key) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.promptOverwrite {
|
||||||
|
exists, err := keyExistsInDB(db, entry.Key)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("entry %d: %v", entryNo, err)
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
fmt.Printf("overwrite '%s'? (y/n)\n", entry.Key)
|
||||||
|
var confirm string
|
||||||
|
if _, err := fmt.Scanln(&confirm); err != nil {
|
||||||
|
return 0, fmt.Errorf("entry %d: %v", entryNo, err)
|
||||||
|
}
|
||||||
|
if strings.ToLower(confirm) != "y" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := decodeEntryValue(entry)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("entry %d: %w", entryNo, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeEntry := badger.NewEntry([]byte(entry.Key), value)
|
||||||
|
if entry.ExpiresAt != nil {
|
||||||
|
if *entry.ExpiresAt < 0 {
|
||||||
|
return 0, fmt.Errorf("entry %d: expires_at must be >= 0", entryNo)
|
||||||
|
}
|
||||||
|
writeEntry.ExpiresAt = uint64(*entry.ExpiresAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := wb.SetEntry(writeEntry); err != nil {
|
||||||
|
return 0, fmt.Errorf("entry %d: %w", entryNo, err)
|
||||||
|
}
|
||||||
|
restored++
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := wb.Flush(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return restored, nil
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
restoreCmd.Flags().StringP("file", "f", "", "Path to an NDJSON dump (defaults to stdin)")
|
restoreCmd.Flags().StringP("file", "f", "", "Path to an NDJSON dump (defaults to stdin)")
|
||||||
restoreCmd.Flags().StringSliceP("glob", "g", nil, "Restore keys matching glob pattern (repeatable)")
|
restoreCmd.Flags().StringSliceP("glob", "g", nil, "Restore keys matching glob pattern (repeatable)")
|
||||||
|
|
|
||||||
|
|
@ -76,10 +76,6 @@ func set(cmd *cobra.Command, args []string) error {
|
||||||
value = bytes
|
value = bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
secret, err := cmd.Flags().GetBool("secret")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannot set '%s': %v", args[0], err)
|
|
||||||
}
|
|
||||||
ttl, err := cmd.Flags().GetDuration("ttl")
|
ttl, err := cmd.Flags().GetDuration("ttl")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot set '%s': %v", args[0], err)
|
return fmt.Errorf("cannot set '%s': %v", args[0], err)
|
||||||
|
|
@ -108,9 +104,6 @@ func set(cmd *cobra.Command, args []string) error {
|
||||||
sync: false,
|
sync: false,
|
||||||
transact: func(tx *badger.Txn, k []byte) error {
|
transact: func(tx *badger.Txn, k []byte) error {
|
||||||
entry := badger.NewEntry(k, value)
|
entry := badger.NewEntry(k, value)
|
||||||
if secret {
|
|
||||||
entry = entry.WithMeta(metaSecret)
|
|
||||||
}
|
|
||||||
if ttl != 0 {
|
if ttl != 0 {
|
||||||
entry = entry.WithTTL(ttl)
|
entry = entry.WithTTL(ttl)
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +120,6 @@ func set(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(setCmd)
|
rootCmd.AddCommand(setCmd)
|
||||||
setCmd.Flags().Bool("secret", false, "Mark the stored value as a secret")
|
|
||||||
setCmd.Flags().DurationP("ttl", "t", 0, "Expire the key after the provided duration (e.g. 24h, 30m)")
|
setCmd.Flags().DurationP("ttl", "t", 0, "Expire the key after the provided duration (e.g. 24h, 30m)")
|
||||||
setCmd.Flags().BoolP("interactive", "i", false, "Prompt before overwriting an existing key")
|
setCmd.Flags().BoolP("interactive", "i", false, "Prompt before overwriting an existing key")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,10 +41,6 @@ type errNotFound struct {
|
||||||
suggestions []string
|
suggestions []string
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
metaSecret byte = 0x1
|
|
||||||
)
|
|
||||||
|
|
||||||
func (err errNotFound) Error() string {
|
func (err errNotFound) Error() string {
|
||||||
if len(err.suggestions) == 0 {
|
if len(err.suggestions) == 0 {
|
||||||
return "No such key"
|
return "No such key"
|
||||||
|
|
|
||||||
66
cmd/sync.go
66
cmd/sync.go
|
|
@ -64,35 +64,36 @@ func sync(manual bool) error {
|
||||||
}
|
}
|
||||||
remoteAhead, behind, err := repoAheadBehind(repoDir, remoteInfo.Ref)
|
remoteAhead, behind, err := repoAheadBehind(repoDir, remoteInfo.Ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
ahead = 1 // ref doesn't exist yet; just push
|
||||||
}
|
} else {
|
||||||
ahead = remoteAhead
|
ahead = remoteAhead
|
||||||
if behind > 0 {
|
if behind > 0 {
|
||||||
if ahead > 0 {
|
if ahead > 0 {
|
||||||
return fmt.Errorf("repo diverged from remote (ahead %d, behind %d); resolve manually", ahead, behind)
|
return fmt.Errorf("repo diverged from remote (ahead %d, behind %d); resolve manually", ahead, behind)
|
||||||
}
|
}
|
||||||
fmt.Printf("remote has %d commit(s) not present locally; discard local changes and pull? (y/n)\n", behind)
|
fmt.Printf("remote has %d commit(s) not present locally; discard local changes and pull? (y/n)\n", behind)
|
||||||
var confirm string
|
var confirm string
|
||||||
if _, err := fmt.Scanln(&confirm); err != nil {
|
if _, err := fmt.Scanln(&confirm); err != nil {
|
||||||
return fmt.Errorf("cannot continue sync: %w", err)
|
return fmt.Errorf("cannot continue sync: %w", err)
|
||||||
}
|
}
|
||||||
if strings.ToLower(confirm) != "y" {
|
if strings.ToLower(confirm) != "y" {
|
||||||
return fmt.Errorf("aborted sync")
|
return fmt.Errorf("aborted sync")
|
||||||
}
|
}
|
||||||
dirty, err := repoHasChanges(repoDir)
|
dirty, err := repoHasChanges(repoDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
|
||||||
}
|
|
||||||
if dirty {
|
|
||||||
stashMsg := fmt.Sprintf("pda sync: %s", time.Now().UTC().Format(time.RFC3339))
|
|
||||||
if err := runGit(repoDir, "stash", "push", "-u", "-m", stashMsg); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if dirty {
|
||||||
|
stashMsg := fmt.Sprintf("pda sync: %s", time.Now().UTC().Format(time.RFC3339))
|
||||||
|
if err := runGit(repoDir, "stash", "push", "-u", "-m", stashMsg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := pullRemote(repoDir, remoteInfo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return restoreAllSnapshots(store, repoDir)
|
||||||
}
|
}
|
||||||
if err := pullRemote(repoDir, remoteInfo); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return restoreAllSnapshots(store, repoDir)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,7 +109,9 @@ func sync(manual bool) error {
|
||||||
}
|
}
|
||||||
madeCommit := false
|
madeCommit := false
|
||||||
if !changed {
|
if !changed {
|
||||||
fmt.Println("no changes to commit")
|
if manual {
|
||||||
|
fmt.Println("no changes to commit")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
msg := fmt.Sprintf("sync: %s", time.Now().UTC().Format(time.RFC3339))
|
msg := fmt.Sprintf("sync: %s", time.Now().UTC().Format(time.RFC3339))
|
||||||
if err := runGit(repoDir, "commit", "-m", msg); err != nil {
|
if err := runGit(repoDir, "commit", "-m", msg); err != nil {
|
||||||
|
|
@ -117,10 +120,15 @@ func sync(manual bool) error {
|
||||||
madeCommit = true
|
madeCommit = true
|
||||||
}
|
}
|
||||||
if manual || config.Git.AutoPush {
|
if manual || config.Git.AutoPush {
|
||||||
if remoteInfo.Ref != "" && (madeCommit || ahead > 0) {
|
if remoteInfo.Ref == "" {
|
||||||
|
if manual {
|
||||||
|
fmt.Println("no remote configured; skipping push")
|
||||||
|
}
|
||||||
|
} else if madeCommit || ahead > 0 {
|
||||||
return pushRemote(repoDir, remoteInfo)
|
return pushRemote(repoDir, remoteInfo)
|
||||||
|
} else if manual {
|
||||||
|
fmt.Println("nothing to push")
|
||||||
}
|
}
|
||||||
fmt.Println("no remote configured; skipping push")
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
63
cmd/vcs.go
63
cmd/vcs.go
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/dgraph-io/badger/v4"
|
|
||||||
gap "github.com/muesli/go-app-paths"
|
gap "github.com/muesli/go-app-paths"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -63,7 +62,7 @@ func writeGitignore(repoDir string) error {
|
||||||
if err := runGit(repoDir, "add", ".gitignore"); err != nil {
|
if err := runGit(repoDir, "add", ".gitignore"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return runGit(repoDir, "commit", "--allow-empty", "-m", "generated gitignore")
|
return runGit(repoDir, "commit", "-m", "generated gitignore")
|
||||||
}
|
}
|
||||||
fmt.Println("Existing .gitignore found.")
|
fmt.Println("Existing .gitignore found.")
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -82,8 +81,7 @@ func snapshotDB(store *Store, repoDir, db string) error {
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
opts := DumpOptions{
|
opts := DumpOptions{
|
||||||
Encoding: "auto",
|
Encoding: "auto",
|
||||||
IncludeSecret: false,
|
|
||||||
}
|
}
|
||||||
if err := dumpDatabase(store, db, f, opts); err != nil {
|
if err := dumpDatabase(store, db, f, opts); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -373,60 +371,7 @@ func restoreSnapshot(store *Store, path string, dbName string) error {
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
decoder := json.NewDecoder(bufio.NewReader(f))
|
decoder := json.NewDecoder(bufio.NewReader(f))
|
||||||
wb := db.NewWriteBatch()
|
_, err = restoreEntries(decoder, db, restoreOpts{})
|
||||||
defer wb.Cancel()
|
return err
|
||||||
|
|
||||||
entryNo := 0
|
|
||||||
for {
|
|
||||||
var entry dumpEntry
|
|
||||||
if err := decoder.Decode(&entry); err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return fmt.Errorf("entry %d: %w", entryNo+1, err)
|
|
||||||
}
|
|
||||||
entryNo++
|
|
||||||
if entry.Key == "" {
|
|
||||||
return fmt.Errorf("entry %d: missing key", entryNo)
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := decodeEntryValue(entry)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("entry %d: %w", entryNo, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
entryMeta := byte(0x0)
|
|
||||||
if entry.Secret {
|
|
||||||
entryMeta = metaSecret
|
|
||||||
}
|
|
||||||
|
|
||||||
writeEntry := badger.NewEntry([]byte(entry.Key), value).WithMeta(entryMeta)
|
|
||||||
if entry.ExpiresAt != nil {
|
|
||||||
if *entry.ExpiresAt < 0 {
|
|
||||||
return fmt.Errorf("entry %d: expires_at must be >= 0", entryNo)
|
|
||||||
}
|
|
||||||
writeEntry.ExpiresAt = uint64(*entry.ExpiresAt)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := wb.SetEntry(writeEntry); err != nil {
|
|
||||||
return fmt.Errorf("entry %d: %w", entryNo, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := wb.Flush(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasMergeConflicts returns true if there are files with unresolved merge
|
|
||||||
// conflicts in the working tree.
|
|
||||||
func hasMergeConflicts(dir string) (bool, error) {
|
|
||||||
cmd := exec.Command("git", "diff", "--name-only", "--diff-filter=U")
|
|
||||||
cmd.Dir = dir
|
|
||||||
out, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return len(bytes.TrimSpace(out)) > 0, nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
6
testdata/get__missing__err__with__any.ct
vendored
6
testdata/get__missing__err__with__any.ct
vendored
|
|
@ -8,7 +8,7 @@ $ pda get foobar --secret --> FAIL
|
||||||
Error: cannot get 'foobar': Key not found
|
Error: cannot get 'foobar': Key not found
|
||||||
Error: cannot get 'foobar': Key not found
|
Error: cannot get 'foobar': Key not found
|
||||||
Error: cannot get 'foobar': Key not found
|
Error: cannot get 'foobar': Key not found
|
||||||
|
Error: unknown flag: --secret
|
||||||
Error: cannot get 'foobar': Key not found
|
Error: cannot get 'foobar': Key not found
|
||||||
Error: cannot get 'foobar': Key not found
|
Error: unknown flag: --secret
|
||||||
Error: cannot get 'foobar': Key not found
|
Error: unknown flag: --secret
|
||||||
Error: cannot get 'foobar': Key not found
|
|
||||||
|
|
|
||||||
4
testdata/get__ok__with__binary_run_secret.ct
vendored
4
testdata/get__ok__with__binary_run_secret.ct
vendored
|
|
@ -1,4 +0,0 @@
|
||||||
$ fecho cmd echo hello
|
|
||||||
$ pda set foo < cmd
|
|
||||||
$ pda get foo --include-binary --run --secret
|
|
||||||
hello
|
|
||||||
6
testdata/get__ok__with__run_secret.ct
vendored
6
testdata/get__ok__with__run_secret.ct
vendored
|
|
@ -1,6 +0,0 @@
|
||||||
$ fecho cmd echo hello
|
|
||||||
$ pda set a < cmd
|
|
||||||
$ pda get a
|
|
||||||
echo hello
|
|
||||||
$ pda get a --run --secret
|
|
||||||
hello
|
|
||||||
3
testdata/get__ok__with__secret.ct
vendored
3
testdata/get__ok__with__secret.ct
vendored
|
|
@ -1,3 +0,0 @@
|
||||||
$ pda set foo bar
|
|
||||||
$ pda get foo --secret
|
|
||||||
bar
|
|
||||||
3
testdata/get__secret__err.ct
vendored
3
testdata/get__secret__err.ct
vendored
|
|
@ -1,3 +0,0 @@
|
||||||
$ pda set a b --secret
|
|
||||||
$ pda get a --> FAIL
|
|
||||||
Error: cannot get 'a': marked as secret, run with --secret
|
|
||||||
4
testdata/get__secret__err__with__binary.ct
vendored
4
testdata/get__secret__err__with__binary.ct
vendored
|
|
@ -1,4 +0,0 @@
|
||||||
$ fecho cmd echo hello world
|
|
||||||
$ pda set a --secret < cmd
|
|
||||||
$ pda get a --include-binary --> FAIL
|
|
||||||
Error: cannot get 'a': marked as secret, run with --secret
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
$ fecho cmd echo hello world
|
|
||||||
$ pda set a --secret < cmd
|
|
||||||
$ pda get a --include-binary --run --> FAIL
|
|
||||||
Error: cannot get 'a': marked as secret, run with --secret
|
|
||||||
4
testdata/get__secret__err__with__run.ct
vendored
4
testdata/get__secret__err__with__run.ct
vendored
|
|
@ -1,4 +0,0 @@
|
||||||
$ fecho cmd echo hello world
|
|
||||||
$ pda set a --secret < cmd
|
|
||||||
$ pda get a --run --> FAIL
|
|
||||||
Error: cannot get 'a': marked as secret, run with --secret
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
$ fecho cmd echo hello world
|
|
||||||
$ pda set a --secret < cmd
|
|
||||||
$ pda get a --secret --run --include-binary
|
|
||||||
hello world
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
$ fecho cmd echo hello world
|
|
||||||
$ pda set a --secret < cmd
|
|
||||||
$ pda get a --include-binary --secret
|
|
||||||
echo hello world
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
$ fecho cmd echo hello world
|
|
||||||
$ pda set a --secret < cmd
|
|
||||||
$ pda get a --run --secret
|
|
||||||
hello world
|
|
||||||
4
testdata/get__secret__ok__with__secret.ct
vendored
4
testdata/get__secret__ok__with__secret.ct
vendored
|
|
@ -1,4 +0,0 @@
|
||||||
$ fecho cmd echo hello world
|
|
||||||
$ pda set a --secret < cmd
|
|
||||||
$ pda get a --secret
|
|
||||||
echo hello world
|
|
||||||
2
testdata/help__dump__ok.ct
vendored
2
testdata/help__dump__ok.ct
vendored
|
|
@ -13,7 +13,6 @@ Flags:
|
||||||
-g, --glob strings Filter keys with glob pattern (repeatable)
|
-g, --glob strings Filter keys with glob pattern (repeatable)
|
||||||
--glob-sep string Characters treated as separators for globbing (default "/-_.@: ")
|
--glob-sep string Characters treated as separators for globbing (default "/-_.@: ")
|
||||||
-h, --help help for export
|
-h, --help help for export
|
||||||
--secret Include entries marked as secret
|
|
||||||
Dump all key/value pairs as NDJSON
|
Dump all key/value pairs as NDJSON
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
@ -27,4 +26,3 @@ Flags:
|
||||||
-g, --glob strings Filter keys with glob pattern (repeatable)
|
-g, --glob strings Filter keys with glob pattern (repeatable)
|
||||||
--glob-sep string Characters treated as separators for globbing (default "/-_.@: ")
|
--glob-sep string Characters treated as separators for globbing (default "/-_.@: ")
|
||||||
-h, --help help for export
|
-h, --help help for export
|
||||||
--secret Include entries marked as secret
|
|
||||||
|
|
|
||||||
2
testdata/help__get__ok.ct
vendored
2
testdata/help__get__ok.ct
vendored
|
|
@ -20,7 +20,6 @@ Flags:
|
||||||
-b, --include-binary include binary data in text output
|
-b, --include-binary include binary data in text output
|
||||||
--no-template directly output template syntax
|
--no-template directly output template syntax
|
||||||
-c, --run execute the result as a shell command
|
-c, --run execute the result as a shell command
|
||||||
--secret display values marked as secret
|
|
||||||
Get the value of a key. Optionally specify a store.
|
Get the value of a key. Optionally specify a store.
|
||||||
|
|
||||||
{{ .TEMPLATES }} can be filled by passing TEMPLATE=VALUE as an
|
{{ .TEMPLATES }} can be filled by passing TEMPLATE=VALUE as an
|
||||||
|
|
@ -41,4 +40,3 @@ Flags:
|
||||||
-b, --include-binary include binary data in text output
|
-b, --include-binary include binary data in text output
|
||||||
--no-template directly output template syntax
|
--no-template directly output template syntax
|
||||||
-c, --run execute the result as a shell command
|
-c, --run execute the result as a shell command
|
||||||
--secret display values marked as secret
|
|
||||||
|
|
|
||||||
2
testdata/help__list__ok.ct
vendored
2
testdata/help__list__ok.ct
vendored
|
|
@ -17,7 +17,6 @@ Flags:
|
||||||
-h, --help help for list
|
-h, --help help for list
|
||||||
--no-keys suppress the key column
|
--no-keys suppress the key column
|
||||||
--no-values suppress the value column
|
--no-values suppress the value column
|
||||||
-S, --secret display values marked as secret
|
|
||||||
-t, --ttl append a TTL column when entries expire
|
-t, --ttl append a TTL column when entries expire
|
||||||
List the contents of a store
|
List the contents of a store
|
||||||
|
|
||||||
|
|
@ -36,5 +35,4 @@ Flags:
|
||||||
-h, --help help for list
|
-h, --help help for list
|
||||||
--no-keys suppress the key column
|
--no-keys suppress the key column
|
||||||
--no-values suppress the value column
|
--no-values suppress the value column
|
||||||
-S, --secret display values marked as secret
|
|
||||||
-t, --ttl append a TTL column when entries expire
|
-t, --ttl append a TTL column when entries expire
|
||||||
|
|
|
||||||
2
testdata/help__set__ok.ct
vendored
2
testdata/help__set__ok.ct
vendored
|
|
@ -20,7 +20,6 @@ Aliases:
|
||||||
Flags:
|
Flags:
|
||||||
-h, --help help for set
|
-h, --help help for set
|
||||||
-i, --interactive Prompt before overwriting an existing key
|
-i, --interactive Prompt before overwriting an existing key
|
||||||
--secret Mark the stored value as a secret
|
|
||||||
-t, --ttl duration Expire the key after the provided duration (e.g. 24h, 30m)
|
-t, --ttl duration Expire the key after the provided duration (e.g. 24h, 30m)
|
||||||
Set a key to a given value or stdin. Optionally specify a store.
|
Set a key to a given value or stdin. Optionally specify a store.
|
||||||
|
|
||||||
|
|
@ -42,5 +41,4 @@ Aliases:
|
||||||
Flags:
|
Flags:
|
||||||
-h, --help help for set
|
-h, --help help for set
|
||||||
-i, --interactive Prompt before overwriting an existing key
|
-i, --interactive Prompt before overwriting an existing key
|
||||||
--secret Mark the stored value as a secret
|
|
||||||
-t, --ttl duration Expire the key after the provided duration (e.g. 24h, 30m)
|
-t, --ttl duration Expire the key after the provided duration (e.g. 24h, 30m)
|
||||||
|
|
|
||||||
3
testdata/remove__dedupe__ok.ct
vendored
3
testdata/remove__dedupe__ok.ct
vendored
|
|
@ -1,7 +1,8 @@
|
||||||
$ pda set foo 1
|
$ pda set foo 1
|
||||||
$ pda set bar 2
|
$ pda set bar 2
|
||||||
$ pda ls
|
$ pda ls
|
||||||
a **********
|
a echo hello
|
||||||
|
|
||||||
a1 1
|
a1 1
|
||||||
a2 2
|
a2 2
|
||||||
b1 3
|
b1 3
|
||||||
|
|
|
||||||
1
testdata/set__ok__with__secret.ct
vendored
1
testdata/set__ok__with__secret.ct
vendored
|
|
@ -1 +0,0 @@
|
||||||
$ pda set foo foobar --secret
|
|
||||||
1
testdata/set__ok__with__secret_ttl.ct
vendored
1
testdata/set__ok__with__secret_ttl.ct
vendored
|
|
@ -1 +0,0 @@
|
||||||
$ pda set a b --secret --ttl 10m
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue