Add JSON marshalling and unmarshalling specify model types as a field on a Table object

Signed-off-by: Ying Li <ying.li@docker.com>
This commit is contained in:
Ying Li 2016-06-08 16:20:45 -07:00
parent 0826dbe201
commit 2f62e8d260
6 changed files with 246 additions and 2 deletions

View File

@ -3,6 +3,7 @@ package storage
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"sort"
"time"
@ -43,6 +44,47 @@ func (r RDBKey) TableName() string {
return "tuf_keys"
}
// gorethink can't handle an UnmarshalJSON function (see https://github.com/dancannon/gorethink/issues/201),
// so do this here in an anonymous struct
func rdbTUFFileFromJSON(data []byte) (interface{}, error) {
a := struct {
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt time.Time `json:"deleted_at"`
Gun string `json:"gun"`
Role string `json:"role"`
Version int `json:"version"`
Sha256 string `json:"sha256"`
Data []byte `json:"data"`
TSchecksum string `json:"timestamp_checksum"`
}{}
if err := json.Unmarshal(data, &a); err != nil {
return RDBTUFFile{}, err
}
return RDBTUFFile{
Timing: rethinkdb.Timing{
CreatedAt: a.CreatedAt,
UpdatedAt: a.UpdatedAt,
DeletedAt: a.DeletedAt,
},
GunRoleVersion: []interface{}{a.Gun, a.Role, a.Version},
Gun: a.Gun,
Role: a.Role,
Version: a.Version,
Sha256: a.Sha256,
Data: a.Data,
TSchecksum: a.TSchecksum,
}, nil
}
func rdbKeyFromJSON(data []byte) (interface{}, error) {
rdb := RDBKey{}
if err := json.Unmarshal(data, &rdb); err != nil {
return RDBKey{}, err
}
return rdb, nil
}
// RethinkDB implements a MetaStore against the Rethink Database
type RethinkDB struct {
dbName string

View File

@ -27,6 +27,7 @@ var (
Config: map[string]string{
"write_acks": "majority",
},
JSONUnmarshaller: rdbTUFFileFromJSON,
}
// PubKeysRethinkTable is the table definition of notary server's public key information for TUF roles
@ -36,5 +37,6 @@ var (
SecondaryIndexes: map[string][]string{
rdbGunRoleIdx: {"gun", "role"},
},
JSONUnmarshaller: rdbKeyFromJSON,
}
)

View File

@ -0,0 +1,97 @@
package storage
import (
"encoding/json"
"fmt"
"testing"
"time"
"github.com/docker/notary/storage/rethinkdb"
"github.com/stretchr/testify/require"
)
func TestRDBTUFFileMarshalling(t *testing.T) {
created := time.Now().AddDate(-1, -1, -1)
updated := time.Now().AddDate(0, -5, 0)
deleted := time.Time{}
data := []byte("Hello world")
createdMarshalled, err := json.Marshal(created)
require.NoError(t, err)
updatedMarshalled, err := json.Marshal(updated)
require.NoError(t, err)
deletedMarshalled, err := json.Marshal(deleted)
require.NoError(t, err)
dataMarshalled, err := json.Marshal(data)
require.NoError(t, err)
jsonBytes := []byte(fmt.Sprintf(`
{
"created_at": %s,
"updated_at": %s,
"deleted_at": %s,
"gun_role_version": ["completely", "invalid", "garbage"],
"gun": "namespaced/name",
"role": "timestamp",
"version": 5,
"sha256": "56ee4a23129fc22c6cb4b4ba5f78d730c91ab6def514e80d807c947bb21f0d63",
"data": %s,
"timestamp_checksum": "ebe6b6e082c94ef24043f1786a7046432506c3d193a47c299ed48ff4413ad7b0"
}
`, createdMarshalled, updatedMarshalled, deletedMarshalled, dataMarshalled))
unmarshalledAnon, err := TUFFilesRethinkTable.JSONUnmarshaller(jsonBytes)
require.NoError(t, err)
unmarshalled, ok := unmarshalledAnon.(RDBTUFFile)
require.True(t, ok)
// There is some weirdness with comparing time.Time due to a location pointer,
// so let's use time.Time's equal function to compare times, and then re-assign
// the timing struct to compare the rest of the RDBTUFFile struct
require.True(t, created.Equal(unmarshalled.CreatedAt))
require.True(t, updated.Equal(unmarshalled.UpdatedAt))
require.True(t, deleted.Equal(unmarshalled.DeletedAt))
expected := RDBTUFFile{
Timing: unmarshalled.Timing,
GunRoleVersion: []interface{}{"namespaced/name", "timestamp", 5},
Gun: "namespaced/name",
Role: "timestamp",
Version: 5,
Sha256: "56ee4a23129fc22c6cb4b4ba5f78d730c91ab6def514e80d807c947bb21f0d63",
Data: data,
TSchecksum: "ebe6b6e082c94ef24043f1786a7046432506c3d193a47c299ed48ff4413ad7b0",
}
require.Equal(t, expected, unmarshalled)
}
func TestRDBTUFKeyMarshalling(t *testing.T) {
rdb := RDBKey{
Timing: rethinkdb.Timing{
CreatedAt: time.Now().AddDate(-1, -1, -1),
UpdatedAt: time.Now().AddDate(0, -5, 0),
DeletedAt: time.Time{},
},
Gun: "namespaced/name",
Role: "timestamp",
Cipher: "ecdsa",
Public: []byte("Hello world"),
}
jsonBytes, err := json.Marshal(rdb)
require.NoError(t, err)
unmarshalledAnon, err := PubKeysRethinkTable.JSONUnmarshaller(jsonBytes)
require.NoError(t, err)
unmarshalled, ok := unmarshalledAnon.(RDBKey)
require.True(t, ok)
// There is some weirdness with comparing time.Time due to a location pointer,
// so let's use time.Time's equal function to compare times, and then re-assign
// the timing struct to compare the rest of the RDBTUFFile struct
require.True(t, rdb.CreatedAt.Equal(unmarshalled.CreatedAt))
require.True(t, rdb.UpdatedAt.Equal(unmarshalled.UpdatedAt))
require.True(t, rdb.DeletedAt.Equal(unmarshalled.DeletedAt))
unmarshalled.Timing = rdb.Timing
require.Equal(t, rdb, unmarshalled)
}

View File

@ -1,6 +1,7 @@
package keydbstore
import (
"encoding/json"
"errors"
"fmt"
"sync"
@ -39,10 +40,46 @@ type RDBPrivateKey struct {
Private string `gorethink:"private"`
}
// gorethink can't handle an UnmarshalJSON function (see https://github.com/dancannon/gorethink/issues/201),
// so do this here in an anonymous struct
func rdbPrivateKeyFromJSON(data []byte) (interface{}, error) {
a := struct {
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt time.Time `json:"deleted_at"`
KeyID string `json:"key_id"`
EncryptionAlg string `json:"encryption_alg"`
KeywrapAlg string `json:"keywrap_alg"`
Algorithm string `json:"algorithm"`
PassphraseAlias string `json:"passphrase_alias"`
Public string `json:"public"`
Private string `json:"private"`
}{}
if err := json.Unmarshal(data, &a); err != nil {
return RDBPrivateKey{}, err
}
return RDBPrivateKey{
Timing: rethinkdb.Timing{
CreatedAt: a.CreatedAt,
UpdatedAt: a.UpdatedAt,
DeletedAt: a.DeletedAt,
},
KeyID: a.KeyID,
EncryptionAlg: a.EncryptionAlg,
KeywrapAlg: a.KeywrapAlg,
Algorithm: a.Algorithm,
PassphraseAlias: a.PassphraseAlias,
Public: a.Public,
Private: a.Private,
}, nil
}
// PrivateKeysRethinkTable is the table definition for notary signer's key information
var PrivateKeysRethinkTable = rethinkdb.Table{
Name: RDBPrivateKey{}.TableName(),
PrimaryKey: RDBPrivateKey{}.KeyID,
Name: RDBPrivateKey{}.TableName(),
PrimaryKey: RDBPrivateKey{}.KeyID,
JSONUnmarshaller: rdbPrivateKeyFromJSON,
}
// TableName sets a specific table name for our RDBPrivateKey

View File

@ -0,0 +1,63 @@
package keydbstore
import (
"encoding/json"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestRDBTUFFileMarshalling(t *testing.T) {
created := time.Now().AddDate(-1, -1, -1)
updated := time.Now().AddDate(0, -5, 0)
deleted := time.Time{}
createdMarshalled, err := json.Marshal(created)
require.NoError(t, err)
updatedMarshalled, err := json.Marshal(updated)
require.NoError(t, err)
deletedMarshalled, err := json.Marshal(deleted)
require.NoError(t, err)
jsonBytes := []byte(fmt.Sprintf(`
{
"created_at": %s,
"updated_at": %s,
"deleted_at": %s,
"key_id": "56ee4a23129fc22c6cb4b4ba5f78d730c91ab6def514e80d807c947bb21f0d63",
"encryption_alg": "A256GCM",
"keywrap_alg": "PBES2-HS256+A128KW",
"algorithm": "ecdsa",
"passphrase_alias": "timestamp_1",
"public": "Hello world public",
"private": "Hello world private"
}
`, createdMarshalled, updatedMarshalled, deletedMarshalled))
fmt.Println(string(jsonBytes))
unmarshalledAnon, err := PrivateKeysRethinkTable.JSONUnmarshaller(jsonBytes)
require.NoError(t, err)
unmarshalled, ok := unmarshalledAnon.(RDBPrivateKey)
require.True(t, ok)
// There is some weirdness with comparing time.Time due to a location pointer,
// so let's use time.Time's equal function to compare times, and then re-assign
// the timing struct to compare the rest of the RDBTUFFile struct
require.True(t, created.Equal(unmarshalled.CreatedAt))
require.True(t, updated.Equal(unmarshalled.UpdatedAt))
require.True(t, deleted.Equal(unmarshalled.DeletedAt))
expected := RDBPrivateKey{
Timing: unmarshalled.Timing,
KeyID: "56ee4a23129fc22c6cb4b4ba5f78d730c91ab6def514e80d807c947bb21f0d63",
EncryptionAlg: "A256GCM",
KeywrapAlg: "PBES2-HS256+A128KW",
Algorithm: "ecdsa",
PassphraseAlias: "timestamp_1",
Public: "Hello world public",
Private: "Hello world private",
}
require.Equal(t, expected, unmarshalled)
}

View File

@ -31,6 +31,9 @@ type Table struct {
// on the list of fields in the corrensponding slice value.
SecondaryIndexes map[string][]string
Config map[string]string
//JSONUnmarshaller takes a byte slice representing JSON data and knows how
//to unmarshal them into a model representing this table
JSONUnmarshaller func([]byte) (interface{}, error)
}
func (t Table) term(dbName string) gorethink.Term {