mirror of
https://github.com/openziti/ziti.git
synced 2026-09-17 08:05:31 +00:00
441 lines
14 KiB
Go
441 lines
14 KiB
Go
/*
|
|
Copyright NetFoundry, Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package model
|
|
|
|
import (
|
|
"github.com/michaelquigley/pfxlog"
|
|
"github.com/openziti/edge/controller/persistence"
|
|
"github.com/openziti/edge/eid"
|
|
"github.com/openziti/fabric/controller/models"
|
|
"github.com/openziti/foundation/storage/ast"
|
|
"github.com/openziti/foundation/storage/boltz"
|
|
"github.com/openziti/foundation/util/errorz"
|
|
"github.com/pkg/errors"
|
|
"go.etcd.io/bbolt"
|
|
"reflect"
|
|
)
|
|
|
|
type Handler interface {
|
|
models.EntityRetriever
|
|
|
|
GetEnv() Env
|
|
|
|
newModelEntity() boltEntitySink
|
|
readEntityInTx(tx *bbolt.Tx, id string, modelEntity boltEntitySink) error
|
|
}
|
|
|
|
func newBaseHandler(env Env, store boltz.CrudStore) baseHandler {
|
|
return baseHandler{
|
|
BaseController: models.BaseController{
|
|
Store: store,
|
|
},
|
|
env: env,
|
|
}
|
|
}
|
|
|
|
type baseHandler struct {
|
|
models.BaseController
|
|
env Env
|
|
impl Handler
|
|
}
|
|
|
|
func (handler *baseHandler) GetStore() boltz.CrudStore {
|
|
return handler.Store
|
|
}
|
|
|
|
func (handler *baseHandler) GetDb() boltz.Db {
|
|
return handler.env.GetDbProvider().GetDb()
|
|
}
|
|
|
|
func (handler *baseHandler) GetEnv() Env {
|
|
return handler.env
|
|
}
|
|
|
|
func (handler *baseHandler) BaseLoad(id string) (models.Entity, error) {
|
|
entity := handler.impl.newModelEntity()
|
|
if err := handler.readEntity(id, entity); err != nil {
|
|
return nil, err
|
|
}
|
|
return entity, nil
|
|
}
|
|
|
|
func (handler *baseHandler) BaseLoadInTx(tx *bbolt.Tx, id string) (models.Entity, error) {
|
|
entity := handler.impl.newModelEntity()
|
|
if err := handler.readEntityInTx(tx, id, entity); err != nil {
|
|
return nil, err
|
|
}
|
|
return entity, nil
|
|
}
|
|
|
|
func (handler *baseHandler) BaseList(query string) (*models.EntityListResult, error) {
|
|
result := &models.EntityListResult{Loader: handler}
|
|
if err := handler.list(query, result.Collect); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (handler *baseHandler) BasePreparedList(query ast.Query) (*models.EntityListResult, error) {
|
|
result := &models.EntityListResult{Loader: handler}
|
|
if err := handler.preparedList(query, result.Collect); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (handler *baseHandler) BasePreparedListIndexed(cursorProvider ast.SetCursorProvider, query ast.Query) (*models.EntityListResult, error) {
|
|
result := &models.EntityListResult{Loader: handler}
|
|
if err := handler.preparedListIndexed(cursorProvider, query, result.Collect); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (handler *baseHandler) preparedList(query ast.Query, resultHandler models.ListResultHandler) error {
|
|
return handler.GetDb().View(func(tx *bbolt.Tx) error {
|
|
return handler.PreparedListWithTx(tx, query, resultHandler)
|
|
})
|
|
}
|
|
|
|
func (handler *baseHandler) preparedListIndexed(cursorProvider ast.SetCursorProvider, query ast.Query, resultHandler models.ListResultHandler) error {
|
|
return handler.GetDb().View(func(tx *bbolt.Tx) error {
|
|
return handler.PreparedListIndexedWithTx(tx, cursorProvider, query, resultHandler)
|
|
})
|
|
}
|
|
|
|
func (handler *baseHandler) BasePreparedListAssociated(id string, typeLoader models.EntityRetriever, query ast.Query) (*models.EntityListResult, error) {
|
|
result := &models.EntityListResult{Loader: typeLoader}
|
|
err := handler.GetDb().View(func(tx *bbolt.Tx) error {
|
|
return handler.PreparedListAssociatedWithTx(tx, id, typeLoader.GetStore().GetEntityType(), query, result.Collect)
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (handler *baseHandler) createEntity(modelEntity boltEntitySource) (string, error) {
|
|
var id string
|
|
err := handler.GetDb().Update(func(tx *bbolt.Tx) error {
|
|
var err error
|
|
id, err = handler.createEntityInTx(boltz.NewMutateContext(tx), modelEntity)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
func (handler *baseHandler) validateNameOnCreate(ctx boltz.MutateContext, boltEntity boltz.Entity) error {
|
|
// validate name for named entities
|
|
if namedEntity, ok := boltEntity.(boltz.NamedExtEntity); ok {
|
|
if namedEntity.GetName() == "" {
|
|
return errorz.NewFieldError("name is required", "name", namedEntity.GetName())
|
|
}
|
|
if nameIndexStore, ok := handler.GetStore().(persistence.NameIndexedStore); ok {
|
|
if nameIndexStore.GetNameIndex().Read(ctx.Tx(), []byte(namedEntity.GetName())) != nil {
|
|
return errorz.NewFieldError("name is must be unique", "name", namedEntity.GetName())
|
|
}
|
|
} else {
|
|
pfxlog.Logger().Errorf("entity of type %v is named, but store doesn't have name index", reflect.TypeOf(boltEntity))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (handler *baseHandler) createEntityInTx(ctx boltz.MutateContext, modelEntity boltEntitySource) (string, error) {
|
|
if modelEntity == nil {
|
|
return "", errors.Errorf("can't create %v with nil value", handler.Store.GetEntityType())
|
|
}
|
|
if modelEntity.GetId() == "" {
|
|
modelEntity.SetId(eid.New())
|
|
}
|
|
|
|
boltEntity, err := modelEntity.toBoltEntityForCreate(ctx.Tx(), handler.impl)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if err = handler.validateNameOnCreate(ctx, boltEntity); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if err := handler.GetStore().Create(ctx, boltEntity); err != nil {
|
|
pfxlog.Logger().WithError(err).Errorf("could not create %v in bolt storage", handler.GetStore().GetSingularEntityType())
|
|
return "", err
|
|
}
|
|
|
|
return modelEntity.GetId(), nil
|
|
}
|
|
|
|
func (handler *baseHandler) updateEntity(modelEntity boltEntitySource, checker boltz.FieldChecker) error {
|
|
return handler.updateGeneral(modelEntity, checker, false)
|
|
}
|
|
|
|
func (handler *baseHandler) patchEntity(modelEntity boltEntitySource, checker boltz.FieldChecker) error {
|
|
return handler.updateGeneral(modelEntity, checker, true)
|
|
}
|
|
|
|
func (handler *baseHandler) patchEntityBatch(modelEntity boltEntitySource, checker boltz.FieldChecker) error {
|
|
return handler.updateGeneralBatch(modelEntity, checker, true)
|
|
}
|
|
|
|
func (handler *baseHandler) updateGeneralBatch(modelEntity boltEntitySource, checker boltz.FieldChecker, patch bool) error {
|
|
return handler.GetDb().Batch(func(tx *bbolt.Tx) error {
|
|
ctx := boltz.NewMutateContext(tx)
|
|
existing := handler.GetStore().NewStoreEntity()
|
|
found, err := handler.GetStore().BaseLoadOneById(tx, modelEntity.GetId(), existing)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !found {
|
|
return boltz.NewNotFoundError(handler.GetStore().GetSingularEntityType(), "id", modelEntity.GetId())
|
|
}
|
|
var boltEntity boltz.Entity
|
|
if patch {
|
|
boltEntity, err = modelEntity.toBoltEntityForPatch(tx, handler.impl, checker)
|
|
} else {
|
|
boltEntity, err = modelEntity.toBoltEntityForUpdate(tx, handler.impl)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// validate name for named entities
|
|
if namedEntity, ok := boltEntity.(boltz.NamedExtEntity); ok {
|
|
existingNamed := existing.(boltz.NamedExtEntity)
|
|
if (checker == nil || checker.IsUpdated("name")) && namedEntity.GetName() != existingNamed.GetName() {
|
|
if namedEntity.GetName() == "" {
|
|
return errorz.NewFieldError("name is required", "name", namedEntity.GetName())
|
|
}
|
|
if nameIndexStore, ok := handler.GetStore().(persistence.NameIndexedStore); ok {
|
|
if nameIndexStore.GetNameIndex().Read(ctx.Tx(), []byte(namedEntity.GetName())) != nil {
|
|
return errorz.NewFieldError("name is must be unique", "name", namedEntity.GetName())
|
|
}
|
|
} else {
|
|
pfxlog.Logger().Errorf("batch: entity of type %v is named, but store doesn't have name index", reflect.TypeOf(boltEntity))
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := handler.GetStore().Update(ctx, boltEntity, checker); err != nil {
|
|
pfxlog.Logger().Errorf("batch: entity of type %v is named, but store doesn't have name index", reflect.TypeOf(boltEntity))
|
|
if patch {
|
|
pfxlog.Logger().WithError(err).Errorf("batch: could not patch %v entity", handler.GetStore().GetEntityType())
|
|
} else {
|
|
pfxlog.Logger().WithError(err).Errorf("batch: could not update %v entity", handler.GetStore().GetEntityType())
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (handler *baseHandler) updateGeneral(modelEntity boltEntitySource, checker boltz.FieldChecker, patch bool) error {
|
|
return handler.GetDb().Update(func(tx *bbolt.Tx) error {
|
|
ctx := boltz.NewMutateContext(tx)
|
|
existing := handler.GetStore().NewStoreEntity()
|
|
found, err := handler.GetStore().BaseLoadOneById(tx, modelEntity.GetId(), existing)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !found {
|
|
return boltz.NewNotFoundError(handler.GetStore().GetSingularEntityType(), "id", modelEntity.GetId())
|
|
}
|
|
var boltEntity boltz.Entity
|
|
if patch {
|
|
boltEntity, err = modelEntity.toBoltEntityForPatch(tx, handler.impl, checker)
|
|
} else {
|
|
boltEntity, err = modelEntity.toBoltEntityForUpdate(tx, handler.impl)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// validate name for named entities
|
|
if namedEntity, ok := boltEntity.(boltz.NamedExtEntity); ok {
|
|
existingNamed := existing.(boltz.NamedExtEntity)
|
|
if (checker == nil || checker.IsUpdated("name")) && namedEntity.GetName() != existingNamed.GetName() {
|
|
if namedEntity.GetName() == "" {
|
|
return errorz.NewFieldError("name is required", "name", namedEntity.GetName())
|
|
}
|
|
if nameIndexStore, ok := handler.GetStore().(persistence.NameIndexedStore); ok {
|
|
if nameIndexStore.GetNameIndex().Read(ctx.Tx(), []byte(namedEntity.GetName())) != nil {
|
|
return errorz.NewFieldError("name is must be unique", "name", namedEntity.GetName())
|
|
}
|
|
} else {
|
|
pfxlog.Logger().Errorf("entity of type %v is named, but store doesn't have name index", reflect.TypeOf(boltEntity))
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := handler.GetStore().Update(ctx, boltEntity, checker); err != nil {
|
|
if patch {
|
|
pfxlog.Logger().WithError(err).Errorf("could not patch %v entity", handler.GetStore().GetEntityType())
|
|
} else {
|
|
pfxlog.Logger().WithError(err).Errorf("could not update %v entity", handler.GetStore().GetEntityType())
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (handler *baseHandler) readEntity(id string, modelEntity boltEntitySink) error {
|
|
return handler.GetDb().View(func(tx *bbolt.Tx) error {
|
|
return handler.readEntityInTx(tx, id, modelEntity)
|
|
})
|
|
}
|
|
|
|
func (handler *baseHandler) readEntityInTx(tx *bbolt.Tx, id string, modelEntity boltEntitySink) error {
|
|
boltEntity := handler.GetStore().NewStoreEntity()
|
|
found, err := handler.GetStore().BaseLoadOneById(tx, id, boltEntity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !found {
|
|
return boltz.NewNotFoundError(handler.GetStore().GetSingularEntityType(), "id", id)
|
|
}
|
|
|
|
return modelEntity.fillFrom(handler.impl, tx, boltEntity)
|
|
}
|
|
|
|
func (handler *baseHandler) readEntityWithIndex(name string, key []byte, index boltz.ReadIndex, modelEntity boltEntitySink) error {
|
|
return handler.GetDb().View(func(tx *bbolt.Tx) error {
|
|
return handler.readEntityInTxWithIndex(name, tx, key, index, modelEntity)
|
|
})
|
|
}
|
|
|
|
func (handler *baseHandler) readEntityInTxWithIndex(name string, tx *bbolt.Tx, key []byte, index boltz.ReadIndex, modelEntity boltEntitySink) error {
|
|
id := index.Read(tx, key)
|
|
if id == nil {
|
|
return boltz.NewNotFoundError(handler.GetStore().GetSingularEntityType(), name, string(key))
|
|
}
|
|
return handler.readEntityInTx(tx, string(id), modelEntity)
|
|
}
|
|
|
|
func (handler *baseHandler) readEntityByQuery(query string) (models.Entity, error) {
|
|
result, err := handler.BaseList(query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(result.GetEntities()) > 0 {
|
|
return result.GetEntities()[0], nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (handler *baseHandler) deleteEntity(id string) error {
|
|
return handler.GetDb().Update(func(tx *bbolt.Tx) error {
|
|
return handler.GetStore().DeleteById(boltz.NewMutateContext(tx), id)
|
|
})
|
|
}
|
|
|
|
func (handler *baseHandler) deleteEntityBatch(ids []string) error {
|
|
return handler.GetDb().Update(func(tx *bbolt.Tx) error {
|
|
for _, id := range ids {
|
|
if err := handler.GetStore().DeleteById(boltz.NewMutateContext(tx), id); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (handler *baseHandler) list(queryString string, resultHandler models.ListResultHandler) error {
|
|
return handler.GetDb().View(func(tx *bbolt.Tx) error {
|
|
return handler.ListWithTx(tx, queryString, resultHandler)
|
|
})
|
|
}
|
|
|
|
func (handler *baseHandler) queryRoleAttributes(index boltz.SetReadIndex, queryString string) ([]string, *models.QueryMetaData, error) {
|
|
query, err := ast.Parse(handler.env.GetStores().Index, queryString)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
var results []string
|
|
var count int64
|
|
err = handler.GetDb().View(func(tx *bbolt.Tx) error {
|
|
results, count, err = handler.env.GetStores().Index.QueryWithCursorC(tx, index.OpenKeyCursor, query)
|
|
return err
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
qmd := &models.QueryMetaData{
|
|
Count: count,
|
|
Limit: *query.GetLimit(),
|
|
Offset: *query.GetSkip(),
|
|
FilterableFields: handler.env.GetStores().Index.GetPublicSymbols(),
|
|
}
|
|
|
|
return results, qmd, nil
|
|
}
|
|
|
|
func (handler *baseHandler) iterateRelatedEntities(id, field string, f func(tx *bbolt.Tx, relatedId string) error) error {
|
|
return handler.GetDb().View(func(tx *bbolt.Tx) error {
|
|
return handler.iterateRelatedEntitiesInTx(tx, id, field, f)
|
|
})
|
|
}
|
|
|
|
func (handler *baseHandler) iterateRelatedEntitiesInTx(tx *bbolt.Tx, id, field string, f func(tx *bbolt.Tx, relatedId string) error) error {
|
|
cursor := handler.Store.GetRelatedEntitiesCursor(tx, id, field, true)
|
|
for cursor.IsValid() {
|
|
key := cursor.Current()
|
|
if err := f(tx, string(key)); err != nil {
|
|
return err
|
|
}
|
|
cursor.Next()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type AndFieldChecker struct {
|
|
first boltz.FieldChecker
|
|
second boltz.FieldChecker
|
|
}
|
|
|
|
func (checker *AndFieldChecker) IsUpdated(field string) bool {
|
|
return checker.first.IsUpdated(field) && checker.second.IsUpdated(field)
|
|
}
|
|
|
|
type OrFieldChecker struct {
|
|
first boltz.FieldChecker
|
|
second boltz.FieldChecker
|
|
}
|
|
|
|
func NewOrFieldChecker(checker boltz.FieldChecker, fields ...string) *OrFieldChecker {
|
|
return &OrFieldChecker{first: NewFieldChecker(fields...), second: checker}
|
|
}
|
|
|
|
func (checker *OrFieldChecker) IsUpdated(field string) bool {
|
|
return checker.first.IsUpdated(field) || checker.second.IsUpdated(field)
|
|
}
|
|
|
|
func NewFieldChecker(fields ...string) boltz.FieldChecker {
|
|
result := boltz.MapFieldChecker{}
|
|
for _, field := range fields {
|
|
result[field] = struct{}{}
|
|
}
|
|
return result
|
|
}
|