Implement base store encoder states

This commit is contained in:
Tomaž Jerman
2020-11-27 11:19:15 +01:00
parent 3333394165
commit 31c672e2e4
12 changed files with 2106 additions and 0 deletions
+179
View File
@@ -0,0 +1,179 @@
package store
import (
"context"
"time"
"github.com/cortezaproject/corteza-server/pkg/envoy"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
"github.com/cortezaproject/corteza-server/store"
"github.com/cortezaproject/corteza-server/system/types"
)
type (
applicationState struct {
cfg *EncoderConfig
res *resource.Application
app *types.Application
}
)
func NewApplicationState(res *resource.Application, cfg *EncoderConfig) resourceState {
return &applicationState{
cfg: cfg,
res: res,
}
}
func (n *applicationState) Prepare(ctx context.Context, s store.Storer, rs *envoy.ResourceState) (err error) {
// Initial values
if n.res.Res.CreatedAt.IsZero() {
n.res.Res.CreatedAt = time.Now()
}
if n.res.Res.Unify == nil {
n.res.Res.Unify = &types.ApplicationUnify{}
}
// Get the existing app
n.app, err = findApplicationS(ctx, s, makeGenericFilter(n.res.Identifiers()))
if err != nil {
return err
}
if n.app != nil {
n.res.Res.ID = n.app.ID
}
return nil
}
// Encode encodes the given application
func (n *applicationState) Encode(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error) {
res := n.res.Res
exists := n.app != nil && n.app.ID > 0
// Determine the ID
if res.ID <= 0 && exists {
res.ID = n.app.ID
}
if res.ID <= 0 {
res.ID = nextID()
}
// This is not possible, but let's do it anyway
if state.Conflicting {
return nil
}
// Create fresh application
if !exists {
return store.CreateApplication(ctx, s, res)
}
// Update existing application
switch n.cfg.OnExisting {
case Skip:
return nil
case MergeLeft:
res = mergeApplications(n.app, res)
case MergeRight:
res = mergeApplications(res, n.app)
}
err = store.UpdateApplication(ctx, s, res)
if err != nil {
return err
}
n.res.Res = res
return nil
}
// mergeApplications merges b into a, prioritising a
func mergeApplications(a, b *types.Application) *types.Application {
c := *a
if c.Name == "" {
c.Name = b.Name
}
// I'll just compare the entire struct for now
if c.Unify == nil || *c.Unify == (types.ApplicationUnify{}) {
c.Unify = b.Unify
}
return &c
}
// findApplicationRS looks for the app in the resources & the store
//
// Provided resources are prioritized.
func findApplicationRS(ctx context.Context, s store.Storer, rr resource.InterfaceSet, ii resource.Identifiers) (ap *types.Application, err error) {
ap = findApplicationR(rr, ii)
if ap != nil {
return ap, nil
}
return findApplicationS(ctx, s, makeGenericFilter(ii))
}
// findApplicationS looks for the app in the store
func findApplicationS(ctx context.Context, s store.Storer, gf genericFilter) (ap *types.Application, err error) {
if gf.id > 0 {
ap, err = store.LookupApplicationByID(ctx, s, gf.id)
if err != nil && err != store.ErrNotFound {
return nil, err
}
if ap != nil {
return
}
}
q := gf.handle
if q == "" {
q = gf.name
}
if q != "" {
var aa types.ApplicationSet
aa, _, err = store.SearchApplications(ctx, s, types.ApplicationFilter{Name: q})
if err != nil && err != store.ErrNotFound {
return nil, err
}
if len(aa) > 0 {
ap = aa[0]
return
}
}
return nil, nil
}
// findApplicationR looks for the app in the resource set
func findApplicationR(rr resource.InterfaceSet, ii resource.Identifiers) (ap *types.Application) {
var apRes *resource.Application
var ok bool
rr.Walk(func(r resource.Interface) error {
apRes, ok = r.(*resource.Application)
if !ok {
return nil
}
if !apRes.Identifiers().HasAny(r.Identifiers()) {
apRes = nil
}
return nil
})
// Found it
if apRes != nil {
return apRes.Res
}
return nil
}
+225
View File
@@ -0,0 +1,225 @@
package store
import (
"context"
"errors"
"time"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/envoy"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
"github.com/cortezaproject/corteza-server/store"
)
type (
composeChartState struct {
cfg *EncoderConfig
res *resource.ComposeChart
chr *types.Chart
relNS *types.Namespace
relMods types.ModuleSet
}
)
func NewComposeChartState(res *resource.ComposeChart, cfg *EncoderConfig) resourceState {
return &composeChartState{
cfg: cfg,
res: res,
}
}
func (n *composeChartState) Prepare(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error) {
// Initial values
if n.res.Res.CreatedAt.IsZero() {
n.res.Res.CreatedAt = time.Now()
}
// Get relate namespace
n.relNS, err = findComposeNamespaceRS(ctx, s, state.ParentResources, n.res.NsRef.Identifiers)
if err != nil {
return err
}
if n.relNS == nil {
return errors.New("@todo couldn't resolve namespace")
}
// Can't do anything else, since the NS doesn't yet exist
if n.relNS.ID <= 0 {
return nil
}
// Get related modules
n.relMods = make(types.ModuleSet, len(n.res.ModRef))
for i, mRef := range n.res.ModRef {
n.relMods[i], err = findComposeModuleRS(ctx, s, n.relNS.ID, state.ParentResources, mRef.Identifiers)
if err != nil {
return err
}
}
// Try to get the original chart
n.chr, err = findComposeChartS(ctx, s, n.relNS.ID, makeGenericFilter(n.res.Identifiers()))
if err != nil {
return err
}
if n.chr != nil {
n.res.Res.ID = n.chr.ID
}
return nil
}
func (n *composeChartState) Encode(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error) {
res := n.res.Res
exists := n.chr != nil && n.chr.ID > 0
// Determine the ID
if res.ID <= 0 && exists {
res.ID = n.chr.ID
}
if res.ID <= 0 {
res.ID = nextID()
}
// This is not possible, but let's do it anyway
if state.Conflicting {
return nil
}
// Namespace
res.NamespaceID = n.relNS.ID
if res.NamespaceID <= 0 {
ns := findComposeNamespaceR(state.ParentResources, n.res.NsRef.Identifiers)
res.NamespaceID = ns.ID
}
if res.NamespaceID <= 0 {
return errors.New("[chart] couldn't find related namespace; @todo error")
}
// Report modules
for i, r := range res.Config.Reports {
mod := n.relMods[i]
if mod == nil {
mod = findComposeModuleR(state.ParentResources, n.res.ModRef[i].Identifiers)
}
if mod == nil || mod.ID <= 0 {
return errors.New("[chart] couldn't find related report module; @todo error")
}
r.ModuleID = mod.ID
}
// Create fresh chart
if !exists {
return store.CreateComposeChart(ctx, s, res)
}
// Update existing chart
switch n.cfg.OnExisting {
case Skip:
return nil
case MergeLeft:
res = mergeComposeChart(n.chr, res)
case MergeRight:
res = mergeComposeChart(res, n.chr)
}
err = store.UpdateComposeChart(ctx, s, res)
if err != nil {
return err
}
n.res.Res = res
return nil
}
// mergeComposeChart merges b into a, prioritising a
func mergeComposeChart(a, b *types.Chart) *types.Chart {
c := *a
if c.Handle == "" {
c.Handle = b.Handle
}
if c.Name == "" {
c.Name = b.Name
}
if len(c.Config.Reports) <= 0 {
c.Config.Reports = b.Config.Reports
}
if c.Config.ColorScheme == "" {
c.Config.ColorScheme = b.Config.ColorScheme
}
return &c
}
// findComposeChartRS looks for the chart in the resources & the store
//
// Provided resources are prioritized.
func findComposeChartRS(ctx context.Context, s store.Storer, nsID uint64, rr resource.InterfaceSet, ii resource.Identifiers) (ch *types.Chart, err error) {
ch = findComposeChartR(rr, ii)
if ch != nil {
return ch, nil
}
// Go in the store
return findComposeChartS(ctx, s, nsID, makeGenericFilter(ii))
}
// findComposeChartS looks for the chart in the store
func findComposeChartS(ctx context.Context, s store.Storer, nsID uint64, gf genericFilter) (ch *types.Chart, err error) {
if gf.id > 0 {
ch, err = store.LookupComposeChartByID(ctx, s, gf.id)
if err != nil && err != store.ErrNotFound {
return nil, err
}
if ch != nil {
return
}
}
if gf.handle != "" {
ch, err = store.LookupComposeChartByNamespaceIDHandle(ctx, s, nsID, gf.handle)
if err != nil && err != store.ErrNotFound {
return nil, err
}
if ch != nil {
return
}
}
return nil, nil
}
// findComposeChartR looks for the chart in the resources
func findComposeChartR(rr resource.InterfaceSet, ii resource.Identifiers) (ch *types.Chart) {
var chRes *resource.ComposeChart
var ok bool
rr.Walk(func(r resource.Interface) error {
chRes, ok = r.(*resource.ComposeChart)
if !ok {
return nil
}
if !chRes.Identifiers().HasAny(r.Identifiers()) {
chRes = nil
}
return nil
})
// Found it
if chRes != nil {
return chRes.Res
}
return nil
}
+324
View File
@@ -0,0 +1,324 @@
package store
import (
"context"
"errors"
"time"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/envoy"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
"github.com/cortezaproject/corteza-server/store"
)
type (
composeModuleState struct {
cfg *EncoderConfig
res *resource.ComposeModule
mod *types.Module
relNS *types.Namespace
recFields map[string]uint64
}
)
func NewComposeModuleState(res *resource.ComposeModule, cfg *EncoderConfig) resourceState {
return &composeModuleState{
cfg: cfg,
res: res,
}
}
func (n *composeModuleState) Prepare(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error) {
// Initial values
if n.res.Res.CreatedAt.IsZero() {
n.res.Res.CreatedAt = time.Now()
}
// Get relate namespace
n.relNS, err = findComposeNamespaceRS(ctx, s, state.ParentResources, n.res.NsRef.Identifiers)
if err != nil {
return err
}
if n.relNS == nil {
return errors.New("@todo couldn't resolve namespace")
}
// Can't do anything else, since the NS doesn't yet exist
if n.relNS.ID <= 0 {
return nil
}
// Get related record field modules
for _, r := range n.res.ModRef {
mod, err := findComposeModuleRS(ctx, s, n.relNS.ID, state.ParentResources, r.Identifiers)
if err != nil {
return err
} else if mod == nil {
return errors.New("[mod prepare] couldn't find related module; @todo error")
}
for i := range r.Identifiers {
n.recFields[i] = mod.ID
}
}
// Try to get the original module
n.mod, err = findComposeModuleS(ctx, s, n.relNS.ID, makeGenericFilter(n.res.Identifiers()))
if err != nil {
return err
}
// Nothing else to do
if n.mod == nil {
return nil
}
// Get the original module fields
// These are used later for some merging logic
n.mod.Fields, err = findComposeModuleFieldsS(ctx, s, n.mod)
if err != nil {
return err
}
if n.mod != nil {
n.res.Res.ID = n.mod.ID
}
return nil
}
func (n *composeModuleState) Encode(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error) {
res := n.res.Res
exists := n.mod != nil && n.mod.ID > 0
// Determine the ID
if res.ID <= 0 && exists {
res.ID = n.mod.ID
}
if res.ID <= 0 {
res.ID = nextID()
}
if state.Conflicting {
return nil
}
// Namespace
res.NamespaceID = n.relNS.ID
if res.NamespaceID <= 0 {
ns := findComposeNamespaceR(state.ParentResources, n.res.NsRef.Identifiers)
res.NamespaceID = ns.ID
}
if res.NamespaceID <= 0 {
return errors.New("[module] couldn't find related namespace; @todo error")
}
// Fields
for i, f := range res.Fields {
f.ID = res.ID
f.ModuleID = res.ID
f.Place = i
f.DeletedAt = nil
f.CreatedAt = time.Now()
if f.Kind == "Record" {
refM := f.Options.String("module")
mID := n.recFields[refM]
if mID <= 0 {
mod := findComposeModuleR(state.ParentResources, resource.MakeIdentifiers(refM))
if mod == nil || mod.ID <= 0 {
return errors.New("[module field] couldn't find related module; @todo error")
}
mID = mod.ID
}
f.Options["module"] = mID
}
}
// Create a fresh module
if !exists {
err = store.CreateComposeModule(ctx, s, res)
if err != nil {
return err
}
err = store.CreateComposeModuleField(ctx, s, res.Fields...)
if err != nil {
return err
}
return nil
}
// Update existing module
switch n.cfg.OnExisting {
case Skip:
return nil
case MergeLeft:
res = mergeComposeModule(n.mod, res)
res.Fields = mergeComposeModuleFields(n.mod.Fields, res.Fields)
case MergeRight:
res = mergeComposeModule(res, n.mod)
res.Fields = mergeComposeModuleFields(res.Fields, n.mod.Fields)
}
err = store.UpdateComposeModule(ctx, s, res)
if err != nil {
return err
}
err = store.DeleteComposeModuleField(ctx, s, n.mod.Fields...)
if err != nil {
return err
}
err = store.CreateComposeModuleField(ctx, s, res.Fields...)
if err != nil {
return err
}
n.res.Res = res
return nil
}
// mergeComposeModuleFields merges b into a, prioritising a
func mergeComposeModuleFields(a, b types.ModuleFieldSet) types.ModuleFieldSet {
ff := a.Clone()
missing := make(types.ModuleFieldSet, 0, len(b))
for _, fb := range b {
for _, fa := range ff {
if fb.ID != fa.ID && fb.Name != fa.Name {
continue
}
if fa.Kind == "" {
fa.Kind = fb.Kind
}
if fa.Name == "" {
fa.Name = fb.Name
}
if fa.Label == "" {
fa.Label = fb.Label
}
if fa.Options == nil {
fa.Options = fb.Options
}
if fa.DefaultValue == nil || len(fa.DefaultValue) <= 0 {
fa.DefaultValue = fb.DefaultValue
}
goto out
}
missing = append(missing, fb)
out:
}
ff = append(ff, missing...)
return ff
}
// mergeComposeModule merges b into a, prioritising a
func mergeComposeModule(a, b *types.Module) *types.Module {
c := a.Clone()
if c.Handle == "" {
c.Handle = b.Handle
}
if c.Name == "" {
c.Name = b.Name
}
// I'll just compare the entire struct for now
if c.Meta == nil {
c.Meta = b.Meta
}
return c
}
// findComposeModuleRS looks for the chart in the resources & the store
//
// Provided resources are prioritized.
func findComposeModuleRS(ctx context.Context, s store.Storer, nsID uint64, rr resource.InterfaceSet, ii resource.Identifiers) (mod *types.Module, err error) {
mod = findComposeModuleR(rr, ii)
if mod != nil {
return mod, nil
}
// Go in the store
return findComposeModuleS(ctx, s, nsID, makeGenericFilter(ii))
}
// findComposeModuleS looks for the module in the store
func findComposeModuleS(ctx context.Context, s store.Storer, nsID uint64, gf genericFilter) (mod *types.Module, err error) {
if gf.id > 0 {
mod, err = store.LookupComposeModuleByID(ctx, s, gf.id)
if err != nil && err != store.ErrNotFound {
return nil, err
}
if mod != nil {
return
}
}
if gf.handle != "" {
mod, err = store.LookupComposeModuleByNamespaceIDHandle(ctx, s, nsID, gf.handle)
if err != nil && err != store.ErrNotFound {
return nil, err
}
if mod != nil {
return
}
}
return nil, nil
}
// findComposeModuleR looks for the module in the store
func findComposeModuleR(rr resource.InterfaceSet, ii resource.Identifiers) (ns *types.Module) {
var modRes *resource.ComposeModule
var ok bool
rr.Walk(func(r resource.Interface) error {
modRes, ok = r.(*resource.ComposeModule)
if !ok {
return nil
}
if !modRes.Identifiers().HasAny(r.Identifiers()) {
modRes = nil
}
return nil
})
// Found it
if modRes != nil {
return modRes.Res
}
return nil
}
// findComposeModuleFieldsS looks for the module fields in the store
func findComposeModuleFieldsS(ctx context.Context, s store.Storer, mod *types.Module) (types.ModuleFieldSet, error) {
if mod.ID <= 0 {
return mod.Fields, nil
}
ff, _, err := store.SearchComposeModuleFields(ctx, s, types.ModuleFieldFilter{
ModuleID: []uint64{mod.ID},
})
if err != nil {
return nil, err
}
return ff, nil
}
+172
View File
@@ -0,0 +1,172 @@
package store
import (
"context"
"time"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/envoy"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
"github.com/cortezaproject/corteza-server/store"
)
type (
composeNamespaceState struct {
cfg *EncoderConfig
res *resource.ComposeNamespace
ns *types.Namespace
}
)
func NewComposeNamespaceState(res *resource.ComposeNamespace, cfg *EncoderConfig) resourceState {
return &composeNamespaceState{
cfg: cfg,
res: res,
}
}
func (n *composeNamespaceState) Prepare(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error) {
// Initial values
if n.res.Res.CreatedAt.IsZero() {
n.res.Res.CreatedAt = time.Now()
}
// Try to get the original chart
n.ns, err = findComposeNamespaceS(ctx, s, makeGenericFilter(n.res.Identifiers()))
if err != nil {
return err
}
if n.ns != nil {
n.res.Res.ID = n.ns.ID
}
return nil
}
func (n *composeNamespaceState) Encode(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error) {
res := n.res.Res
exists := n.ns != nil && n.ns.ID > 0
// Determine the ID
if res.ID <= 0 && exists {
res.ID = n.ns.ID
}
if res.ID <= 0 {
res.ID = nextID()
}
// This is not possible, but let's do it anyway
if state.Conflicting {
return nil
}
// Create a fresh namespace
if !exists {
return store.CreateComposeNamespace(ctx, s, res)
}
// Update existing namespace
switch n.cfg.OnExisting {
case Skip:
return nil
case MergeLeft:
res = mergeComposeNamespaces(n.ns, res)
case MergeRight:
res = mergeComposeNamespaces(res, n.ns)
}
err = store.UpdateComposeNamespace(ctx, s, res)
if err != nil {
return err
}
n.res.Res = res
return nil
}
// mergeComposeNamespaces merges b into a, prioritising a
func mergeComposeNamespaces(a, b *types.Namespace) *types.Namespace {
c := a.Clone()
if c.Name == "" {
c.Name = b.Name
}
if c.Slug == "" {
c.Slug = b.Slug
}
// I'll just compare the entire struct for now
if c.Meta == (types.NamespaceMeta{}) {
c.Meta = b.Meta
}
return c
}
// findComposeNamespaceRS looks for the namespace in the resources & the store
//
// Provided resources are prioritized.
func findComposeNamespaceRS(ctx context.Context, s store.Storer, rr resource.InterfaceSet, ii resource.Identifiers) (ns *types.Namespace, err error) {
ns = findComposeNamespaceR(rr, ii)
if ns != nil {
return ns, nil
}
return findComposeNamespaceS(ctx, s, makeGenericFilter(ii))
}
// findComposeNamespaceS looks for the namespace in the store
func findComposeNamespaceS(ctx context.Context, s store.Storer, gf genericFilter) (ns *types.Namespace, err error) {
if gf.id > 0 {
ns, err = store.LookupComposeNamespaceByID(ctx, s, gf.id)
if err != nil && err != store.ErrNotFound {
return nil, err
}
if ns != nil {
return
}
}
if gf.handle != "" {
ns, err = store.LookupComposeNamespaceBySlug(ctx, s, gf.handle)
if err != nil && err != store.ErrNotFound {
return nil, err
}
if ns != nil {
return
}
}
return nil, nil
}
// findComposeNamespaceR looks for the namespace in the resources
func findComposeNamespaceR(rr resource.InterfaceSet, ii resource.Identifiers) (ns *types.Namespace) {
var nsRes *resource.ComposeNamespace
var ok bool
rr.Walk(func(r resource.Interface) error {
nsRes, ok = r.(*resource.ComposeNamespace)
if !ok {
return nil
}
if !nsRes.Identifiers().HasAny(r.Identifiers()) {
nsRes = nil
}
return nil
})
// Found it
if nsRes != nil {
return nsRes.Res
}
return nil
}
+229
View File
@@ -0,0 +1,229 @@
package store
import (
"context"
"errors"
"time"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/envoy"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
"github.com/cortezaproject/corteza-server/store"
)
type (
composePageState struct {
cfg *EncoderConfig
res *resource.ComposePage
pg *types.Page
relNS *types.Namespace
relMod *types.Module
}
)
func NewComposePageState(res *resource.ComposePage, cfg *EncoderConfig) resourceState {
return &composePageState{
cfg: cfg,
res: res,
}
}
func (n *composePageState) Prepare(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error) {
// Initial values
if n.res.Res.CreatedAt.IsZero() {
n.res.Res.CreatedAt = time.Now()
}
// Get relate namespace
n.relNS, err = findComposeNamespaceRS(ctx, s, state.ParentResources, n.res.NsRef.Identifiers)
if err != nil {
return err
}
if n.relNS == nil {
return errors.New("@todo couldn't resolve namespace")
}
// Can't do anything else, since the NS doesn't yet exist
if n.relNS.ID <= 0 {
return nil
}
// Get related module
// If this isn't a record page, there is no related module
if n.res.ModRef != nil {
n.relMod, err = findComposeModuleRS(ctx, s, n.relNS.ID, state.ParentResources, n.res.ModRef.Identifiers)
if err != nil {
return err
}
if n.relMod == nil {
return errors.New("@todo couldn't resolve module")
}
}
// Try to get the original page
n.pg, err = findComposePageS(ctx, s, n.relNS.ID, makeGenericFilter(n.res.Identifiers()))
if err != nil {
return err
}
if n.pg != nil {
n.res.Res.ID = n.pg.ID
}
return nil
}
func (n *composePageState) Encode(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error) {
res := n.res.Res
exists := n.pg != nil && n.pg.ID > 0
// Determine the ID
if res.ID <= 0 && exists {
res.ID = n.pg.ID
}
if res.ID <= 0 {
res.ID = nextID()
}
// This is not possible, but let's do it anyway
if state.Conflicting {
return nil
}
// Namespace
res.NamespaceID = n.relNS.ID
if res.NamespaceID <= 0 {
ns := findComposeNamespaceR(state.ParentResources, n.res.NsRef.Identifiers)
res.NamespaceID = ns.ID
}
if res.NamespaceID <= 0 {
return errors.New("[chart] couldn't find related namespace; @todo error")
}
// Module?
if n.res.ModRef != nil {
res.ModuleID = n.relMod.ID
if res.ModuleID <= 0 {
mod := findComposeModuleR(state.ParentResources, n.res.ModRef.Identifiers)
res.ModuleID = mod.ID
}
}
// Create a fresh page
if !exists {
return store.CreateComposePage(ctx, s, res)
}
// Update existing page
switch n.cfg.OnExisting {
case Skip:
return nil
case MergeLeft:
res = mergeComposePage(n.pg, res)
case MergeRight:
res = mergeComposePage(res, n.pg)
}
err = store.UpdateComposePage(ctx, s, res)
if err != nil {
return err
}
n.res.Res = res
return nil
}
// mergeComposePage merges b into a, prioritising a
func mergeComposePage(a, b *types.Page) *types.Page {
c := a.Clone()
if c.SelfID <= 0 {
c.SelfID = b.SelfID
}
if c.Handle == "" {
c.Handle = b.Handle
}
if c.Title == "" {
c.Title = b.Title
}
if c.Description == "" {
c.Description = b.Description
}
if len(c.Blocks) <= 0 {
c.Blocks = b.Blocks
}
if len(c.Children) <= 0 {
c.Children = b.Children
}
return c
}
// findComposePageRS looks for the page in the resources & the store
//
// Provided resources are prioritized.
func findComposePageRS(ctx context.Context, s store.Storer, nsID uint64, rr resource.InterfaceSet, ii resource.Identifiers) (pg *types.Page, err error) {
pg = findComposePageR(ctx, rr, ii)
if pg != nil {
return pg, nil
}
// Go in the store
return findComposePageS(ctx, s, nsID, makeGenericFilter(ii))
}
// findComposePageS looks for the page in the store
func findComposePageS(ctx context.Context, s store.Storer, nsID uint64, gf genericFilter) (pg *types.Page, err error) {
if gf.id > 0 {
pg, err = store.LookupComposePageByID(ctx, s, gf.id)
if err != nil && err != store.ErrNotFound {
return nil, err
}
if pg != nil {
return
}
}
if gf.handle != "" {
pg, err = store.LookupComposePageByNamespaceIDHandle(ctx, s, nsID, gf.handle)
if err != nil && err != store.ErrNotFound {
return nil, err
}
if pg != nil {
return
}
}
return nil, nil
}
// findComposePageR looks for the page in the resources
func findComposePageR(ctx context.Context, rr resource.InterfaceSet, ii resource.Identifiers) (pg *types.Page) {
var pgRes *resource.ComposePage
var ok bool
rr.Walk(func(r resource.Interface) error {
pgRes, ok = r.(*resource.ComposePage)
if !ok {
return nil
}
if !pgRes.Identifiers().HasAny(r.Identifiers()) {
pgRes = nil
}
return nil
})
// Found it
if pgRes != nil {
return pgRes.Res
}
return nil
}
+206
View File
@@ -0,0 +1,206 @@
package store
import (
"context"
"errors"
"strconv"
"time"
"github.com/cortezaproject/corteza-server/compose/service/values"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/envoy"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
"github.com/cortezaproject/corteza-server/store"
)
type (
composeRecordState struct {
cfg *EncoderConfig
res *resource.ComposeRecord
relNS *types.Namespace
relMod *types.Module
}
)
var (
rvSanitizer = values.Sanitizer()
rvValidator = values.Validator()
)
func NewComposeRecordState(res *resource.ComposeRecord, cfg *EncoderConfig) resourceState {
return &composeRecordState{
cfg: cfg,
res: res,
}
}
func (n *composeRecordState) Prepare(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error) {
// Get related namespace
n.relNS, err = findComposeNamespaceRS(ctx, s, state.ParentResources, n.res.NsRef.Identifiers)
if err != nil {
return err
}
if n.relNS == nil {
return errors.New("@todo couldn't resolve namespace")
}
n.relMod = findComposeModuleR(state.ParentResources, n.res.ModRef.Identifiers)
if n.relMod == nil && n.relNS.ID > 0 {
n.relMod, err = findComposeModuleS(ctx, s, n.relNS.ID, makeGenericFilter(n.res.ModRef.Identifiers))
if err != nil {
return err
}
if n.relMod == nil {
return errors.New("@todo couldn't resolve module")
}
// Preload existing fields
n.relMod.Fields, err = findComposeModuleFieldsS(ctx, s, n.relMod)
if err != nil {
return err
}
}
// Add missing refs
for _, f := range n.relMod.Fields {
switch f.Kind {
case "Record":
refM := f.Options.String("module")
if refM != "" && refM != "0" {
// Make a reference with that module's records
n.res.AddRef(resource.COMPOSE_RECORD_RESOURCE_TYPE, refM)
}
}
}
// Can't do anything else, since the NS doesn't yet exist
if n.relNS.ID <= 0 {
return nil
}
// Try to get existing records
//
// @todo handle large amounts of
for rID := range n.res.IDMap {
var r *types.Record
// @todo support for labels
if refy.MatchString(rID) {
id, _ := strconv.ParseUint(rID, 10, 64)
r, err = store.LookupComposeRecordByID(ctx, s, n.relMod, id)
if err == store.ErrNotFound {
continue
} else if err != nil {
return err
}
if r != nil {
n.res.RecMap[rID] = r
}
} else {
continue
}
n.res.RecMap[rID] = r
}
return nil
}
func (n *composeRecordState) Encode(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error) {
// Namespace
nsID := n.relNS.ID
if nsID <= 0 {
ns := findComposeNamespaceR(state.ParentResources, n.res.NsRef.Identifiers)
nsID = ns.ID
}
// Module
mod := n.relMod
if mod.ID <= 0 {
m := findComposeModuleR(state.ParentResources, n.res.ModRef.Identifiers)
mod.ID = m.ID
}
// Some pointing
rm := n.res.RecMap
im := n.res.IDMap
return n.res.Walker(func(r *resource.ComposeRecordRaw) error {
rec := &types.Record{
ID: im[r.ID],
NamespaceID: nsID,
ModuleID: mod.ID,
CreatedAt: time.Now(),
}
exists := rm[r.ID] != nil
if rec.ID <= 0 && exists {
rec.ID = rm[r.ID].ID
} else {
rec.ID = nextID()
}
im[r.ID] = rec.ID
if state.Conflicting {
return nil
}
// Sys values
//
// @todo
for k, v := range r.SysValues {
if v == "" {
continue
}
switch k {
case "createdAt":
// @todo set time
rec.CreatedAt = time.Now()
case "updatedAt":
// @todo set time
rec.UpdatedAt = nil
case "deletedAt":
// @todo set time
rec.DeletedAt = nil
}
}
rvs := make(types.RecordValueSet, 0, len(r.Values))
for k, v := range r.Values {
rv := &types.RecordValue{
RecordID: rec.ID,
Name: k,
Value: v,
Updated: true,
}
rvs = append(rvs, rv)
}
// @todo validator
rec.Values = rvSanitizer.Run(mod, rvs)
// Create a new record
if !exists {
err = store.CreateComposeRecord(ctx, s, mod, rec)
if err != nil {
return err
}
return nil
}
// Update existing
err = store.UpdateComposeRecord(ctx, s, mod, rec)
if err != nil {
return err
}
return nil
})
}
+149
View File
@@ -0,0 +1,149 @@
package store
import (
"context"
"errors"
"sync"
"github.com/cortezaproject/corteza-server/pkg/envoy"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
"github.com/cortezaproject/corteza-server/store"
"github.com/davecgh/go-spew/spew"
)
const (
// Skip skips the existing resource
Skip mergeAlg = iota
// Replace replaces the existing resource
Replace
// MergeLeft updates the existing resource, giving priority to the existing data
MergeLeft
// MergeRight updates the existing resource, giving priority to the new data
MergeRight
)
type (
storeEncoder struct {
s store.Storer
cfg *EncoderConfig
// Each resource should define its own state that is used when encoding the resource.
// Such approach removes the need for a janky generic global state.
// This also simplifies any slight deviations between resource complexities.
state map[resource.Interface]resourceState
}
mergeAlg int
// EncoderConfig allows us to configure the resource encoding process
EncoderConfig struct {
OnExisting mergeAlg
}
// resourceState allows each conforming struct to be initialized and encoded
// by the store encoder
resourceState interface {
Prepare(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error)
Encode(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error)
}
)
// NewStoreEncoder initializes a fresh store encoder
//
// If no config is provided, it uses Skip as the default merge alg.
func NewStoreEncoder(s store.Storer, cfg *EncoderConfig) envoy.PrepareEncoder {
if cfg == nil {
cfg = &EncoderConfig{
OnExisting: Skip,
}
}
return &storeEncoder{
s: s,
cfg: cfg,
state: make(map[resource.Interface]resourceState),
}
}
// Prepare prepares the encoder for the given set of resources
//
// It initializes and prepares the resource state for each provided resource
func (se *storeEncoder) Prepare(ctx context.Context, ee ...*envoy.ResourceState) (err error) {
f := func(rs resourceState, es *envoy.ResourceState) error {
err = rs.Prepare(ctx, se.s, es)
if err != nil {
return err
}
se.state[es.Res] = rs
return nil
}
for _, e := range ee {
switch res := e.Res.(type) {
// Compose things
case *resource.ComposeNamespace:
err = f(NewComposeNamespaceState(res, se.cfg), e)
case *resource.ComposeModule:
err = f(NewComposeModuleState(res, se.cfg), e)
case *resource.ComposeRecord:
err = f(NewComposeRecordState(res, se.cfg), e)
case *resource.ComposeChart:
err = f(NewComposeChartState(res, se.cfg), e)
case *resource.ComposePage:
err = f(NewComposePageState(res, se.cfg), e)
// System things
case *resource.User:
err = f(NewUserState(res, se.cfg), e)
case *resource.Role:
err = f(NewRole(res, se.cfg), e)
case *resource.Application:
err = f(NewApplicationState(res, se.cfg), e)
case *resource.Settings:
err = f(NewSettingsState(res, se.cfg), e)
case *resource.RbacRule:
err = f(NewRbacRuleState(res, se.cfg), e)
default:
return errors.New("[encoder] unknown resource; @todo error")
}
if err != nil {
return err
}
}
return nil
}
// Encode encodes available resource states using the given store encoder
func (se *storeEncoder) Encode(ctx context.Context, wg *sync.WaitGroup, rc envoy.Rc, ec envoy.Ec) {
defer wg.Done()
var e *envoy.ResourceState
err := store.Tx(ctx, se.s, func(ctx context.Context, s store.Storer) (err error) {
for {
e = <-rc
if e == nil {
return nil
}
state := se.state[e.Res]
if state == nil {
return errors.New("Resource state not defined; @todo error")
}
err = state.Encode(ctx, se.s, e)
if err != nil {
return err
}
}
})
if err != nil {
// ec <- err
spew.Dump(err)
}
}
+131
View File
@@ -0,0 +1,131 @@
package store
import (
"context"
"errors"
"github.com/cortezaproject/corteza-server/pkg/envoy"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
"github.com/cortezaproject/corteza-server/pkg/rbac"
"github.com/cortezaproject/corteza-server/store"
"github.com/cortezaproject/corteza-server/system/types"
)
type (
rbacRuleState struct {
cfg *EncoderConfig
res *resource.RbacRule
rule *rbac.Rule
relRole *types.Role
relResource resource.Interface
}
)
var (
// Use this as a cache, so we don't have to constantly fetch them
rbacRules rbac.RuleSet = nil
)
func NewRbacRuleState(res *resource.RbacRule, cfg *EncoderConfig) resourceState {
return &rbacRuleState{
cfg: cfg,
res: res,
}
}
func (n *rbacRuleState) Prepare(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error) {
// Preload rbac rules if needed
if rbacRules == nil {
rbacRules, _, err = store.SearchRbacRules(ctx, s, rbac.RuleFilter{})
if err != store.ErrNotFound && err != nil {
return err
}
}
// Check for role
n.relRole, err = findRoleRS(ctx, s, state.ParentResources, n.res.RefRole.Identifiers)
if err != nil {
return err
}
if n.relRole == nil {
return errors.New("[rbac rule] couldn't resolve role; @todo error")
}
// Check for resource if there is any
if n.res.RefResource != nil {
n.relResource = n.findResourceR(ctx, state.ParentResources, n.res.RefResource.ResourceType, n.res.RefResource.Identifiers)
if n.relResource == nil {
// Try to find it in the store
// @todo...
}
}
return nil
}
func (n *rbacRuleState) Encode(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error) {
rule := n.res.Res
// Role
rule.RoleID = n.relRole.ID
if rule.RoleID <= 0 {
rl := findRoleR(state.ParentResources, n.res.RefRole.Identifiers)
rule.RoleID = rl.ID
}
// Resource?
if n.res.RefResource != nil {
if ir, ok := n.relResource.(resource.IdentifiableInterface); !ok {
return errors.New("[rbac rule] resource not identifiable; @todo error")
} else {
rule.Resource = rule.Resource.AppendID(ir.SysID())
}
} else if rule.Resource.IsAppendable() {
rule.Resource = rule.Resource.AppendWildcard()
}
// There isn't anything to merge really, so Skip & MergeLeft skip it;
// Replace & MergeRight replace it.
//
// Replacing is handled after
switch n.cfg.OnExisting {
case Skip,
MergeLeft:
return nil
}
nrr := rbacRules.Merge(rule)
d, u := nrr.Dirty()
err = store.DeleteRbacRule(ctx, s, d...)
if err != nil {
return
}
err = store.UpsertRbacRule(ctx, s, u...)
if err != nil {
return
}
nrr.Clear()
rbacRules = nrr
return nil
}
func (n *rbacRuleState) findResourceR(ctx context.Context, rr resource.InterfaceSet, rt string, ii resource.Identifiers) (rtr resource.Interface) {
for _, r := range rr {
if r.ResourceType() != rt {
continue
}
if !r.Identifiers().HasAny(ii) {
continue
}
return r
}
return nil
}
+166
View File
@@ -0,0 +1,166 @@
package store
import (
"context"
"time"
"github.com/cortezaproject/corteza-server/pkg/envoy"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
"github.com/cortezaproject/corteza-server/store"
"github.com/cortezaproject/corteza-server/system/types"
)
type (
roleState struct {
cfg *EncoderConfig
res *resource.Role
rl *types.Role
}
)
func NewRole(res *resource.Role, cfg *EncoderConfig) resourceState {
return &roleState{
cfg: cfg,
res: res,
}
}
func (n *roleState) Prepare(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error) {
// Initial values
if n.res.Res.CreatedAt.IsZero() {
n.res.Res.CreatedAt = time.Now()
}
n.rl, err = findRoleS(ctx, s, makeGenericFilter(n.res.Identifiers()))
if err != nil {
return err
}
if n.rl != nil {
n.res.Res.ID = n.rl.ID
}
return nil
}
func (n *roleState) Encode(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error) {
rl := n.res.Res
exists := n.rl != nil && n.rl.ID > 0
// Determine the ID
if rl.ID <= 0 && exists {
rl.ID = n.rl.ID
}
if rl.ID <= 0 {
rl.ID = nextID()
}
// This is not possible, but let's do it anyway
if state.Conflicting {
return nil
}
// Create a fresh role
if !exists {
return store.CreateRole(ctx, s, rl)
}
// Update existing roles
switch n.cfg.OnExisting {
case Skip:
return nil
case MergeLeft:
rl = mergeRole(n.rl, rl)
case MergeRight:
rl = mergeRole(rl, n.rl)
}
err = store.UpdateRole(ctx, s, rl)
if err != nil {
return err
}
n.res.Res = rl
return nil
}
// mergeRole merges b into a, prioritising a
func mergeRole(a, b *types.Role) *types.Role {
c := *a
if c.Name == "" {
c.Name = b.Name
}
if c.Handle == "" {
c.Handle = b.Handle
}
return &c
}
// findRoleRS looks for the role in the resources & the store
//
// Provided resources are prioritized.
func findRoleRS(ctx context.Context, s store.Storer, rr resource.InterfaceSet, ii resource.Identifiers) (rl *types.Role, err error) {
rl = findRoleR(rr, ii)
if rl != nil {
return rl, nil
}
return findRoleS(ctx, s, makeGenericFilter(ii))
}
// findRoleS looks for the role in the store
func findRoleS(ctx context.Context, s store.Storer, gf genericFilter) (rl *types.Role, err error) {
if gf.id > 0 {
rl, err = store.LookupRoleByID(ctx, s, gf.id)
if err != nil && err != store.ErrNotFound {
return nil, err
}
if rl != nil {
return
}
}
if gf.handle != "" {
rl, err = store.LookupRoleByHandle(ctx, s, gf.handle)
if err != nil && err != store.ErrNotFound {
return nil, err
}
if rl != nil {
return
}
}
return nil, nil
}
// findRoleR looks for the role in the resources
func findRoleR(rr resource.InterfaceSet, ii resource.Identifiers) (rl *types.Role) {
var rlRes *resource.Role
var ok bool
rr.Walk(func(r resource.Interface) error {
rlRes, ok = r.(*resource.Role)
if !ok {
return nil
}
if rlRes.Identifiers().HasAny(r.Identifiers()) {
rlRes = nil
}
return nil
})
// Found it
if rlRes != nil {
return rlRes.Res
}
return nil
}
+90
View File
@@ -0,0 +1,90 @@
package store
import (
"context"
"time"
"github.com/cortezaproject/corteza-server/pkg/envoy"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
"github.com/cortezaproject/corteza-server/store"
"github.com/cortezaproject/corteza-server/system/types"
)
type (
settingsState struct {
cfg *EncoderConfig
res *resource.Settings
ss types.SettingValueSet
}
)
func NewSettingsState(res *resource.Settings, cfg *EncoderConfig) resourceState {
return &settingsState{
cfg: cfg,
res: res,
}
}
func (n *settingsState) Prepare(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error) {
// Preload settings
n.ss, _, err = store.SearchSettings(ctx, s, types.SettingsFilter{})
if err == store.ErrNotFound {
n.ss = make(types.SettingValueSet, 0, len(n.res.Res))
} else if err != nil {
return err
}
// Default values
for _, s := range n.res.Res {
if s.UpdatedAt.IsZero() {
s.UpdatedAt = time.Now()
}
}
// Nothing else to do.
// Settings can't conflict either.
return nil
}
func (n *settingsState) Encode(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error) {
ss := make(types.SettingValueSet, 0, len(n.res.Res))
for _, ns := range n.res.Res {
os := n.ss.First(ns.Name)
if os != nil {
// Update existing setting
switch n.cfg.OnExisting {
case Skip:
ss = append(ss, os)
case Replace:
ss = append(ss, ns)
case MergeLeft:
ss = append(ss, mergeSettings(os, ns))
case MergeRight:
ss = append(ss, mergeSettings(ns, os))
}
} else {
// Create fresh setting
ss = append(ss, ns)
}
}
return store.UpsertSetting(ctx, s, ss...)
}
// mergeSettings merges b into a, prioritising a
func mergeSettings(a, b *types.SettingValue) *types.SettingValue {
c := *a
if len(c.Value) <= 0 {
c.Value = b.Value
}
return &c
}
+181
View File
@@ -0,0 +1,181 @@
package store
import (
"context"
"time"
"github.com/cortezaproject/corteza-server/pkg/envoy"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
"github.com/cortezaproject/corteza-server/store"
"github.com/cortezaproject/corteza-server/system/types"
)
type (
userState struct {
cfg *EncoderConfig
res *resource.User
u *types.User
}
)
func NewUserState(res *resource.User, cfg *EncoderConfig) resourceState {
return &userState{
cfg: cfg,
res: res,
}
}
func (n *userState) Prepare(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error) {
// Initial values
if n.res.Res.CreatedAt.IsZero() {
n.res.Res.CreatedAt = time.Now()
}
// Try to get the original user
// @todo make filtering more flexible (email, username, ...)
n.u, err = findUserS(ctx, s, makeGenericFilter(n.res.Identifiers()))
if err != nil {
return err
}
if n.u != nil {
n.res.Res.ID = n.u.ID
}
return nil
}
func (n *userState) Encode(ctx context.Context, s store.Storer, state *envoy.ResourceState) (err error) {
res := n.res.Res
exists := n.u != nil && n.u.ID > 0
// Determine the ID
if res.ID <= 0 && exists {
res.ID = n.u.ID
}
if res.ID <= 0 {
res.ID = nextID()
}
// This is not possible, but let's do it anyway
if state.Conflicting {
return nil
}
// Create a fresh user
if !exists {
return store.CreateUser(ctx, s, res)
}
// Update existing user
switch n.cfg.OnExisting {
case Skip:
return nil
case MergeLeft:
res = mergeUsers(n.u, res)
case MergeRight:
res = mergeUsers(res, n.u)
}
err = store.UpdateUser(ctx, s, res)
if err != nil {
return err
}
n.res.Res = res
return nil
}
// mergeUsers merges b into a, prioritising a
func mergeUsers(a, b *types.User) *types.User {
c := *a
if c.Username == "" {
c.Username = b.Username
}
if c.Email == "" {
c.Email = b.Email
}
if c.Name == "" {
c.Name = b.Name
}
if c.Handle == "" {
c.Handle = b.Handle
}
if c.Kind == "" {
c.Kind = b.Kind
}
if *c.Meta == (types.UserMeta{}) {
c.Meta = b.Meta
}
return &c
}
// findUserRS looks for the user in the resources & the store
//
// Provided resources are prioritized.
func findUserRS(ctx context.Context, s store.Storer, rr resource.InterfaceSet, ii resource.Identifiers) (u *types.User, err error) {
u = findUserR(ctx, rr, ii)
if u != nil {
return u, nil
}
return findUserS(ctx, s, makeGenericFilter(ii))
}
// findUserS looks for the user in the store
func findUserS(ctx context.Context, s store.Storer, gf genericFilter) (u *types.User, err error) {
if gf.id > 0 {
u, err = store.LookupUserByID(ctx, s, gf.id)
if err != nil && err != store.ErrNotFound {
return nil, err
}
if u != nil {
return
}
}
if gf.handle != "" {
u, err = store.LookupUserByHandle(ctx, s, gf.handle)
if err != nil && err != store.ErrNotFound {
return nil, err
}
if u != nil {
return
}
}
return nil, nil
}
// findUserR looks for the user in the resources
func findUserR(ctx context.Context, rr resource.InterfaceSet, ii resource.Identifiers) (u *types.User) {
// Try to find it in the parent resources
var uRes *resource.User
var ok bool
rr.Walk(func(r resource.Interface) error {
uRes, ok = r.(*resource.User)
if !ok {
return nil
}
if !uRes.Identifiers().HasAny(r.Identifiers()) {
uRes = nil
}
return nil
})
// Found it
if uRes != nil {
return uRes.Res
}
return nil
}
+54
View File
@@ -0,0 +1,54 @@
package store
import (
"regexp"
"strconv"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
"github.com/cortezaproject/corteza-server/pkg/handle"
"github.com/cortezaproject/corteza-server/pkg/id"
)
type (
genericFilter struct {
id uint64
handle string
name string
}
)
var (
// simple uint check.
// we'll use the pkg/handle to check for handles.
refy = regexp.MustCompile(`^[1-9](\d*)$`)
// wrapper around nextID that will aid service testing
nextID = func() uint64 {
return id.Next()
}
)
// makeGenericFilter is a helper to determine the base resource filter.
//
// It attempts to determine an identifier, handle, and name.
func makeGenericFilter(ii resource.Identifiers) (f genericFilter) {
for i := range ii {
if i == "" {
continue
}
if refy.MatchString(i) && f.id <= 0 {
id, err := strconv.ParseUint(i, 10, 64)
if err != nil {
continue
}
f.id = id
} else if handle.IsValid(i) && f.handle == "" {
f.handle = i
} else if f.name == "" {
f.name = i
}
}
return f
}