mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 17:52:32 +00:00
helper/backend: start this thing
This commit is contained in:
61
helper/backend/backend.go
Normal file
61
helper/backend/backend.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
// Backend is an implementation of vault.LogicalBackend that allows
|
||||
// the implementer to code a backend using a much more programmer-friendly
|
||||
// framework that handles a lot of the routing and validation for you.
|
||||
//
|
||||
// This is recommended over implementing vault.LogicalBackend directly.
|
||||
type Backend struct {
|
||||
Paths []*Path
|
||||
}
|
||||
|
||||
// Path is a single path that the backend responds to.
|
||||
type Path struct {
|
||||
// Pattern is the pattern of the URL that matches this path.
|
||||
//
|
||||
// This should be a valid regular expression. Named captures will be
|
||||
// exposed as fields that should map to a schema in Fields. If a named
|
||||
// capture is not a field in the Fields map, then it will be ignored.
|
||||
Pattern string
|
||||
|
||||
// Fields is the mapping of data fields to a schema describing that
|
||||
// field. Named captures in the Pattern also map to fields. If a named
|
||||
// capture name matches a PUT body name, the named capture takes
|
||||
// priority.
|
||||
//
|
||||
// Note that only named capture fields are available in every operation,
|
||||
// whereas all fields are avaiable in the Write operation.
|
||||
Fields map[string]*FieldSchema
|
||||
|
||||
// Root if not blank, denotes that this path requires root
|
||||
// privileges and the path pattern that is the root path. This can't
|
||||
// be a regular expression and must be an exact path. It may have a
|
||||
// trailing '*' to denote that it is a prefix, and not an exact match.
|
||||
Root string
|
||||
|
||||
// Callback is what is called when this path is requested with
|
||||
// a valid set of data.
|
||||
Callback func(*vault.Request, *FieldData) (*vault.Response, error)
|
||||
}
|
||||
|
||||
// FieldSchema is a basic schema to describe the format of a path field.
|
||||
type FieldSchema struct {
|
||||
Type FieldType
|
||||
}
|
||||
|
||||
func (t FieldType) Zero() interface{} {
|
||||
switch t {
|
||||
case TypeString:
|
||||
return ""
|
||||
case TypeInt:
|
||||
return 0
|
||||
case TypeBool:
|
||||
return false
|
||||
default:
|
||||
panic("unknown type: " + t.String())
|
||||
}
|
||||
}
|
||||
111
helper/backend/field_data.go
Normal file
111
helper/backend/field_data.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// FieldData is the structure passed to the callback to handle a path
|
||||
// containing the populated parameters for fields. This should be used
|
||||
// instead of the raw (*vault.Request).Data to access data in a type-safe
|
||||
// way.
|
||||
type FieldData struct {
|
||||
Raw map[string]interface{}
|
||||
Schema map[string]*FieldSchema
|
||||
}
|
||||
|
||||
// Get gets the value for the given field. If the key is an invalid field,
|
||||
// FieldData will panic. If you want a safer version of this method, use
|
||||
// GetOk. If the field k is not set, the default value (if set) will be
|
||||
// returned, otherwise the zero value will be returned.
|
||||
func (d *FieldData) Get(k string) interface{} {
|
||||
schema, ok := d.Schema[k]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("field %s not in the schema", k))
|
||||
}
|
||||
|
||||
value, ok := d.GetOk(k)
|
||||
if !ok {
|
||||
value = schema.Type.Zero()
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
// GetOk gets the value for the given field. The second return value
|
||||
// will be false if the key is invalid or the key is not set at all.
|
||||
func (d *FieldData) GetOk(k string) (interface{}, bool) {
|
||||
schema, ok := d.Schema[k]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
result, ok, err := d.GetOkErr(k)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("error reading %s: %s", k, err))
|
||||
}
|
||||
|
||||
if ok && result == nil {
|
||||
result = schema.Type.Zero()
|
||||
}
|
||||
|
||||
return result, ok
|
||||
}
|
||||
|
||||
// GetOkErr is the most conservative of all the Get methods. It returns
|
||||
// whether key is set or not, but also an error value. The error value is
|
||||
// non-nil if the field doesn't exist or there was an error parsing the
|
||||
// field value.
|
||||
func (d *FieldData) GetOkErr(k string) (interface{}, bool, error) {
|
||||
schema, ok := d.Schema[k]
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("unknown field: %s", k)
|
||||
}
|
||||
|
||||
switch schema.Type {
|
||||
case TypeBool:
|
||||
fallthrough
|
||||
case TypeInt:
|
||||
fallthrough
|
||||
case TypeString:
|
||||
return d.getPrimitive(k, schema)
|
||||
default:
|
||||
return nil, false,
|
||||
fmt.Errorf("unknown field type %s for field %s", schema.Type, k)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *FieldData) getPrimitive(
|
||||
k string, schema *FieldSchema) (interface{}, bool, error) {
|
||||
raw, ok := d.Raw[k]
|
||||
if !ok {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
switch schema.Type {
|
||||
case TypeBool:
|
||||
var result bool
|
||||
if err := mapstructure.WeakDecode(raw, &result); err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
|
||||
return result, true, nil
|
||||
case TypeInt:
|
||||
var result int
|
||||
if err := mapstructure.WeakDecode(raw, &result); err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
|
||||
return result, true, nil
|
||||
case TypeString:
|
||||
var result string
|
||||
if err := mapstructure.WeakDecode(raw, &result); err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
|
||||
return result, true, nil
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown type: %s", schema.Type))
|
||||
}
|
||||
}
|
||||
82
helper/backend/field_data_test.go
Normal file
82
helper/backend/field_data_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFieldDataGet(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
Schema map[string]*FieldSchema
|
||||
Raw map[string]interface{}
|
||||
Key string
|
||||
Value interface{}
|
||||
}{
|
||||
"string type, string value": {
|
||||
map[string]*FieldSchema{
|
||||
"foo": &FieldSchema{Type: TypeString},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
"foo",
|
||||
"bar",
|
||||
},
|
||||
|
||||
"string type, int value": {
|
||||
map[string]*FieldSchema{
|
||||
"foo": &FieldSchema{Type: TypeString},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"foo": 42,
|
||||
},
|
||||
"foo",
|
||||
"42",
|
||||
},
|
||||
|
||||
"string type, unset value": {
|
||||
map[string]*FieldSchema{
|
||||
"foo": &FieldSchema{Type: TypeString},
|
||||
},
|
||||
map[string]interface{}{},
|
||||
"foo",
|
||||
"",
|
||||
},
|
||||
|
||||
"int type, int value": {
|
||||
map[string]*FieldSchema{
|
||||
"foo": &FieldSchema{Type: TypeInt},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"foo": 42,
|
||||
},
|
||||
"foo",
|
||||
42,
|
||||
},
|
||||
|
||||
"bool type, bool value": {
|
||||
map[string]*FieldSchema{
|
||||
"foo": &FieldSchema{Type: TypeBool},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"foo": false,
|
||||
},
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
data := &FieldData{
|
||||
Raw: tc.Raw,
|
||||
Schema: tc.Schema,
|
||||
}
|
||||
|
||||
actual := data.Get(tc.Key)
|
||||
if !reflect.DeepEqual(actual, tc.Value) {
|
||||
t.Fatalf(
|
||||
"bad: %s\n\nExpected: %#v\nGot: %#v",
|
||||
name, tc.Value, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
16
helper/backend/field_type.go
Normal file
16
helper/backend/field_type.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package backend
|
||||
|
||||
//go:generate stringer -type=FieldType field_type.go
|
||||
|
||||
// FieldType is the enum of types that a field can be.
|
||||
type FieldType uint
|
||||
|
||||
const (
|
||||
TypeInvalid FieldType = 0
|
||||
TypeString FieldType = iota
|
||||
TypeInt
|
||||
TypeBool
|
||||
)
|
||||
|
||||
// FieldType has more methods defined on it in backend.go. They aren't
|
||||
// in this file since stringer doesn't like that.
|
||||
16
helper/backend/fieldtype_string.go
Normal file
16
helper/backend/fieldtype_string.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// generated by stringer -type=FieldType field_type.go; DO NOT EDIT
|
||||
|
||||
package backend
|
||||
|
||||
import "fmt"
|
||||
|
||||
const _FieldType_name = "TypeInvalidTypeStringTypeIntTypeBool"
|
||||
|
||||
var _FieldType_index = [...]uint8{0, 11, 21, 28, 36}
|
||||
|
||||
func (i FieldType) String() string {
|
||||
if i+1 >= FieldType(len(_FieldType_index)) {
|
||||
return fmt.Sprintf("FieldType(%d)", i)
|
||||
}
|
||||
return _FieldType_name[_FieldType_index[i]:_FieldType_index[i+1]]
|
||||
}
|
||||
Reference in New Issue
Block a user