mirror of https://github.com/docker/docs.git
206 lines
6.1 KiB
Go
206 lines
6.1 KiB
Go
package storage
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/dancannon/gorethink"
|
|
"github.com/docker/notary/storage/rethinkdb"
|
|
)
|
|
|
|
// RDBTUFFile is a tuf file record
|
|
type RDBTUFFile struct {
|
|
rethinkdb.Timing
|
|
GunRoleVersion []interface{} `gorethink:"gun_role_version"`
|
|
Gun string `gorethink:"gun"`
|
|
Role string `gorethink:"role"`
|
|
Version int `gorethink:"version"`
|
|
Sha256 string `gorethink:"sha256"`
|
|
Data []byte `gorethink:"data"`
|
|
}
|
|
|
|
// TableName returns the table name for the record type
|
|
func (r RDBTUFFile) TableName() string {
|
|
return "tuf_files"
|
|
}
|
|
|
|
// RDBKey is the public key record
|
|
type RDBKey struct {
|
|
rethinkdb.Timing
|
|
Gun string `gorethink:"gun"`
|
|
Role string `gorethink:"role"`
|
|
Cipher string `gorethink:"cipher"`
|
|
Public []byte `gorethink:"public"`
|
|
}
|
|
|
|
// TableName returns the table name for the record type
|
|
func (r RDBKey) TableName() string {
|
|
return "tuf_keys"
|
|
}
|
|
|
|
// RethinkDB implements a MetaStore against the Rethink Database
|
|
type RethinkDB struct {
|
|
dbName string
|
|
sess *gorethink.Session
|
|
}
|
|
|
|
// NewRethinkDBStorage initializes a RethinkDB object
|
|
func NewRethinkDBStorage(dbName string, sess *gorethink.Session) RethinkDB {
|
|
return RethinkDB{
|
|
dbName: dbName,
|
|
sess: sess,
|
|
}
|
|
}
|
|
|
|
// GetKey returns the cipher and public key for the given GUN and role.
|
|
// If the GUN+role don't exist, returns an error.
|
|
func (rdb RethinkDB) GetKey(gun, role string) (cipher string, public []byte, err error) {
|
|
var key RDBKey
|
|
res, err := gorethink.DB(rdb.dbName).Table(key.TableName()).GetAllByIndex(
|
|
rdbGunRoleIdx, []string{gun, role},
|
|
).Run(rdb.sess)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
defer res.Close()
|
|
err = res.One(&key)
|
|
if err == gorethink.ErrEmptyResult {
|
|
return "", nil, &ErrNoKey{gun: gun}
|
|
}
|
|
return key.Cipher, key.Public, err
|
|
}
|
|
|
|
// SetKey sets the cipher and public key for the given GUN and role if
|
|
// it doesn't already exist. Otherwise an error is returned.
|
|
func (rdb RethinkDB) SetKey(gun, role, cipher string, public []byte) error {
|
|
now := time.Now()
|
|
key := RDBKey{
|
|
Timing: rethinkdb.Timing{
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
Gun: gun,
|
|
Role: role,
|
|
Cipher: cipher,
|
|
Public: public,
|
|
}
|
|
_, err := gorethink.DB(rdb.dbName).Table(key.TableName()).Insert(key).RunWrite(rdb.sess)
|
|
return err
|
|
}
|
|
|
|
// UpdateCurrent adds new metadata version for the given GUN if and only
|
|
// if it's a new role, or the version is greater than the current version
|
|
// for the role. Otherwise an error is returned.
|
|
func (rdb RethinkDB) UpdateCurrent(gun string, update MetaUpdate) error {
|
|
now := time.Now()
|
|
checksum := sha256.Sum256(update.Data)
|
|
file := RDBTUFFile{
|
|
Timing: rethinkdb.Timing{
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
GunRoleVersion: []interface{}{gun, update.Role, update.Version},
|
|
Gun: gun,
|
|
Role: update.Role,
|
|
Version: update.Version,
|
|
Sha256: hex.EncodeToString(checksum[:]),
|
|
Data: update.Data,
|
|
}
|
|
_, err := gorethink.DB(rdb.dbName).Table(file.TableName()).Insert(
|
|
file,
|
|
gorethink.InsertOpts{
|
|
Conflict: "error", // default but explicit for clarity of intent
|
|
},
|
|
).RunWrite(rdb.sess)
|
|
if err != nil && gorethink.IsConflictErr(err) {
|
|
return &ErrOldVersion{}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// UpdateMany adds multiple new metadata for the given GUN. RethinkDB does
|
|
// not support transactions, therefore we will attempt to insert the timestamp
|
|
// first as this represents a published version of the repo. If this is successful,
|
|
// we will insert the remaining roles (in any order). If any of those roles
|
|
// errors on insert, we will do a best effort rollback, at a minimum attempting
|
|
// to delete the timestamp so nobody pulls a broken repo.
|
|
func (rdb RethinkDB) UpdateMany(gun string, updates []MetaUpdate) error {
|
|
for _, up := range updates {
|
|
if err := rdb.UpdateCurrent(gun, up); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetCurrent returns the modification date and data part of the metadata for
|
|
// the latest version of the given GUN and role. If there is no data for
|
|
// the given GUN and role, an error is returned.
|
|
func (rdb RethinkDB) GetCurrent(gun, role string) (created *time.Time, data []byte, err error) {
|
|
file := RDBTUFFile{}
|
|
res, err := gorethink.DB(rdb.dbName).Table(file.TableName(), gorethink.TableOpts{ReadMode: "majority"}).GetAllByIndex(
|
|
rdbGunRoleIdx, []string{gun, role},
|
|
).OrderBy(gorethink.Desc("version")).Run(rdb.sess)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
defer res.Close()
|
|
if res.IsNil() {
|
|
return nil, nil, ErrNotFound{}
|
|
}
|
|
err = res.One(&file)
|
|
if err == gorethink.ErrEmptyResult {
|
|
return nil, nil, ErrNotFound{}
|
|
}
|
|
return &file.CreatedAt, file.Data, err
|
|
}
|
|
|
|
// GetChecksum returns the given TUF role file and creation date for the
|
|
// GUN with the provided checksum. If the given (gun, role, checksum) are
|
|
// not found, it returns storage.ErrNotFound
|
|
func (rdb RethinkDB) GetChecksum(gun, role, checksum string) (created *time.Time, data []byte, err error) {
|
|
var file RDBTUFFile
|
|
res, err := gorethink.DB(rdb.dbName).Table(file.TableName(), gorethink.TableOpts{ReadMode: "majority"}).GetAllByIndex(
|
|
rdbGunRoleSha256Idx, []string{gun, role, checksum},
|
|
).Run(rdb.sess)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
defer res.Close()
|
|
if res.IsNil() {
|
|
return nil, nil, ErrNotFound{}
|
|
}
|
|
err = res.One(&file)
|
|
if err == gorethink.ErrEmptyResult {
|
|
return nil, nil, ErrNotFound{}
|
|
}
|
|
return &file.CreatedAt, file.Data, err
|
|
}
|
|
|
|
// Delete removes all metadata for a given GUN. It does not return an
|
|
// error if no metadata exists for the given GUN.
|
|
func (rdb RethinkDB) Delete(gun string) error {
|
|
_, err := gorethink.DB(rdb.dbName).Table(RDBTUFFile{}.TableName()).GetAllByIndex(
|
|
"gun", []string{gun},
|
|
).Delete().RunWrite(rdb.sess)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to delete %s from database: %s", gun, err.Error())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Bootstrap sets up the database and tables
|
|
func (rdb RethinkDB) Bootstrap() error {
|
|
return rethinkdb.SetupDB(rdb.sess, rdb.dbName, []rethinkdb.Table{
|
|
tufFiles,
|
|
keys,
|
|
})
|
|
}
|
|
|
|
// CheckHealth is currently a noop
|
|
func (rdb RethinkDB) CheckHealth() error {
|
|
return nil
|
|
}
|