Skip to content

Commit

Permalink
feat: inventory compilation working
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasjarosch committed Mar 11, 2024
1 parent 6fdc4b3 commit 6ee8e05
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 7 deletions.
4 changes: 2 additions & 2 deletions class.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func (c Class) GetAll() data.Value {
func (c *Class) Set(path string, value interface{}) error {
absPath, err := c.container.AbsolutePath(data.NewPath(path))
if err != nil {
return err
return fmt.Errorf("unable to determine absolute path in container: %w", err)
}

err = c.callPreSetHooks(absPath, data.NewValue(value))
Expand All @@ -151,7 +151,7 @@ func (c *Class) Set(path string, value interface{}) error {

err = c.container.SetPath(absPath, value)
if err != nil {
return err
return fmt.Errorf("cannot set path in container %s: %w", absPath, err)
}

err = c.callPostSetHooks(absPath, data.NewValue(value))
Expand Down
87 changes: 82 additions & 5 deletions inventory.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,27 @@ package skipper
import (
"fmt"

"github.com/davecgh/go-spew/spew"

"github.com/lukasjarosch/skipper/data"
)

// Scope defines a 'namespace' within the Inventory which is made up of a collection of classes.
type Scope string

var (
ErrEmptyScope = fmt.Errorf("scope is empty")
ErrNilRegistry = fmt.Errorf("registry is nil")
ErrScopeDoesNotExist = fmt.Errorf("scope does not exist")
ErrScopeAlreadyRegistered = fmt.Errorf("scope already registered")

DataScope Scope = "data"
TargetsScope Scope = "targets"
)

var (
ErrEmptyScope = fmt.Errorf("scope is empty")
ErrNilRegistry = fmt.Errorf("registry is nil")
ErrScopeDoesNotExist = fmt.Errorf("scope does not exist")
ErrScopeAlreadyRegistered = fmt.Errorf("scope already registered")
ErrTargetCannotIntroducePaths = fmt.Errorf("target cannot introduce new paths")
)

// Inventory is the top-level abstraction which represents all data.
// The Inventory is essentially just a wrapper over a map of Registries.
// It introduces the [Scope] which separates collections of Classes (Registries).
Expand Down Expand Up @@ -61,6 +67,75 @@ func (inv *Inventory) RegisterScope(scope Scope, registry *Registry) error {
return nil
}

// Compile compiles the Inventory for a given target class.
//
// The target class is special as it is capable of overwriting values within the whole inventory.
// This is required because most of the time there will be values which are only valid per target.
// But in order to be able to model data wholistically, the user must be able to introduce
// paths which values only become defined once the target (say: environment) is defined.
//
// The target given must be a valid, inventory-absolute, class-path.
// So if the target to use originates from 'targets/develop.yaml', then
// the target path is 'targets.develop'.
//
// Within the target, one can overwrite every existing (!) path within the inventory.
// This works by iterating over every path of the target from which the class-name is stripped.
// If the remaining path is a valid inventory path, then the path is overwritten by the value defined in the target.
// Let's say that there exists a path 'data.common.network.name' with value 'AwesomeNet'.
// If the target class is 'develop' and it has a class-path 'develop.data.common.network.name' with value 'SuperNet',
// then the compiled inventory will have 'SuperNet' as value at 'data.common.network.name'.
//
// If within the target class, a non-existing
func (inv *Inventory) Compile(target data.Path) error {
targetScope, err := inv.PathScope(target)
if err != nil {
return err
}

registry, err := inv.GetScope(targetScope)
if err != nil {
return err
}

// strip the scope prefix from the path, this should yield a valid classIdentifier within the registry
classIdentifier := target.StripPrefix(data.NewPath(string(targetScope)))
targetClass, err := registry.GetClassByIdentifier(classIdentifier.String())
if err != nil {
return err
}

// Overwrite inventory paths with target values if applicable.
err = targetClass.WalkValues(func(p data.Path, v data.Value) error {
pathWithoutClassName := p.StripPrefix(data.NewPath(targetClass.Name))

// If the path does not start with a valid scope we simply ignore it.
if _, err := inv.PathScope(pathWithoutClassName); err != nil {
return nil
}

// If the path does not exist within the inventory, but the target attempted to set it
// by using the scope prefix, abort.
// This is not allowed, only known paths can be overwritten.
// See [Registry.Set] for an explanation as of why.
if _, err := inv.GetPath(pathWithoutClassName); err != nil {
return fmt.Errorf("%w: path does not exist in inventory: %s", ErrTargetCannotIntroducePaths, pathWithoutClassName)
}

// The path exists within the inventory, overwrite it with the value from the target.
spew.Println("OVERWRITTEN", pathWithoutClassName)
err = inv.SetPath(pathWithoutClassName, v)
if err != nil {
return fmt.Errorf("failed to overwrite path %s: %w", pathWithoutClassName, err)
}
return nil
})
if err != nil {
return err
}

return nil
}

// Get attempts to retrieve a [data.Value] from within the inventory.
// The path must begin with a valid [Scope], otherwise the Inventory
// will not be able to determine the correct [Registry] to search in.
Expand Down Expand Up @@ -133,10 +208,12 @@ func (inv *Inventory) Set(path string, value interface{}) error {
return registry.Set(registryPath.String(), value)
}

// SetPath is a wrapper for [inventory.Set]
func (inv *Inventory) SetPath(path data.Path, value interface{}) error {
return inv.Set(path.String(), value)
}

// Scopes returns all [Scope]s registered at the inventory
func (inv *Inventory) Scopes() []Scope {
var scopes []Scope
for scope := range inv.scopes {
Expand Down
28 changes: 28 additions & 0 deletions inventory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package skipper_test
import (
"testing"

"github.com/davecgh/go-spew/spew"
"github.com/stretchr/testify/assert"

. "github.com/lukasjarosch/skipper"
"github.com/lukasjarosch/skipper/codec"
"github.com/lukasjarosch/skipper/data"
)

Expand Down Expand Up @@ -156,3 +158,29 @@ func TestInventoryAbsolutePath(t *testing.T) {
})
}
}

func TestInventory_Compile(t *testing.T) {
commonClass, err := NewClass("testdata/compile/data/common.yaml", codec.NewYamlCodec(), data.NewPath("common"))
assert.NoError(t, err)

dataRegistry := NewRegistry()
err = dataRegistry.RegisterClass(commonClass)
assert.NoError(t, err)

testTarget, err := NewClass("testdata/compile/targets/test.yaml", codec.NewYamlCodec(), data.NewPath("test"))
assert.NoError(t, err)
targetRegistry := NewRegistry()
err = targetRegistry.RegisterClass(testTarget)
assert.NoError(t, err)

inventory, _ := NewInventory()
err = inventory.RegisterScope(DataScope, dataRegistry)
assert.NoError(t, err)
err = inventory.RegisterScope(TargetsScope, targetRegistry)
assert.NoError(t, err)

err = inventory.Compile(data.NewPath("targets.test"))
assert.NoError(t, err)

spew.Dump(inventory.Get("data.common"))
}
2 changes: 2 additions & 0 deletions testdata/compile/data/common.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
common:
name: test
11 changes: 11 additions & 0 deletions testdata/compile/targets/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
test:
ohai: "there"
data:
common.name: "PETER"
common.foo: "asdf"
# targets.test.data.common.name: "lol"
# data:
# common.name: ASDF
# common:
# name: John
# age: 35

0 comments on commit 6ee8e05

Please sign in to comment.