Simple DB using yaml. A project for managing the content of yaml files.
- DB Yaml
- Features
- Usage
The module can do
- Create/Load yaml files
- Update content
- Get values from keys
- Query for keys
- Delete keys
- Merge content
Simple examples for working with yaml files as 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.
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
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())
}
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
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"]
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)
We can also query paths that have arrays.
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)
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)
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())
}
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
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
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)
}
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
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)
}
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)
}
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
To switch to a doc by using the doc's name, issue
err = state.SwitchDoc("PodDisruptionBudget/caller-svc")
if err != nil {
logger.Fatal(err)
}
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)
}
Wrappers for working with all documents
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 works as GlobalUpsert but it skips documents that miss a path rather than creating the path on those docs.
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
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
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
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 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
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
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"])
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"])
Again here we can do it two ways as with the map example
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)
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)