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 }