mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 02:28:09 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			159 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			159 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package logical
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/hashicorp/errwrap"
 | |
| 	"github.com/hashicorp/go-hclog"
 | |
| 	"github.com/hashicorp/vault/sdk/helper/jsonutil"
 | |
| )
 | |
| 
 | |
| // ErrReadOnly is returned when a backend does not support
 | |
| // writing. This can be caused by a read-only replica or secondary
 | |
| // cluster operation.
 | |
| var ErrReadOnly = errors.New("cannot write to readonly storage")
 | |
| 
 | |
| // ErrSetupReadOnly is returned when a write operation is attempted on a
 | |
| // storage while the backend is still being setup.
 | |
| var ErrSetupReadOnly = errors.New("cannot write to storage during setup")
 | |
| 
 | |
| // Storage is the way that logical backends are able read/write data.
 | |
| type Storage interface {
 | |
| 	List(context.Context, string) ([]string, error)
 | |
| 	Get(context.Context, string) (*StorageEntry, error)
 | |
| 	Put(context.Context, *StorageEntry) error
 | |
| 	Delete(context.Context, string) error
 | |
| }
 | |
| 
 | |
| // StorageEntry is the entry for an item in a Storage implementation.
 | |
| type StorageEntry struct {
 | |
| 	Key      string
 | |
| 	Value    []byte
 | |
| 	SealWrap bool
 | |
| }
 | |
| 
 | |
| // DecodeJSON decodes the 'Value' present in StorageEntry.
 | |
| func (e *StorageEntry) DecodeJSON(out interface{}) error {
 | |
| 	return jsonutil.DecodeJSON(e.Value, out)
 | |
| }
 | |
| 
 | |
| // StorageEntryJSON creates a StorageEntry with a JSON-encoded value.
 | |
| func StorageEntryJSON(k string, v interface{}) (*StorageEntry, error) {
 | |
| 	encodedBytes, err := jsonutil.EncodeJSON(v)
 | |
| 	if err != nil {
 | |
| 		return nil, errwrap.Wrapf("failed to encode storage entry: {{err}}", err)
 | |
| 	}
 | |
| 
 | |
| 	return &StorageEntry{
 | |
| 		Key:   k,
 | |
| 		Value: encodedBytes,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| type ClearableView interface {
 | |
| 	List(context.Context, string) ([]string, error)
 | |
| 	Delete(context.Context, string) error
 | |
| }
 | |
| 
 | |
| // ScanView is used to scan all the keys in a view iteratively
 | |
| func ScanView(ctx context.Context, view ClearableView, cb func(path string)) error {
 | |
| 	frontier := []string{""}
 | |
| 	for len(frontier) > 0 {
 | |
| 		n := len(frontier)
 | |
| 		current := frontier[n-1]
 | |
| 		frontier = frontier[:n-1]
 | |
| 
 | |
| 		// List the contents
 | |
| 		contents, err := view.List(ctx, current)
 | |
| 		if err != nil {
 | |
| 			return errwrap.Wrapf(fmt.Sprintf("list failed at path %q: {{err}}", current), err)
 | |
| 		}
 | |
| 
 | |
| 		// Handle the contents in the directory
 | |
| 		for _, c := range contents {
 | |
| 			// Exit if the context has been canceled
 | |
| 			if ctx.Err() != nil {
 | |
| 				return ctx.Err()
 | |
| 			}
 | |
| 			fullPath := current + c
 | |
| 			if strings.HasSuffix(c, "/") {
 | |
| 				frontier = append(frontier, fullPath)
 | |
| 			} else {
 | |
| 				cb(fullPath)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // CollectKeys is used to collect all the keys in a view
 | |
| func CollectKeys(ctx context.Context, view ClearableView) ([]string, error) {
 | |
| 	return CollectKeysWithPrefix(ctx, view, "")
 | |
| }
 | |
| 
 | |
| // CollectKeysWithPrefix is used to collect all the keys in a view with a given prefix string
 | |
| func CollectKeysWithPrefix(ctx context.Context, view ClearableView, prefix string) ([]string, error) {
 | |
| 	var keys []string
 | |
| 
 | |
| 	cb := func(path string) {
 | |
| 		if strings.HasPrefix(path, prefix) {
 | |
| 			keys = append(keys, path)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Scan for all the keys
 | |
| 	if err := ScanView(ctx, view, cb); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return keys, nil
 | |
| }
 | |
| 
 | |
| // ClearView is used to delete all the keys in a view
 | |
| func ClearView(ctx context.Context, view ClearableView) error {
 | |
| 	return ClearViewWithLogging(ctx, view, nil)
 | |
| }
 | |
| 
 | |
| func ClearViewWithLogging(ctx context.Context, view ClearableView, logger hclog.Logger) error {
 | |
| 	if view == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if logger == nil {
 | |
| 		logger = hclog.NewNullLogger()
 | |
| 	}
 | |
| 
 | |
| 	// Collect all the keys
 | |
| 	keys, err := CollectKeys(ctx, view)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	logger.Debug("clearing view", "total_keys", len(keys))
 | |
| 
 | |
| 	// Delete all the keys
 | |
| 	var pctDone int
 | |
| 	for idx, key := range keys {
 | |
| 		// Rather than keep trying to do stuff with a canceled context, bail;
 | |
| 		// storage will fail anyways
 | |
| 		if ctx.Err() != nil {
 | |
| 			return ctx.Err()
 | |
| 		}
 | |
| 		if err := view.Delete(ctx, key); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		newPctDone := idx * 100.0 / len(keys)
 | |
| 		if int(newPctDone) > pctDone {
 | |
| 			pctDone = int(newPctDone)
 | |
| 			logger.Trace("view deletion progress", "percent", pctDone, "keys_deleted", idx)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	logger.Debug("view cleared")
 | |
| 
 | |
| 	return nil
 | |
| }
 | 
