blob: 16ba60b94875ce4c8b94d1fc08c1a2b002b6ebfb [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
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")
// Plugins using Paths.WriteForwardedStorage will need to use this sentinel
// in their path to write cross-cluster. See the description of that parameter
// for more information.
const PBPWFClusterSentinel = "{{clusterId}}"
// 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
}