github.com/hashicorp/vault/sdk@v0.13.0/logical/storage.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package logical
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"strings"
    11  
    12  	"github.com/hashicorp/errwrap"
    13  	"github.com/hashicorp/go-hclog"
    14  	"github.com/hashicorp/vault/sdk/helper/jsonutil"
    15  )
    16  
    17  // ErrReadOnly is returned when a backend does not support
    18  // writing. This can be caused by a read-only replica or secondary
    19  // cluster operation.
    20  var ErrReadOnly = errors.New("cannot write to readonly storage")
    21  
    22  // ErrSetupReadOnly is returned when a write operation is attempted on a
    23  // storage while the backend is still being setup.
    24  var ErrSetupReadOnly = errors.New("cannot write to storage during setup")
    25  
    26  // Plugins using Paths.WriteForwardedStorage will need to use this sentinel
    27  // in their path to write cross-cluster. See the description of that parameter
    28  // for more information.
    29  const PBPWFClusterSentinel = "{{clusterId}}"
    30  
    31  // Storage is the way that logical backends are able read/write data.
    32  type Storage interface {
    33  	List(context.Context, string) ([]string, error)
    34  	Get(context.Context, string) (*StorageEntry, error)
    35  	Put(context.Context, *StorageEntry) error
    36  	Delete(context.Context, string) error
    37  }
    38  
    39  // StorageEntry is the entry for an item in a Storage implementation.
    40  type StorageEntry struct {
    41  	Key      string
    42  	Value    []byte
    43  	SealWrap bool
    44  }
    45  
    46  // DecodeJSON decodes the 'Value' present in StorageEntry.
    47  func (e *StorageEntry) DecodeJSON(out interface{}) error {
    48  	return jsonutil.DecodeJSON(e.Value, out)
    49  }
    50  
    51  // StorageEntryJSON creates a StorageEntry with a JSON-encoded value.
    52  func StorageEntryJSON(k string, v interface{}) (*StorageEntry, error) {
    53  	encodedBytes, err := jsonutil.EncodeJSON(v)
    54  	if err != nil {
    55  		return nil, errwrap.Wrapf("failed to encode storage entry: {{err}}", err)
    56  	}
    57  
    58  	return &StorageEntry{
    59  		Key:   k,
    60  		Value: encodedBytes,
    61  	}, nil
    62  }
    63  
    64  type ClearableView interface {
    65  	List(context.Context, string) ([]string, error)
    66  	Delete(context.Context, string) error
    67  }
    68  
    69  // ScanView is used to scan all the keys in a view iteratively
    70  func ScanView(ctx context.Context, view ClearableView, cb func(path string)) error {
    71  	frontier := []string{""}
    72  	for len(frontier) > 0 {
    73  		n := len(frontier)
    74  		current := frontier[n-1]
    75  		frontier = frontier[:n-1]
    76  
    77  		// List the contents
    78  		contents, err := view.List(ctx, current)
    79  		if err != nil {
    80  			return errwrap.Wrapf(fmt.Sprintf("list failed at path %q: {{err}}", current), err)
    81  		}
    82  
    83  		// Handle the contents in the directory
    84  		for _, c := range contents {
    85  			// Exit if the context has been canceled
    86  			if ctx.Err() != nil {
    87  				return ctx.Err()
    88  			}
    89  			fullPath := current + c
    90  			if strings.HasSuffix(c, "/") {
    91  				frontier = append(frontier, fullPath)
    92  			} else {
    93  				cb(fullPath)
    94  			}
    95  		}
    96  	}
    97  	return nil
    98  }
    99  
   100  // AbortableScanView is used to scan all the keys in a view iteratively,
   101  // but will abort the scan if cb returns false
   102  func AbortableScanView(ctx context.Context, view ClearableView, cb func(path string) (cont bool)) error {
   103  	frontier := []string{""}
   104  	for len(frontier) > 0 {
   105  		n := len(frontier)
   106  		current := frontier[n-1]
   107  		frontier = frontier[:n-1]
   108  
   109  		// List the contents
   110  		contents, err := view.List(ctx, current)
   111  		if err != nil {
   112  			return errwrap.Wrapf(fmt.Sprintf("list failed at path %q: {{err}}", current), err)
   113  		}
   114  
   115  		// Handle the contents in the directory
   116  		for _, c := range contents {
   117  			// Exit if the context has been canceled
   118  			if ctx.Err() != nil {
   119  				return ctx.Err()
   120  			}
   121  			fullPath := current + c
   122  			if strings.HasSuffix(c, "/") {
   123  				frontier = append(frontier, fullPath)
   124  			} else {
   125  				if !cb(fullPath) {
   126  					return nil
   127  				}
   128  			}
   129  		}
   130  	}
   131  	return nil
   132  }
   133  
   134  // CollectKeys is used to collect all the keys in a view
   135  func CollectKeys(ctx context.Context, view ClearableView) ([]string, error) {
   136  	return CollectKeysWithPrefix(ctx, view, "")
   137  }
   138  
   139  // CollectKeysWithPrefix is used to collect all the keys in a view with a given prefix string
   140  func CollectKeysWithPrefix(ctx context.Context, view ClearableView, prefix string) ([]string, error) {
   141  	var keys []string
   142  
   143  	cb := func(path string) {
   144  		if strings.HasPrefix(path, prefix) {
   145  			keys = append(keys, path)
   146  		}
   147  	}
   148  
   149  	// Scan for all the keys
   150  	if err := ScanView(ctx, view, cb); err != nil {
   151  		return nil, err
   152  	}
   153  	return keys, nil
   154  }
   155  
   156  // ClearView is used to delete all the keys in a view
   157  func ClearView(ctx context.Context, view ClearableView) error {
   158  	return ClearViewWithLogging(ctx, view, nil)
   159  }
   160  
   161  func ClearViewWithLogging(ctx context.Context, view ClearableView, logger hclog.Logger) error {
   162  	if view == nil {
   163  		return nil
   164  	}
   165  
   166  	if logger == nil {
   167  		logger = hclog.NewNullLogger()
   168  	}
   169  
   170  	// Collect all the keys
   171  	keys, err := CollectKeys(ctx, view)
   172  	if err != nil {
   173  		return err
   174  	}
   175  
   176  	logger.Debug("clearing view", "total_keys", len(keys))
   177  
   178  	// Delete all the keys
   179  	var pctDone int
   180  	for idx, key := range keys {
   181  		// Rather than keep trying to do stuff with a canceled context, bail;
   182  		// storage will fail anyways
   183  		if ctx.Err() != nil {
   184  			return ctx.Err()
   185  		}
   186  		if err := view.Delete(ctx, key); err != nil {
   187  			return err
   188  		}
   189  
   190  		newPctDone := idx * 100.0 / len(keys)
   191  		if int(newPctDone) > pctDone {
   192  			pctDone = int(newPctDone)
   193  			logger.Trace("view deletion progress", "percent", pctDone, "keys_deleted", idx)
   194  		}
   195  	}
   196  
   197  	logger.Debug("view cleared")
   198  
   199  	return nil
   200  }