manager/state/store/resources.go
package store
import (
"strings"
memdb "github.com/hashicorp/go-memdb"
"github.com/moby/swarmkit/v2/api"
"github.com/pkg/errors"
)
const tableResource = "resource"
var (
// ErrNoKind is returned by resource create operations if the provided Kind
// of the resource does not exist
ErrNoKind = errors.New("object kind is unregistered")
)
func init() {
register(ObjectStoreConfig{
Table: &memdb.TableSchema{
Name: tableResource,
Indexes: map[string]*memdb.IndexSchema{
indexID: {
Name: indexID,
Unique: true,
Indexer: resourceIndexerByID{},
},
indexName: {
Name: indexName,
Unique: true,
Indexer: resourceIndexerByName{},
},
indexKind: {
Name: indexKind,
Indexer: resourceIndexerByKind{},
},
indexCustom: {
Name: indexCustom,
Indexer: resourceCustomIndexer{},
AllowMissing: true,
},
},
},
Save: func(tx ReadTx, snapshot *api.StoreSnapshot) error {
var err error
snapshot.Resources, err = FindResources(tx, All)
return err
},
Restore: func(tx Tx, snapshot *api.StoreSnapshot) error {
toStoreObj := make([]api.StoreObject, len(snapshot.Resources))
for i, x := range snapshot.Resources {
toStoreObj[i] = resourceEntry{x}
}
return RestoreTable(tx, tableResource, toStoreObj)
},
ApplyStoreAction: func(tx Tx, sa api.StoreAction) error {
switch v := sa.Target.(type) {
case *api.StoreAction_Resource:
obj := v.Resource
switch sa.Action {
case api.StoreActionKindCreate:
return CreateResource(tx, obj)
case api.StoreActionKindUpdate:
return UpdateResource(tx, obj)
case api.StoreActionKindRemove:
return DeleteResource(tx, obj.ID)
}
}
return errUnknownStoreAction
},
})
}
type resourceEntry struct {
*api.Resource
}
func (r resourceEntry) CopyStoreObject() api.StoreObject {
return resourceEntry{Resource: r.Resource.Copy()}
}
// ensure that when update events are emitted, we unwrap resourceEntry
func (r resourceEntry) EventUpdate(oldObject api.StoreObject) api.Event {
if oldObject != nil {
return api.EventUpdateResource{Resource: r.Resource, OldResource: oldObject.(resourceEntry).Resource}
}
return api.EventUpdateResource{Resource: r.Resource}
}
func confirmExtension(tx Tx, r *api.Resource) error {
// There must be an extension corresponding to the Kind field.
extensions, err := FindExtensions(tx, ByName(r.Kind))
if err != nil {
return errors.Wrap(err, "failed to query extensions")
}
if len(extensions) == 0 {
return ErrNoKind
}
return nil
}
// CreateResource adds a new resource object to the store.
// Returns ErrExist if the ID is already taken.
// Returns ErrNameConflict if a Resource with this Name already exists
// Returns ErrNoKind if the specified Kind does not exist
func CreateResource(tx Tx, r *api.Resource) error {
if err := confirmExtension(tx, r); err != nil {
return err
}
// TODO(dperny): currently the "name" index is unique, which means only one
// Resource of _any_ Kind can exist with that name. This isn't a problem
// right now, but the ideal case would be for names to be namespaced to the
// kind.
if tx.lookup(tableResource, indexName, strings.ToLower(r.Annotations.Name)) != nil {
return ErrNameConflict
}
return tx.create(tableResource, resourceEntry{r})
}
// UpdateResource updates an existing resource object in the store.
// Returns ErrNotExist if the object doesn't exist.
func UpdateResource(tx Tx, r *api.Resource) error {
if err := confirmExtension(tx, r); err != nil {
return err
}
return tx.update(tableResource, resourceEntry{r})
}
// DeleteResource removes a resource object from the store.
// Returns ErrNotExist if the object doesn't exist.
func DeleteResource(tx Tx, id string) error {
return tx.delete(tableResource, id)
}
// GetResource looks up a resource object by ID.
// Returns nil if the object doesn't exist.
func GetResource(tx ReadTx, id string) *api.Resource {
r := tx.get(tableResource, id)
if r == nil {
return nil
}
return r.(resourceEntry).Resource
}
// FindResources selects a set of resource objects and returns them.
func FindResources(tx ReadTx, by By) ([]*api.Resource, error) {
checkType := func(by By) error {
switch by.(type) {
case byIDPrefix, byName, byNamePrefix, byKind, byCustom, byCustomPrefix:
return nil
default:
return ErrInvalidFindBy
}
}
resourceList := []*api.Resource{}
appendResult := func(o api.StoreObject) {
resourceList = append(resourceList, o.(resourceEntry).Resource)
}
err := tx.find(tableResource, by, checkType, appendResult)
return resourceList, err
}
type resourceIndexerByKind struct{}
func (ri resourceIndexerByKind) FromArgs(args ...interface{}) ([]byte, error) {
return fromArgs(args...)
}
func (ri resourceIndexerByKind) FromObject(obj interface{}) (bool, []byte, error) {
r := obj.(resourceEntry)
// Add the null character as a terminator
val := r.Resource.Kind + "\x00"
return true, []byte(val), nil
}
type resourceIndexerByID struct{}
func (indexer resourceIndexerByID) FromArgs(args ...interface{}) ([]byte, error) {
return api.ResourceIndexerByID{}.FromArgs(args...)
}
func (indexer resourceIndexerByID) PrefixFromArgs(args ...interface{}) ([]byte, error) {
return api.ResourceIndexerByID{}.PrefixFromArgs(args...)
}
func (indexer resourceIndexerByID) FromObject(obj interface{}) (bool, []byte, error) {
return api.ResourceIndexerByID{}.FromObject(obj.(resourceEntry).Resource)
}
type resourceIndexerByName struct{}
func (indexer resourceIndexerByName) FromArgs(args ...interface{}) ([]byte, error) {
return api.ResourceIndexerByName{}.FromArgs(args...)
}
func (indexer resourceIndexerByName) PrefixFromArgs(args ...interface{}) ([]byte, error) {
return api.ResourceIndexerByName{}.PrefixFromArgs(args...)
}
func (indexer resourceIndexerByName) FromObject(obj interface{}) (bool, []byte, error) {
return api.ResourceIndexerByName{}.FromObject(obj.(resourceEntry).Resource)
}
type resourceCustomIndexer struct{}
func (indexer resourceCustomIndexer) FromArgs(args ...interface{}) ([]byte, error) {
return api.ResourceCustomIndexer{}.FromArgs(args...)
}
func (indexer resourceCustomIndexer) PrefixFromArgs(args ...interface{}) ([]byte, error) {
return api.ResourceCustomIndexer{}.PrefixFromArgs(args...)
}
func (indexer resourceCustomIndexer) FromObject(obj interface{}) (bool, [][]byte, error) {
return api.ResourceCustomIndexer{}.FromObject(obj.(resourceEntry).Resource)
}