Skip to content

ulfox/dby

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

74 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DB Yaml

Simple DB using yaml. A project for managing the content of yaml files.

Table of Contents

Features

The module can do

  • Create/Load yaml files
  • Update content
  • Get values from keys
  • Query for keys
  • Delete keys
  • Merge content

Usage

Simple examples for working with yaml files as db

Initiate a new stateful DB

Create a new local DB

package main

import (
	"github.com/sirupsen/logrus"
	"github.com/ulfox/dby/db"
)

func main() {
	logger := logrus.New()

	state, err := db.NewStorageFactory("local/db.yaml")
	if err != nil {
		logger.Fatalf(err.Error())
	}
}

The code above will create a new yaml file under local directory.

Initiate a new stateless DB

package main

import (
	"github.com/sirupsen/logrus"
	"github.com/ulfox/dby/db"
)

func main() {
	logger := logrus.New()

	state, err := db.NewStorageFactory()
	if err != nil {
		logger.Fatalf(err.Error())
	}
}

Initiating a db without arguments will not create/write/read from a file. All operations will be done in memory and unless the caller saves the data externally, all data will be lose on termination

Write to DB

Insert a map to the local yaml file.

err = state.Upsert(
	"some.path",
	map[string]string{
		"key-1": "value-1",
		"key-2": "value-2",
	},
)

if err != nil {
	logger.Fatalf(err.Error())
}

Query DB

Get First Key

Get the value of the first key in the hierarchy (if any)

val, err := state.GetFirst("key-1")
if err != nil {
	logger.Fatalf(err.Error())
}
logger.Info(val)

For example if we have the following structure

key-1:
    key-2:
        key-3: "1"
    key-3: "2"

And we query for key-3, then we will get back "2" and not "1" since key-3 appears first on a higher layer with a value of 2

Search for keys

Get all they keys (if any). This returns the full path for the key, not the key values. To get the values check the next section GetPath

keys, err := state.FindKeys("key-1")
if err != nil {
	logger.Fatalf(err.Error())
}
logger.Info(keys)

From the previous example, this query would have returned

["key-1.key-2.key-3", "key-1.key-3"]

Query Path

Get the value from a given path (if any)

For example if we have in yaml file the following key-path

key-1:
    key-2:
        key-3: someValue

Then to get someValue, issue

keyPath, err := state.GetPath("key-1.key-2.key-3")
if err != nil {
	logger.Fatalf(err.Error())
}
logger.Info(keyPath)

Query Path with Arrays

We can also query paths that have arrays.

Without trailing array
key-1:
    key-2:
        - key-3: 
            key-4: value-1

To get the value of key-4, issue

keyPath, err := state.GetPath("key-1.key-2.[0].key-3.key-4")
if err != nil {
	logger.Fatalf(err.Error())
}
logger.Info(keyPath)
With trailing array
key-1:
    key-2:
        - value-1
        - value-2
        - value-3

To get the first index of key-2, issue

keyPath, err := state.GetPath("key-1.key-2.[0]")
if err != nil {
	logger.Fatalf(err.Error())
}
logger.Info(keyPath)

Delete Key By Path

To delete a single key for a given path, e.g. key-2 from the example above, issue

err = state.Delete("key-1.key-2")
if err != nil {
	logger.Fatalf(err.Error())
}

Document Management

DBy creates by default an array of documents called library. That is in fact an array of interfaces

When initiating DBy, document 0 (index 0) is creatd by default and any action is done to that document, unless we switch to a new one

Add a new doc

To add a new doc, issue

err = state.AddDoc()
if err != nil {
  logger.Fatal(err)
}

Note: Adding a new doc also switches the pointer to that doc. Any action will write/read from the new doc by default

Switch Doc

To switch a different document, we can use Switch method that takes as an argument an index

For example to switch to doc 1 (second doc), issue

err = state.Switch(1)
if err != nil {
  logger.Fatal(err)
}

Document names

When we work with more than 1 document, we may want to set names in order to easily switch between docs

We have 2 ways to name our documents

  • Add a name to each document manually
  • Add a name providing a path that exists in all documents
Name documents manually

To name a document manually, we can use the SetName method which takes 2 arguments

  • name
  • doc index

For example to name document with index 0, as myDoc

err := state.SetName("myDoc", 0)
if err != nil {
  logger.Fatal(err)
}
Name all documents automatically

To name all documents automatically we need to ensure that the same path exists in all documents.

The method for updating all documents is called SetNames and takes 2 arguments

  • Prefix: A path in the documents that will be used for the first name
  • Suffix: A path in the documents that will be used for the last name

Note: Docs that do not have the paths that are queried will not get a name

This method best works with Kubernetes manifests, where all docs have a common set of fields.

For example

apiVersion: someApi-0
kind: someKind-0
metadata:
...
  name: someName-0
...
---
apiVersion: someApi-1
kind: someKind-1
metadata:
...
  name: someName-1
...
---

From above we could give a name for all our documents if we use kind + metadata.name for the name.

err := state.SetNames("kind", "metadata.name")
if err != nil {
  logger.Fatal(err)
}
List all doc names

To get the name of all named docs, issue

for i, j := range state.ListDocs() {
  fmt.Println(i, j)
}

Example output based on the previous SetNames example

0 service/listener-svc
1 poddisruptionbudget/listener-svc
2 horizontalpodautoscaler/caller-svc
3 deployment/caller-svc
4 service/caller-svc
5 poddisruptionbudget/caller-svc
6 horizontalpodautoscaler/listener-svc
7 deployment/listener-svc
Switch between docs by name

To switch to a doc by using the doc's name, issue

err = state.SwitchDoc("PodDisruptionBudget/caller-svc")
if err != nil {
  logger.Fatal(err)
}

Import Docs

We can import a set of docs with ImportDocs method. For example if we have the following yaml

apiVersion: someApi-0
kind: someKind-0
metadata:
...
  name: someName-0
...
---
apiVersion: someApi-1
kind: someKind-1
metadata:
...
  name: someName-1
...
---

We can import it by giving the path of the file

err = state.ImportDocs("file-name.yaml")
if err != nil {
  logger.Fatal(err)
}

Global Commands

Wrappers for working with all documents

Global Upsert

We can use upsert to update or create keys on all documents

err = state.UpsertGlobal(
  "some.path",
  "v0.3.0",
)
if err != nil {
  logger.Fatal(err)
}
Global Update

Global update works as GlobalUpsert but it skips documents that miss a path rather than creating the path on those docs.

Global GetFirst

To get the value of the first key in the hierarchy for each document, issue

valueOfDocs, err := state.GetFirstGlobal("keyName")
if err != nil {
	logger.Fatalf(err.Error())
}
logger.Info(valueOfDocs)

This returns a map[int]interface{} object. The key is the index of each document and it's value is the value of the first key in the hierarchy in that document

Global FindKeys

To get all the paths for a given from all documents, issue

mapOfPaths, err := state.FindKeysGlobal("keyName")
if err != nil {
	logger.Fatalf(err.Error())
}
logger.Info(mapOfPaths)

This returns a map[int][]string object. The key is the index of each document and it's value is a list of paths that have the queried key

Global GetPath

To get a path that exists in all documents, issue

valueOfDocs, err := state.GetPathGlobal("key-1.key-2.key-3")
if err != nil {
	logger.Fatalf(err.Error())
}
logger.Info(valueOfDocs)

This returns a map[int]interface{} object. The key is the index of each document and it's value is the value of the specific key in that document

Global Delete

To delete a path from all documents, issue

err := state.DeleteGlobal("key-1.key-2.key-3")
if err != nil {
	logger.Fatalf(err.Error())
}

The above will delete all the paths that match the queried path from each doc

Convert Utils

Convert simply automate the need to explicitly do assertion each time we need to access an interface object.

Let us assume we have the following YAML structure

to:
  array-1:
    key-1:
    - key-2: 2
    - key-3: 3
    - key-4: 4
  array-2:
  - 1
  - 2
  - 3
  - 4
  - 5
  array-3:
  - key-1: 1
  - key-2: 2

Get map of strings from interface

We can do this in two ways, get object by giving a path and assert the interface to map[string]string, or work manually our way to the object

Get map directly from a GetPath object

To get map key-2: 2, first get object via GetPath

obj, err := state.GetPath("to.array-1.key-1.[0]")
if err != nil {
	logger.Fatalf(err.Error())
}
logger.Info(val)

Next, assert obj as map[string]string

assertData := db.NewConvertFactory()

assertData.Input(val)
if assertData.GetError() != nil {
	logger.Fatal(assertData.GetError())
}
vMap, err := assertData.GetMap()
if err != nil {
	logger.Fatal(err)
}
logger.Info(vMap["key-2"])
Get map manually

We can get the map manually by using only Convert operations

assertData := db.NewConvertFactory()

assertData.Input(state.Data).
	Key("to").
	Key("array-1").
	Key("key-1").Index(0)
if assertData.GetError() != nil {
	logger.Fatal(assertData.GetError())
}
vMap, err := assertData.GetMap()
if err != nil {
	logger.Fatal(err)
}
logger.Info(vMap["key-2"])

Get array of string from interface

Again here we can do it two ways as with the map example

Get array directly from a GetPath object

To get array-2 as []string, first get object via GetPath

obj, err = state.GetPath("to.array-2")
if err != nil {
	logger.Fatalf(err.Error())
}
logger.Info(obj)

Next, assert obj as []string

assertData := db.NewConvertFactory()

assertData.Input(obj)
if assertData.GetError() != nil {
	logger.Fatal(assertData.GetError())
}
vArray, err := assertData.GetArray()
if err != nil {
	logger.Fatal(err)
}
logger.Info(vArray)
Get array manually

We can get the array manually by using only Convert operations

assertData.Input(state.Data).
	Key("to").
	Key("array-2")
if assertData.GetError() != nil {
	logger.Fatal(assertData.GetError())
}
vArray, err := assertData.GetArray()
if err != nil {
	logger.Fatal(err)
}
logger.Info(vArray)