Skip to content

Commit

Permalink
Add custom metadata to data responses (#51)
Browse files Browse the repository at this point in the history
* add custom_metadata field to data ReadOperation handler

* add custom_metadata field to data CreateOperation handler

* test for custom_metadata in data read and write responses

* fix some typos

* add custom_metadata field to data PatchOperation handler

* test for custom_metadata in data patch responses

* update help description to include patch
  • Loading branch information
ccapurso authored Oct 26, 2021
1 parent 6d00469 commit bc1c42d
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 39 deletions.
46 changes: 28 additions & 18 deletions path_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,11 @@ func (b *versionedKVBackend) pathDataRead() framework.OperationFunc {
Data: map[string]interface{}{
"data": nil,
"metadata": map[string]interface{}{
"version": verNum,
"created_time": ptypesTimestampToString(vm.CreatedTime),
"deletion_time": ptypesTimestampToString(vm.DeletionTime),
"destroyed": vm.Destroyed,
"version": verNum,
"created_time": ptypesTimestampToString(vm.CreatedTime),
"deletion_time": ptypesTimestampToString(vm.DeletionTime),
"destroyed": vm.Destroyed,
"custom_metadata": meta.CustomMetadata,
},
},
}
Expand Down Expand Up @@ -340,10 +341,11 @@ func (b *versionedKVBackend) pathDataWrite() framework.OperationFunc {

resp := &logical.Response{
Data: map[string]interface{}{
"version": meta.CurrentVersion,
"created_time": ptypesTimestampToString(vm.CreatedTime),
"deletion_time": ptypesTimestampToString(vm.DeletionTime),
"destroyed": vm.Destroyed,
"version": meta.CurrentVersion,
"created_time": ptypesTimestampToString(vm.CreatedTime),
"deletion_time": ptypesTimestampToString(vm.DeletionTime),
"destroyed": vm.Destroyed,
"custom_metadata": meta.CustomMetadata,
},
}

Expand Down Expand Up @@ -433,10 +435,11 @@ func (b *versionedKVBackend) pathDataPatch() framework.OperationFunc {
// or destroyed
notFoundResp := &logical.Response{
Data: map[string]interface{}{
"version": currentVersion,
"created_time": ptypesTimestampToString(versionMetadata.CreatedTime),
"deletion_time": ptypesTimestampToString(versionMetadata.DeletionTime),
"destroyed": versionMetadata.Destroyed,
"version": currentVersion,
"created_time": ptypesTimestampToString(versionMetadata.CreatedTime),
"deletion_time": ptypesTimestampToString(versionMetadata.DeletionTime),
"destroyed": versionMetadata.Destroyed,
"custom_metadata": meta.CustomMetadata,
},
}

Expand Down Expand Up @@ -535,10 +538,11 @@ func (b *versionedKVBackend) pathDataPatch() framework.OperationFunc {

resp := &logical.Response{
Data: map[string]interface{}{
"version": meta.CurrentVersion,
"created_time": ptypesTimestampToString(newVersionMetadata.CreatedTime),
"deletion_time": ptypesTimestampToString(newVersionMetadata.DeletionTime),
"destroyed": newVersionMetadata.Destroyed,
"version": meta.CurrentVersion,
"created_time": ptypesTimestampToString(newVersionMetadata.CreatedTime),
"deletion_time": ptypesTimestampToString(newVersionMetadata.DeletionTime),
"destroyed": newVersionMetadata.Destroyed,
"custom_metadata": meta.CustomMetadata,
},
}

Expand Down Expand Up @@ -650,9 +654,9 @@ func max(a, b uint32) uint32 {
return a
}

const dataHelpSyn = `Write, Read, and Delete data in the Key-Value Store.`
const dataHelpSyn = `Write, Patch, Read, and Delete data in the Key-Value Store.`
const dataHelpDesc = `
This path takes a key name and based on the opperation stores, retreives or
This path takes a key name and based on the operation stores, retrieves or
deletes versions of data.
If a write operation is used the endpoint takes an options object and a data
Expand All @@ -661,6 +665,12 @@ the data object is encrypted and stored in the storage backend. Each write
operation for a key creates a new version and does not overwrite the previous
data.
A patch operation must be performed on an existing secret. The secret must neither
be deleted nor destroyed. Like a write operation, patch operations accept an
options object and data object. The options object is used to pass some options to
the patch command and the data object is used to perform a partial update on the
current version of the secret and store the encrypted result in the storage backend.
A read operation will return the latest version for a key unless the "version"
parameter is set, then it returns the version at that number.
Expand Down
178 changes: 157 additions & 21 deletions path_data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,29 +34,82 @@ func getBackend(t *testing.T) (logical.Backend, logical.Storage) {
return b, config.StorageView
}

func keys(m map[string]interface{}) map[string]struct{} {
set := make(map[string]struct{})

for k := range m {
set[k] = struct{}{}
}

return set
}

// expectedMetadataKeys produces a deterministic set of expected
// metadata fields to ensure consistent shape across all endpoints
func expectedMetadataKeys() map[string]struct{} {
return map[string]struct{}{
"version": {},
"created_time": {},
"deletion_time": {},
"destroyed": {},
"custom_metadata": {},
}
}

func TestVersionedKV_Data_Put(t *testing.T) {
b, storage := getBackend(t)

customMetadata := map[string]string{
"foo": "abc",
"bar": "def",
}

metadata := map[string]interface{}{
"custom_metadata": customMetadata,
}

req := &logical.Request{
Operation: logical.CreateOperation,
Path: "metadata/foo",
Storage: storage,
Data: metadata,
}

resp, err := b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("metadata CreateOperation request failed, err: %s, resp %#v", err, resp)
}

data := map[string]interface{}{
"data": map[string]interface{}{
"bar": "baz",
},
}

req := &logical.Request{
req = &logical.Request{
Operation: logical.CreateOperation,
Path: "data/foo",
Storage: storage,
Data: data,
}

resp, err := b.HandleRequest(context.Background(), req)
resp, err = b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
t.Fatalf("data CreateOperation request failed, err: %s, resp %#v", err, resp)
}

actualKeys := keys(resp.Data)

if diff := deep.Equal(actualKeys, expectedMetadataKeys()); len(diff) > 0 {
t.Fatalf("metadata map keys mismatch, diff: %#v", diff)
}

if resp.Data["version"] != uint64(1) {
t.Fatalf("Bad response: %#v", resp)
t.Fatalf("expected version to be 1, resp: %#v", resp)
}

if diff := deep.Equal(resp.Data["custom_metadata"], customMetadata); len(diff) > 0 {
t.Fatalf("custom_metadata map mismatch, diff: %#v", diff)
}

data = map[string]interface{}{
Expand All @@ -77,11 +130,21 @@ func TestVersionedKV_Data_Put(t *testing.T) {

resp, err = b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
t.Fatalf("data CreateOperation request failed, err: %s, resp %#v", err, resp)
}

actualKeys = keys(resp.Data)

if diff := deep.Equal(actualKeys, expectedMetadataKeys()); len(diff) > 0 {
t.Fatalf("metadata map keys mismatch, diff: %#v", diff)
}

if resp.Data["version"] != uint64(2) {
t.Fatalf("Bad response: %#v", resp)
t.Fatalf("expected version to be 2, resp: %#v", resp)
}

if diff := deep.Equal(resp.Data["custom_metadata"], customMetadata); len(diff) > 0 {
t.Fatalf("custom_metadata map mismatch, diff: %#v", diff)
}
}

Expand Down Expand Up @@ -139,11 +202,32 @@ func TestVersionedKV_Data_Get(t *testing.T) {

resp, err := b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
t.Fatalf("data ReadOperation request failed, err: %s, resp %#v", err, resp)
}

if resp != nil {
t.Fatalf("Bad response: %#v", resp)
t.Fatalf("expected nil resp for data ReadOperation resp: %#v", resp)
}

customMetadata := map[string]string{
"foo": "abc",
"bar": "def",
}

metadata := map[string]interface{}{
"custom_metadata": customMetadata,
}

req = &logical.Request{
Operation: logical.CreateOperation,
Path: "metadata/foo",
Storage: storage,
Data: metadata,
}

resp, err = b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("metadata CreateOperation request failed, err: %s, resp %#v", err, resp)
}

data := map[string]interface{}{
Expand All @@ -161,11 +245,11 @@ func TestVersionedKV_Data_Get(t *testing.T) {

resp, err = b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%s resp:%#v\n", err, resp)
t.Fatalf("data CreateOperation request failed, err: %s, resp %#v", err, resp)
}

if resp.Data["version"] != uint64(1) {
t.Fatalf("Bad response: %#v", resp)
t.Fatalf("epxected version to be 1, resp: %#v", resp)
}

req = &logical.Request{
Expand All @@ -183,17 +267,32 @@ func TestVersionedKV_Data_Get(t *testing.T) {
t.Fatalf("Bad response: %#v", resp)
}

if resp.Data["metadata"].(map[string]interface{})["version"].(uint64) != uint64(1) {
t.Fatalf("Bad response: %#v", resp)
if _, ok := resp.Data["metadata"]; !ok {
t.Fatalf("data ReadOperation resp did not include metadata field, resp: %#v", resp)
}

parsed, err := time.Parse(time.RFC3339Nano, resp.Data["metadata"].(map[string]interface{})["created_time"].(string))
respMetadata := resp.Data["metadata"].(map[string]interface{})
actualMetadataKeys := keys(respMetadata)

if diff := deep.Equal(actualMetadataKeys, expectedMetadataKeys()); len(diff) > 0 {
t.Fatalf("metadata map keys mismatch, diff: %#v\n", diff)
}

if respMetadata["version"].(uint64) != uint64(1) {
t.Fatalf("expected version to be 1, resp: %#v", resp)
}

parsed, err := time.Parse(time.RFC3339Nano, respMetadata["created_time"].(string))
if err != nil {
t.Fatal(err)
t.Fatalf("failed to parse created_time: %#v", respMetadata["created_time"])
}

if !parsed.After(time.Now().Add(-1*time.Minute)) || !parsed.Before(time.Now()) {
t.Fatalf("Bad response: %#v", resp)
t.Fatalf("invalid created_time value: %#v", respMetadata["created_time"])
}

if diff := deep.Equal(respMetadata["custom_metadata"], customMetadata); len(diff) > 0 {
t.Fatalf("custom_metadata mismatch, diff: %#v\n", diff)
}
}

Expand Down Expand Up @@ -698,6 +797,27 @@ func TestVersionedKV_Patch_NoData(t *testing.T) {
func TestVersionedKV_Patch_Success(t *testing.T) {
b, storage := getBackend(t)

customMetadata := map[string]string{
"foo": "abc",
"bar": "def",
}

metadata := map[string]interface{}{
"custom_metadata": customMetadata,
}

req := &logical.Request{
Operation: logical.CreateOperation,
Path: "metadata/foo",
Storage: storage,
Data: metadata,
}

resp, err := b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("metadata CreateOperation request failed, err: %s, resp %#v", err, resp)
}

data := map[string]interface{}{
"data": map[string]interface{}{
"bar": "baz",
Expand All @@ -707,20 +827,26 @@ func TestVersionedKV_Patch_Success(t *testing.T) {
},
}

req := &logical.Request{
req = &logical.Request{
Operation: logical.CreateOperation,
Path: "data/foo",
Storage: storage,
Data: data,
}

resp, err := b.HandleRequest(context.Background(), req)
resp, err = b.HandleRequest(context.Background(), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("CreateOperation request failed - err:%s resp:%#v\n", err, resp)
t.Fatalf("data CreateOperation request failed - err:%s resp:%#v\n", err, resp)
}

actualKeys := keys(resp.Data)

if diff := deep.Equal(actualKeys, expectedMetadataKeys()); len(diff) > 0 {
t.Fatalf("metadata map keys mismatch, diff: %#v", diff)
}

if resp.Data["version"] != uint64(1) {
t.Fatalf("Bad response: %#v", resp)
t.Fatalf("expected version to be 1, resp: %#v", resp)
}

data = map[string]interface{}{
Expand All @@ -746,7 +872,17 @@ func TestVersionedKV_Patch_Success(t *testing.T) {
resp, err = b.HandleRequest(context.Background(), req)

if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("PatchOperation request failed - err:%s resp:%#v\n", err, resp)
t.Fatalf("data PatchOperation request failed - err:%s resp:%#v\n", err, resp)
}

actualKeys = keys(resp.Data)

if diff := deep.Equal(actualKeys, expectedMetadataKeys()); len(diff) > 0 {
t.Fatalf("metadata map keys mismatch, diff: %#v", diff)
}

if resp.Data["version"] != uint64(2) {
t.Fatalf("expected version to be 2, resp: %#v", resp)
}

req = &logical.Request{
Expand All @@ -758,7 +894,7 @@ func TestVersionedKV_Patch_Success(t *testing.T) {
resp, err = b.HandleRequest(context.Background(), req)

if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("Readperation request failed - err:%s resp:%#v\n", err, resp)
t.Fatalf("data ReadOperation request failed - err:%s resp:%#v\n", err, resp)
}

expectedData := map[string]interface{}{
Expand Down

0 comments on commit bc1c42d

Please sign in to comment.