influxdb/bolt/variable.go

397 lines
8.8 KiB
Go

package bolt
import (
"bytes"
"context"
"encoding/json"
bolt "github.com/coreos/bbolt"
platform "github.com/influxdata/influxdb"
)
var (
variableBucket = []byte("variablesv1")
variableOrgsIndex = []byte("variableorgsv1")
)
func (c *Client) initializeVariables(ctx context.Context, tx *bolt.Tx) error {
if _, err := tx.CreateBucketIfNotExists([]byte(variableBucket)); err != nil {
return err
}
if _, err := tx.CreateBucketIfNotExists(variableOrgsIndex); err != nil {
return err
}
return nil
}
func decodeVariableOrgsIndexKey(indexKey []byte) (orgID platform.ID, variableID platform.ID, err error) {
if len(indexKey) != 2*platform.IDLength {
return 0, 0, &platform.Error{
Code: platform.EInvalid,
Msg: "malformed variable orgs index key (please report this error)",
}
}
if err := (&orgID).Decode(indexKey[:platform.IDLength]); err != nil {
return 0, 0, &platform.Error{
Code: platform.EInvalid,
Msg: "bad org id",
Err: platform.ErrInvalidID,
}
}
if err := (&variableID).Decode(indexKey[platform.IDLength:]); err != nil {
return 0, 0, &platform.Error{
Code: platform.EInvalid,
Msg: "bad variable id",
Err: platform.ErrInvalidID,
}
}
return orgID, variableID, nil
}
func (c *Client) findOrganizationVariables(ctx context.Context, tx *bolt.Tx, orgID platform.ID) ([]*platform.Variable, error) {
// TODO(leodido): support find options
cur := tx.Bucket(variableOrgsIndex).Cursor()
prefix, err := orgID.Encode()
if err != nil {
return nil, err
}
variables := []*platform.Variable{}
for k, _ := cur.Seek(prefix); bytes.HasPrefix(k, prefix); k, _ = cur.Next() {
_, id, err := decodeVariableOrgsIndexKey(k)
if err != nil {
return nil, err
}
m, err := c.findVariableByID(ctx, tx, id)
if err != nil {
return nil, err
}
variables = append(variables, m)
}
return variables, nil
}
func (c *Client) findVariables(ctx context.Context, tx *bolt.Tx, filter platform.VariableFilter) ([]*platform.Variable, error) {
if filter.OrganizationID != nil {
return c.findOrganizationVariables(ctx, tx, *filter.OrganizationID)
}
if filter.Organization != nil {
o, err := c.findOrganizationByName(ctx, tx, *filter.Organization)
if err != nil {
return nil, err
}
return c.findOrganizationVariables(ctx, tx, o.ID)
}
variables := []*platform.Variable{}
filterFn := filterVariablesFn(filter)
err := c.forEachVariable(ctx, tx, func(m *platform.Variable) bool {
if filterFn(m) {
variables = append(variables, m)
}
return true
})
if err != nil {
return nil, err
}
return variables, nil
}
func filterVariablesFn(filter platform.VariableFilter) func(m *platform.Variable) bool {
if filter.ID != nil {
return func(m *platform.Variable) bool {
return m.ID == *filter.ID
}
}
if filter.OrganizationID != nil {
return func(m *platform.Variable) bool {
return m.OrganizationID == *filter.OrganizationID
}
}
return func(m *platform.Variable) bool { return true }
}
// forEachVariable will iterate through all variables while fn returns true.
func (c *Client) forEachVariable(ctx context.Context, tx *bolt.Tx, fn func(*platform.Variable) bool) error {
cur := tx.Bucket(variableBucket).Cursor()
for k, v := cur.First(); k != nil; k, v = cur.Next() {
m := &platform.Variable{}
if err := json.Unmarshal(v, m); err != nil {
return err
}
if !fn(m) {
break
}
}
return nil
}
// FindVariables returns all variables in the store
func (c *Client) FindVariables(ctx context.Context, filter platform.VariableFilter, opt ...platform.FindOptions) ([]*platform.Variable, error) {
// todo(leodido) > handle find options
op := getOp(platform.OpFindVariables)
res := []*platform.Variable{}
err := c.db.View(func(tx *bolt.Tx) error {
variables, err := c.findVariables(ctx, tx, filter)
if err != nil && platform.ErrorCode(err) != platform.ENotFound {
return err
}
res = variables
return nil
})
if err != nil {
return nil, &platform.Error{
Op: op,
Err: err,
}
}
return res, nil
}
// FindVariableByID finds a single variable in the store by its ID
func (c *Client) FindVariableByID(ctx context.Context, id platform.ID) (*platform.Variable, error) {
op := getOp(platform.OpFindVariableByID)
var variable *platform.Variable
err := c.db.View(func(tx *bolt.Tx) error {
m, pe := c.findVariableByID(ctx, tx, id)
if pe != nil {
return &platform.Error{
Op: op,
Err: pe,
}
}
variable = m
return nil
})
if err != nil {
return nil, err
}
return variable, nil
}
func (c *Client) findVariableByID(ctx context.Context, tx *bolt.Tx, id platform.ID) (*platform.Variable, error) {
encID, err := id.Encode()
if err != nil {
return nil, &platform.Error{
Code: platform.EInvalid,
Err: err,
}
}
d := tx.Bucket(variableBucket).Get(encID)
if d == nil {
return nil, &platform.Error{
Code: platform.ENotFound,
Msg: platform.ErrVariableNotFound,
}
}
variable := &platform.Variable{}
err = json.Unmarshal(d, &variable)
if err != nil {
return nil, &platform.Error{
Err: err,
}
}
return variable, nil
}
// CreateVariable creates a new variable and assigns it an ID
func (c *Client) CreateVariable(ctx context.Context, variable *platform.Variable) error {
op := getOp(platform.OpCreateVariable)
return c.db.Update(func(tx *bolt.Tx) error {
variable.ID = c.IDGenerator.ID()
if err := c.putVariableOrgsIndex(ctx, tx, variable); err != nil {
return err
}
if pe := c.putVariable(ctx, tx, variable); pe != nil {
return &platform.Error{
Op: op,
Err: pe,
}
}
return nil
})
}
// ReplaceVariable puts a variable in the store
func (c *Client) ReplaceVariable(ctx context.Context, variable *platform.Variable) error {
op := getOp(platform.OpReplaceVariable)
return c.db.Update(func(tx *bolt.Tx) error {
if err := c.putVariableOrgsIndex(ctx, tx, variable); err != nil {
return &platform.Error{
Op: op,
Err: err,
}
}
return c.putVariable(ctx, tx, variable)
})
}
func encodeVariableOrgsIndex(variable *platform.Variable) ([]byte, error) {
oID, err := variable.OrganizationID.Encode()
if err != nil {
return nil, &platform.Error{
Err: err,
Msg: "bad organization id",
}
}
mID, err := variable.ID.Encode()
if err != nil {
return nil, &platform.Error{
Err: err,
Msg: "bad variable id",
}
}
key := make([]byte, 0, platform.IDLength*2)
key = append(key, oID...)
key = append(key, mID...)
return key, nil
}
func (c *Client) putVariableOrgsIndex(ctx context.Context, tx *bolt.Tx, variable *platform.Variable) error {
key, err := encodeVariableOrgsIndex(variable)
if err != nil {
return err
}
if err := tx.Bucket(variableOrgsIndex).Put(key, nil); err != nil {
return &platform.Error{
Err: err,
}
}
return nil
}
func (c *Client) removeVariableOrgsIndex(ctx context.Context, tx *bolt.Tx, variable *platform.Variable) error {
key, err := encodeVariableOrgsIndex(variable)
if err != nil {
return err
}
if err := tx.Bucket(variableOrgsIndex).Delete(key); err != nil {
return err
}
return nil
}
func (c *Client) putVariable(ctx context.Context, tx *bolt.Tx, variable *platform.Variable) error {
m, err := json.Marshal(variable)
if err != nil {
return &platform.Error{
Err: err,
}
}
encID, err := variable.ID.Encode()
if err != nil {
return &platform.Error{
Code: platform.EInvalid,
Err: err,
}
}
if err := tx.Bucket(variableBucket).Put(encID, m); err != nil {
return &platform.Error{
Err: err,
}
}
return nil
}
// UpdateVariable updates a single variable in the store with a changeset
func (c *Client) UpdateVariable(ctx context.Context, id platform.ID, update *platform.VariableUpdate) (*platform.Variable, error) {
op := getOp(platform.OpUpdateVariable)
var variable *platform.Variable
err := c.db.Update(func(tx *bolt.Tx) error {
m, pe := c.findVariableByID(ctx, tx, id)
if pe != nil {
return &platform.Error{
Op: op,
Err: pe,
}
}
if err := update.Apply(m); err != nil {
return &platform.Error{
Op: op,
Err: err,
}
}
variable = m
if pe = c.putVariable(ctx, tx, variable); pe != nil {
return &platform.Error{
Op: op,
Err: pe,
}
}
return nil
})
return variable, err
}
// DeleteVariable removes a single variable from the store by its ID
func (c *Client) DeleteVariable(ctx context.Context, id platform.ID) error {
op := getOp(platform.OpDeleteVariable)
return c.db.Update(func(tx *bolt.Tx) error {
m, pe := c.findVariableByID(ctx, tx, id)
if pe != nil {
return &platform.Error{
Op: op,
Err: pe,
}
}
encID, err := id.Encode()
if err != nil {
return &platform.Error{
Op: op,
Err: err,
}
}
if err := c.removeVariableOrgsIndex(ctx, tx, m); err != nil {
return &platform.Error{
Op: op,
Err: err,
}
}
if err := tx.Bucket(variableBucket).Delete(encID); err != nil {
return &platform.Error{
Op: op,
Err: err,
}
}
return nil
})
}