2020-12-01 20:06:49 +01:00
|
|
|
package bolt
|
|
|
|
|
2021-04-09 23:02:19 +02:00
|
|
|
import (
|
2021-05-08 19:25:00 +02:00
|
|
|
"encoding/json"
|
2021-05-07 12:08:34 +02:00
|
|
|
"fmt"
|
2021-05-08 19:25:00 +02:00
|
|
|
"github.com/ansible-semaphore/semaphore/db"
|
2021-04-09 23:02:19 +02:00
|
|
|
"github.com/ansible-semaphore/semaphore/util"
|
2021-05-07 12:08:34 +02:00
|
|
|
"go.etcd.io/bbolt"
|
2021-05-08 19:25:00 +02:00
|
|
|
"reflect"
|
|
|
|
"sort"
|
|
|
|
"strconv"
|
2021-04-09 23:02:19 +02:00
|
|
|
)
|
2020-12-01 20:06:49 +01:00
|
|
|
|
2021-05-08 22:25:31 +02:00
|
|
|
|
|
|
|
type enumerable interface {
|
|
|
|
First() (key []byte, value []byte)
|
|
|
|
Next() (key []byte, value []byte)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-04-09 23:02:19 +02:00
|
|
|
type BoltDb struct {
|
2021-05-07 12:08:34 +02:00
|
|
|
db *bbolt.DB
|
2020-12-01 20:06:49 +01:00
|
|
|
}
|
|
|
|
|
2021-05-08 22:25:31 +02:00
|
|
|
func makeBucketId(props db.ObjectProperties, ids ...int) []byte {
|
2021-05-07 12:08:34 +02:00
|
|
|
n := len(ids)
|
2020-12-01 20:06:49 +01:00
|
|
|
|
2021-05-08 22:25:31 +02:00
|
|
|
id := props.TableName
|
2021-05-07 12:08:34 +02:00
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
id += fmt.Sprintf("_%010d", ids[i])
|
|
|
|
}
|
|
|
|
|
2021-05-08 19:25:00 +02:00
|
|
|
return []byte(id)
|
2021-05-07 12:08:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *BoltDb) Migrate() error {
|
|
|
|
return nil
|
2020-12-01 20:06:49 +01:00
|
|
|
}
|
|
|
|
|
2021-04-09 23:02:19 +02:00
|
|
|
func (d *BoltDb) Connect() error {
|
|
|
|
config, err := util.Config.GetDBConfig()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-05-08 19:25:00 +02:00
|
|
|
d.db, err = bbolt.Open(config.Hostname, 0666, nil)
|
2021-04-09 23:02:19 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2020-12-01 20:06:49 +01:00
|
|
|
|
2021-04-09 23:02:19 +02:00
|
|
|
func (d *BoltDb) Close() error {
|
|
|
|
return d.db.Close()
|
|
|
|
}
|
2021-05-08 19:25:00 +02:00
|
|
|
|
|
|
|
func (d *BoltDb) getObject(projectID int, props db.ObjectProperties, objectID int, object interface{}) (err error) {
|
|
|
|
err = d.db.View(func(tx *bbolt.Tx) error {
|
|
|
|
b := tx.Bucket(makeBucketId(props, projectID))
|
|
|
|
if b == nil {
|
|
|
|
return db.ErrNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
id := []byte(strconv.Itoa(objectID))
|
|
|
|
str := b.Get(id)
|
|
|
|
if str == nil {
|
|
|
|
return db.ErrNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
return json.Unmarshal(str, &object)
|
|
|
|
})
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func getFieldNameByTag(t reflect.Type, tag string, value string) (string, error) {
|
|
|
|
n := t.NumField()
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
if t.Field(i).Tag.Get(tag) == value {
|
|
|
|
return t.Field(i).Name, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "", fmt.Errorf("")
|
|
|
|
}
|
|
|
|
|
|
|
|
func sortObjects(objects interface{}, sortBy string, sortInverted bool) error {
|
|
|
|
objectsValue := reflect.ValueOf(objects).Elem()
|
|
|
|
objType := objectsValue.Type().Elem()
|
2021-05-08 22:25:31 +02:00
|
|
|
|
2021-05-08 19:25:00 +02:00
|
|
|
fieldName, err := getFieldNameByTag(objType, "db", sortBy)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.SliceStable(objectsValue.Interface(), func (i, j int) bool {
|
2021-05-08 22:25:31 +02:00
|
|
|
valueI := objectsValue.Index(i).FieldByName(fieldName)
|
|
|
|
valueJ := objectsValue.Index(j).FieldByName(fieldName)
|
|
|
|
|
|
|
|
less := false
|
|
|
|
|
|
|
|
switch valueI.Kind() {
|
2021-05-08 19:25:00 +02:00
|
|
|
case reflect.Int:
|
|
|
|
case reflect.Int8:
|
|
|
|
case reflect.Int16:
|
|
|
|
case reflect.Int32:
|
|
|
|
case reflect.Int64:
|
|
|
|
case reflect.Uint:
|
|
|
|
case reflect.Uint8:
|
|
|
|
case reflect.Uint16:
|
|
|
|
case reflect.Uint32:
|
|
|
|
case reflect.Uint64:
|
2021-05-08 22:25:31 +02:00
|
|
|
less = valueI.Int() < valueJ.Int()
|
2021-05-08 19:25:00 +02:00
|
|
|
case reflect.Float32:
|
|
|
|
case reflect.Float64:
|
2021-05-08 22:25:31 +02:00
|
|
|
less = valueI.Float() < valueJ.Float()
|
2021-05-08 19:25:00 +02:00
|
|
|
case reflect.String:
|
2021-05-08 22:25:31 +02:00
|
|
|
less = valueI.String() < valueJ.String()
|
2021-05-08 19:25:00 +02:00
|
|
|
}
|
2021-05-08 22:25:31 +02:00
|
|
|
|
|
|
|
if sortInverted {
|
|
|
|
less = !less
|
|
|
|
}
|
|
|
|
|
|
|
|
return less
|
2021-05-08 19:25:00 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-05-08 22:25:31 +02:00
|
|
|
func unmarshalObjects(rawData enumerable, params db.RetrieveQueryParams, objects interface{}) (err error) {
|
2021-05-08 19:25:00 +02:00
|
|
|
objectsValue := reflect.ValueOf(objects).Elem()
|
|
|
|
objType := objectsValue.Type().Elem()
|
|
|
|
|
2021-05-08 22:25:31 +02:00
|
|
|
i := 0 // current item index
|
|
|
|
n := 0 // number of added items
|
2021-05-08 19:25:00 +02:00
|
|
|
|
2021-05-08 22:25:31 +02:00
|
|
|
for k, v := rawData.First(); k != nil; k, v = rawData.Next() {
|
|
|
|
if i < params.Offset {
|
|
|
|
continue
|
|
|
|
}
|
2021-05-08 19:25:00 +02:00
|
|
|
|
2021-05-08 22:25:31 +02:00
|
|
|
obj := reflect.New(objType).Elem()
|
|
|
|
err = json.Unmarshal(v, &obj)
|
|
|
|
if err == nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-05-08 19:25:00 +02:00
|
|
|
|
2021-05-08 22:25:31 +02:00
|
|
|
objectsValue.Set(reflect.Append(objectsValue, obj))
|
2021-05-08 19:25:00 +02:00
|
|
|
|
2021-05-08 22:25:31 +02:00
|
|
|
n++
|
2021-05-08 19:25:00 +02:00
|
|
|
|
2021-05-08 22:25:31 +02:00
|
|
|
if n > params.Count {
|
|
|
|
break
|
2021-05-08 19:25:00 +02:00
|
|
|
}
|
2021-05-08 22:25:31 +02:00
|
|
|
}
|
2021-05-08 19:25:00 +02:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-08 22:25:31 +02:00
|
|
|
if params.SortBy != "" {
|
|
|
|
err = sortObjects(objects, params.SortBy, params.SortInverted)
|
|
|
|
}
|
2021-05-08 19:25:00 +02:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-08 22:25:31 +02:00
|
|
|
func (d *BoltDb) getObjects(projectID int, props db.ObjectProperties, params db.RetrieveQueryParams, objects interface{}) error {
|
|
|
|
return d.db.View(func(tx *bbolt.Tx) error {
|
|
|
|
|
|
|
|
b := tx.Bucket(makeBucketId(props, projectID))
|
|
|
|
c := b.Cursor()
|
|
|
|
|
|
|
|
return unmarshalObjects(c, params, objects)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-05-08 19:25:00 +02:00
|
|
|
|
|
|
|
func (d *BoltDb) isObjectInUse(projectID int, props db.ObjectProperties, objectID int) (inUse bool, err error) {
|
|
|
|
err = d.db.View(func(tx *bbolt.Tx) error {
|
|
|
|
b := tx.Bucket(makeBucketId(props, projectID))
|
|
|
|
inUse = b != nil && b.Get([]byte(strconv.Itoa(objectID))) != nil
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *BoltDb) deleteObject(projectID int, props db.ObjectProperties, objectID int) error {
|
|
|
|
inUse, err := d.isObjectInUse(projectID, props, objectID)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if inUse {
|
|
|
|
return db.ErrInvalidOperation
|
|
|
|
}
|
|
|
|
|
|
|
|
return d.db.Update(func (tx *bbolt.Tx) error {
|
|
|
|
b := tx.Bucket(makeBucketId(db.InventoryObject, projectID))
|
|
|
|
if b == nil {
|
|
|
|
return db.ErrNotFound
|
|
|
|
}
|
|
|
|
return b.Delete([]byte(strconv.Itoa(objectID)))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *BoltDb) deleteObjectSoft(projectID int, props db.ObjectProperties, objectID int) error {
|
|
|
|
return d.deleteObject(projectID, props, objectID)
|
|
|
|
}
|
2021-05-08 22:25:31 +02:00
|
|
|
|
|
|
|
func (d *BoltDb) updateObject(projectID int, props db.ObjectProperties, object interface{}) error {
|
|
|
|
return d.db.Update(func(tx *bbolt.Tx) error {
|
|
|
|
b := tx.Bucket(makeBucketId(props, projectID))
|
|
|
|
if b == nil {
|
|
|
|
return db.ErrNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
idValue := reflect.ValueOf(object).FieldByName("ID")
|
|
|
|
|
|
|
|
id := []byte(strconv.Itoa(int(idValue.Int())))
|
|
|
|
if b.Get(id) == nil {
|
|
|
|
return db.ErrNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
str, err := json.Marshal(object)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return b.Put(id, str)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *BoltDb) createObject(projectID int, props db.ObjectProperties, object interface{}) (interface{}, error) {
|
|
|
|
err := d.db.Update(func(tx *bbolt.Tx) error {
|
|
|
|
b, err2 := tx.CreateBucketIfNotExists(makeBucketId(props, projectID))
|
|
|
|
if err2 != nil {
|
|
|
|
return err2
|
|
|
|
}
|
|
|
|
|
|
|
|
id, err2 := b.NextSequence()
|
|
|
|
if err2 != nil {
|
|
|
|
return err2
|
|
|
|
}
|
|
|
|
|
|
|
|
idValue := reflect.ValueOf(object).FieldByName("ID")
|
|
|
|
|
|
|
|
idValue.SetInt(int64(id))
|
|
|
|
|
|
|
|
str, err2 := json.Marshal(object)
|
|
|
|
if err2 != nil {
|
|
|
|
return err2
|
|
|
|
}
|
|
|
|
|
|
|
|
return b.Put([]byte(strconv.Itoa(int(id))), str)
|
|
|
|
})
|
|
|
|
|
|
|
|
return object, err
|
|
|
|
}
|