diff --git a/compose/service/record.go b/compose/service/record.go index bf1719d03..88f104767 100644 --- a/compose/service/record.go +++ b/compose/service/record.go @@ -343,7 +343,7 @@ func (svc record) Import(ses *recordImportSession) (err error) { // Build cfg := &estore.EncoderConfig{ // For now the identifier is ignored, so this will never occur - OnExisting: estore.Skip, + OnExisting: resource.Skip, Defer: func() { ses.Progress.Completed++ }, diff --git a/pkg/envoy/resource/base.go b/pkg/envoy/resource/base.go index 91bff470f..50c3f3014 100644 --- a/pkg/envoy/resource/base.go +++ b/pkg/envoy/resource/base.go @@ -8,12 +8,14 @@ type ( ts *Timestamps us *Userstamps + cfg *EnvoyConfig urefs RefSet } EnvoyConfig struct { // SkipIf determines when the encoding should be skipped for this resource - SkipIf string + SkipIf string + OnExisting MergeAlg } Timestamps struct { @@ -29,6 +31,21 @@ type ( DeletedBy string OwnedBy string } + + MergeAlg int +) + +const ( + // Default takes the operation defined default + Default MergeAlg = iota + // Skip skips the existing resource + Skip + // 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 ) // State management methods @@ -85,6 +102,13 @@ func (t *base) Userstamps() *Userstamps { return t.us } +func (t *base) SetConfig(cfg *EnvoyConfig) { + t.cfg = cfg +} +func (t *base) Config() *EnvoyConfig { + return t.cfg +} + func (t *base) SetUserRefs(uu []string) { if t.urefs == nil { t.urefs = make(RefSet, 0, 4) diff --git a/pkg/envoy/store/application.go b/pkg/envoy/store/application.go index 9e37e1d4a..ee9f8cc53 100644 --- a/pkg/envoy/store/application.go +++ b/pkg/envoy/store/application.go @@ -21,7 +21,7 @@ type ( func NewApplicationState(res *resource.Application, cfg *EncoderConfig) resourceState { return &applicationState{ - cfg: cfg, + cfg: mergeConfig(cfg, res.Config()), res: res, } } @@ -82,6 +82,14 @@ func (n *applicationState) Encode(ctx context.Context, s store.Storer, state *en } } + // Evaluate the resource skip expression + // @todo expand available parameters; similar implementation to compose/types/record@Dict + if skip, err := basicSkipEval(ctx, n.cfg, !exists); err != nil { + return err + } else if skip { + return nil + } + // Create fresh application if !exists { return store.CreateApplication(ctx, s, res) @@ -89,13 +97,13 @@ func (n *applicationState) Encode(ctx context.Context, s store.Storer, state *en // Update existing application switch n.cfg.OnExisting { - case Skip: + case resource.Skip: return nil - case MergeLeft: + case resource.MergeLeft: res = mergeApplications(n.app, res) - case MergeRight: + case resource.MergeRight: res = mergeApplications(res, n.app) } diff --git a/pkg/envoy/store/compose_chart.go b/pkg/envoy/store/compose_chart.go index 4ae4bb709..0fe634ebb 100644 --- a/pkg/envoy/store/compose_chart.go +++ b/pkg/envoy/store/compose_chart.go @@ -25,7 +25,7 @@ type ( func NewComposeChartState(res *resource.ComposeChart, cfg *EncoderConfig) resourceState { return &composeChartState{ - cfg: cfg, + cfg: mergeConfig(cfg, res.Config()), res: res, } @@ -66,6 +66,7 @@ func (n *composeChartState) Prepare(ctx context.Context, s store.Storer, state * if n.chr != nil { n.res.Res.ID = n.chr.ID + n.res.Res.NamespaceID = n.chr.NamespaceID } return nil } @@ -127,6 +128,14 @@ func (n *composeChartState) Encode(ctx context.Context, s store.Storer, state *e r.ModuleID = mod.ID } + // Evaluate the resource skip expression + // @todo expand available parameters; similar implementation to compose/types/record@Dict + if skip, err := basicSkipEval(ctx, n.cfg, !exists); err != nil { + return err + } else if skip { + return nil + } + // Create fresh chart if !exists { return store.CreateComposeChart(ctx, s, res) @@ -134,13 +143,13 @@ func (n *composeChartState) Encode(ctx context.Context, s store.Storer, state *e // Update existing chart switch n.cfg.OnExisting { - case Skip: + case resource.Skip: return nil - case MergeLeft: + case resource.MergeLeft: res = mergeComposeChart(n.chr, res) - case MergeRight: + case resource.MergeRight: res = mergeComposeChart(res, n.chr) } diff --git a/pkg/envoy/store/compose_module.go b/pkg/envoy/store/compose_module.go index b551b378c..be635c019 100644 --- a/pkg/envoy/store/compose_module.go +++ b/pkg/envoy/store/compose_module.go @@ -26,7 +26,7 @@ type ( func NewComposeModuleState(res *resource.ComposeModule, cfg *EncoderConfig) resourceState { return &composeModuleState{ - cfg: cfg, + cfg: mergeConfig(cfg, res.Config()), res: res, @@ -95,6 +95,7 @@ func (n *composeModuleState) Prepare(ctx context.Context, s store.Storer, state if n.mod != nil { n.res.Res.ID = n.mod.ID + n.res.Res.NamespaceID = n.mod.NamespaceID } return nil } @@ -177,6 +178,14 @@ func (n *composeModuleState) Encode(ctx context.Context, s store.Storer, state * } } + // Evaluate the resource skip expression + // @todo expand available parameters; similar implementation to compose/types/record@Dict + if skip, err := basicSkipEval(ctx, n.cfg, !exists); err != nil { + return err + } else if skip { + return nil + } + // Create a fresh module if !exists { err = store.CreateComposeModule(ctx, s, res) @@ -194,14 +203,14 @@ func (n *composeModuleState) Encode(ctx context.Context, s store.Storer, state * // Update existing module switch n.cfg.OnExisting { - case Skip: + case resource.Skip: return nil - case MergeLeft: + case resource.MergeLeft: res = mergeComposeModule(n.mod, res) res.Fields = mergeComposeModuleFields(n.mod.Fields, res.Fields) - case MergeRight: + case resource.MergeRight: res = mergeComposeModule(res, n.mod) res.Fields = mergeComposeModuleFields(res.Fields, n.mod.Fields) } diff --git a/pkg/envoy/store/compose_namespace.go b/pkg/envoy/store/compose_namespace.go index 583ed16f7..98358b317 100644 --- a/pkg/envoy/store/compose_namespace.go +++ b/pkg/envoy/store/compose_namespace.go @@ -22,7 +22,7 @@ type ( func NewComposeNamespaceState(res *resource.ComposeNamespace, cfg *EncoderConfig) resourceState { return &composeNamespaceState{ - cfg: cfg, + cfg: mergeConfig(cfg, res.Config()), res: res, } @@ -80,6 +80,14 @@ func (n *composeNamespaceState) Encode(ctx context.Context, s store.Storer, stat } } + // Evaluate the resource skip expression + // @todo expand available parameters; similar implementation to compose/types/record@Dict + if skip, err := basicSkipEval(ctx, n.cfg, !exists); err != nil { + return err + } else if skip { + return nil + } + // Create a fresh namespace if !exists { return store.CreateComposeNamespace(ctx, s, res) @@ -87,13 +95,13 @@ func (n *composeNamespaceState) Encode(ctx context.Context, s store.Storer, stat // Update existing namespace switch n.cfg.OnExisting { - case Skip: + case resource.Skip: return nil - case MergeLeft: + case resource.MergeLeft: res = mergeComposeNamespaces(n.ns, res) - case MergeRight: + case resource.MergeRight: res = mergeComposeNamespaces(res, n.ns) } diff --git a/pkg/envoy/store/compose_page.go b/pkg/envoy/store/compose_page.go index d1ca39207..199ab1ef0 100644 --- a/pkg/envoy/store/compose_page.go +++ b/pkg/envoy/store/compose_page.go @@ -30,7 +30,7 @@ type ( func NewComposePageState(res *resource.ComposePage, cfg *EncoderConfig) resourceState { return &composePageState{ - cfg: cfg, + cfg: mergeConfig(cfg, res.Config()), res: res, @@ -113,6 +113,7 @@ func (n *composePageState) Prepare(ctx context.Context, s store.Storer, state *e if n.pg != nil { n.res.Res.ID = n.pg.ID + n.res.Res.NamespaceID = n.pg.NamespaceID } return nil } @@ -258,6 +259,14 @@ func (n *composePageState) Encode(ctx context.Context, s store.Storer, state *en } } + // Evaluate the resource skip expression + // @todo expand available parameters; similar implementation to compose/types/record@Dict + if skip, err := basicSkipEval(ctx, n.cfg, !exists); err != nil { + return err + } else if skip { + return nil + } + // Create a fresh page if !exists { return store.CreateComposePage(ctx, s, res) @@ -265,13 +274,13 @@ func (n *composePageState) Encode(ctx context.Context, s store.Storer, state *en // Update existing page switch n.cfg.OnExisting { - case Skip: + case resource.Skip: return nil - case MergeLeft: + case resource.MergeLeft: res = mergeComposePage(n.pg, res) - case MergeRight: + case resource.MergeRight: res = mergeComposePage(res, n.pg) } diff --git a/pkg/envoy/store/compose_record.go b/pkg/envoy/store/compose_record.go index 269ed9768..1f7a6a117 100644 --- a/pkg/envoy/store/compose_record.go +++ b/pkg/envoy/store/compose_record.go @@ -64,17 +64,6 @@ func (n *composeRecordState) Prepare(ctx context.Context, s store.Storer, state if err != nil { return err } - - // Check if empty - rr, _, err := store.SearchComposeRecords(ctx, s, n.relMod, types.RecordFilter{ - ModuleID: n.relMod.ID, - NamespaceID: n.relNS.ID, - Paging: filter.Paging{Limit: 1}, - }) - if err != nil && err != store.ErrNotFound { - return err - } - n.missing = len(rr) == 0 } } @@ -105,6 +94,17 @@ func (n *composeRecordState) Prepare(ctx context.Context, s store.Storer, state return nil } + // Check if empty + rr, _, err := store.SearchComposeRecords(ctx, s, n.relMod, types.RecordFilter{ + ModuleID: n.relMod.ID, + NamespaceID: n.relNS.ID, + Paging: filter.Paging{Limit: 1}, + }) + if err != nil && err != store.ErrNotFound { + return err + } + n.missing = len(rr) == 0 + // Try to get existing records // // @todo handle large amounts of diff --git a/pkg/envoy/store/encoder.go b/pkg/envoy/store/encoder.go index 0c4f8ee40..5ed105fc3 100644 --- a/pkg/envoy/store/encoder.go +++ b/pkg/envoy/store/encoder.go @@ -11,17 +11,6 @@ import ( "github.com/cortezaproject/corteza-server/store" ) -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 @@ -33,11 +22,12 @@ type ( state map[resource.Interface]resourceState } - mergeAlg int - // EncoderConfig allows us to configure the resource encoding process EncoderConfig struct { - OnExisting mergeAlg + // OnExisting defines what to do if the resource exists + OnExisting resource.MergeAlg + // Skip if defines a pkg/expr expression when to skip the resource + SkipIf string // Defer is called after the resource is encoded, regardles of the result Defer func() // DeferOk is called after the resource is encoded, only when successful @@ -67,7 +57,7 @@ var ( func NewStoreEncoder(s store.Storer, cfg *EncoderConfig) envoy.PrepareEncoder { if cfg == nil { cfg = &EncoderConfig{ - OnExisting: Skip, + OnExisting: resource.Skip, } } diff --git a/pkg/envoy/store/messaging_channel.go b/pkg/envoy/store/messaging_channel.go index 28cecbb43..bdce089b0 100644 --- a/pkg/envoy/store/messaging_channel.go +++ b/pkg/envoy/store/messaging_channel.go @@ -24,7 +24,7 @@ type ( func NewMessagingChannelState(res *resource.MessagingChannel, cfg *EncoderConfig) resourceState { return &messagingChannelState{ - cfg: cfg, + cfg: mergeConfig(cfg, res.Config()), res: res, } } @@ -108,6 +108,14 @@ func (n *messagingChannelState) Encode(ctx context.Context, s store.Storer, stat } } + // Evaluate the resource skip expression + // @todo expand available parameters; similar implementation to compose/types/record@Dict + if skip, err := basicSkipEval(ctx, n.cfg, !exists); err != nil { + return err + } else if skip { + return nil + } + // Create fresh messagingChannel if !exists { return store.CreateMessagingChannel(ctx, s, res) @@ -115,13 +123,13 @@ func (n *messagingChannelState) Encode(ctx context.Context, s store.Storer, stat // Update existing messagingChannel switch n.cfg.OnExisting { - case Skip: + case resource.Skip: return nil - case MergeLeft: + case resource.MergeLeft: res = mergeMessagingChannels(n.ch, res) - case MergeRight: + case resource.MergeRight: res = mergeMessagingChannels(res, n.ch) } diff --git a/pkg/envoy/store/rbac_rule.go b/pkg/envoy/store/rbac_rule.go index 9a4f1d8b9..5018effe5 100644 --- a/pkg/envoy/store/rbac_rule.go +++ b/pkg/envoy/store/rbac_rule.go @@ -30,7 +30,7 @@ var ( func NewRbacRuleState(res *resource.RbacRule, cfg *EncoderConfig) resourceState { return &rbacRuleState{ - cfg: cfg, + cfg: mergeConfig(cfg, res.Config()), res: res, } @@ -97,8 +97,8 @@ func (n *rbacRuleState) Encode(ctx context.Context, s store.Storer, state *envoy // There isn't anything to merge really, so Skip & MergeLeft skip it; // Replace & MergeRight replace it. switch n.cfg.OnExisting { - case Skip, - MergeLeft: + case resource.Skip, + resource.MergeLeft: return nil } diff --git a/pkg/envoy/store/role.go b/pkg/envoy/store/role.go index ded54b238..366f9fe8a 100644 --- a/pkg/envoy/store/role.go +++ b/pkg/envoy/store/role.go @@ -22,7 +22,7 @@ type ( func NewRole(res *resource.Role, cfg *EncoderConfig) resourceState { return &roleState{ - cfg: cfg, + cfg: mergeConfig(cfg, res.Config()), res: res, } @@ -82,6 +82,14 @@ func (n *roleState) Encode(ctx context.Context, s store.Storer, state *envoy.Res } } + // Evaluate the resource skip expression + // @todo expand available parameters; similar implementation to compose/types/record@Dict + if skip, err := basicSkipEval(ctx, n.cfg, !exists); err != nil { + return err + } else if skip { + return nil + } + // Create a fresh role if !exists { return store.CreateRole(ctx, s, rl) @@ -89,13 +97,13 @@ func (n *roleState) Encode(ctx context.Context, s store.Storer, state *envoy.Res // Update existing roles switch n.cfg.OnExisting { - case Skip: + case resource.Skip: return nil - case MergeLeft: + case resource.MergeLeft: rl = mergeRole(n.rl, rl) - case MergeRight: + case resource.MergeRight: rl = mergeRole(rl, n.rl) } diff --git a/pkg/envoy/store/settings.go b/pkg/envoy/store/settings.go index 675c3dab9..2da954812 100644 --- a/pkg/envoy/store/settings.go +++ b/pkg/envoy/store/settings.go @@ -26,7 +26,7 @@ var ( func NewSettingsState(res *resource.Settings, cfg *EncoderConfig) resourceState { return &settingsState{ - cfg: cfg, + cfg: mergeConfig(cfg, res.Config()), res: res.Res, } @@ -68,12 +68,12 @@ func (n *settingsState) Encode(ctx context.Context, s store.Storer, state *envoy if os != nil { // Update existing setting switch n.cfg.OnExisting { - case Skip, - MergeLeft: + case resource.Skip, + resource.MergeLeft: ss = append(ss, os) - case Replace, - MergeRight: + case resource.Replace, + resource.MergeRight: ss = append(ss, ns) } } else { diff --git a/pkg/envoy/store/user.go b/pkg/envoy/store/user.go index 8747cef3f..621e26475 100644 --- a/pkg/envoy/store/user.go +++ b/pkg/envoy/store/user.go @@ -22,7 +22,7 @@ type ( func NewUserState(res *resource.User, cfg *EncoderConfig) resourceState { return &userState{ - cfg: cfg, + cfg: mergeConfig(cfg, res.Config()), res: res, } @@ -84,6 +84,14 @@ func (n *userState) Encode(ctx context.Context, s store.Storer, state *envoy.Res } } + // Evaluate the resource skip expression + // @todo expand available parameters; similar implementation to compose/types/record@Dict + if skip, err := basicSkipEval(ctx, n.cfg, !exists); err != nil { + return err + } else if skip { + return nil + } + // Create a fresh user if !exists { return store.CreateUser(ctx, s, res) @@ -91,13 +99,13 @@ func (n *userState) Encode(ctx context.Context, s store.Storer, state *envoy.Res // Update existing user switch n.cfg.OnExisting { - case Skip: + case resource.Skip: return nil - case MergeLeft: + case resource.MergeLeft: res = mergeUsers(n.u, res) - case MergeRight: + case resource.MergeRight: res = mergeUsers(res, n.u) } diff --git a/pkg/envoy/store/util.go b/pkg/envoy/store/util.go index c3ab5c2ab..06dd65ebf 100644 --- a/pkg/envoy/store/util.go +++ b/pkg/envoy/store/util.go @@ -105,3 +105,54 @@ func resolveUserRefs(ctx context.Context, s store.Storer, pr []resource.Interfac } return nil } + +func mergeConfig(ec *EncoderConfig, rs *resource.EnvoyConfig) *EncoderConfig { + // Nothing we can do + if rs == nil { + return ec + } + + // Take resource config as base + rr := &EncoderConfig{ + OnExisting: rs.OnExisting, + SkipIf: rs.SkipIf, + } + + // Default to store config + rr.Defer = ec.Defer + rr.DeferNok = ec.DeferNok + rr.DeferOk = ec.DeferOk + if rr.OnExisting == resource.Default { + rr.OnExisting = ec.OnExisting + } + if rr.SkipIf == "" { + rr.SkipIf = ec.SkipIf + } + + return rr +} + +func basicSkipEval(ctx context.Context, cfg *EncoderConfig, missing bool) (bool, error) { + if cfg == nil { + cfg = &EncoderConfig{} + } + + if cfg.SkipIf != "" { + evl, err := exprP.NewEvaluable(cfg.SkipIf) + if err != nil { + return false, err + } + // @todo expand this + skip, err := evl.EvalBool(ctx, map[string]interface{}{ + "missing": missing, + }) + if err != nil { + return false, err + } + + return skip, nil + } + + // Don't skip by default + return false, nil +} diff --git a/pkg/envoy/yaml/applications.go b/pkg/envoy/yaml/applications.go index e10feb41d..70a48d690 100644 --- a/pkg/envoy/yaml/applications.go +++ b/pkg/envoy/yaml/applications.go @@ -10,8 +10,9 @@ import ( type ( application struct { // when application is at least partially defined - res *types.Application `yaml:",inline"` - ts *resource.Timestamps + res *types.Application `yaml:",inline"` + ts *resource.Timestamps + config *resource.EnvoyConfig // module's RBAC rules rbac rbacRuleSet @@ -78,6 +79,10 @@ func (wrap *application) UnmarshalYAML(n *yaml.Node) (err error) { if wrap.res == nil { wrap.res = &types.Application{} } + if wrap.config, err = decodeEnvoyConfig(n); err != nil { + return + } + if wrap.ts, err = decodeTimestamps(n); err != nil { return } @@ -96,6 +101,7 @@ func (wrap *application) UnmarshalYAML(n *yaml.Node) (err error) { func (wrap application) MarshalEnvoy() ([]resource.Interface, error) { rs := resource.NewApplication(wrap.res) rs.SetTimestamps(wrap.ts) + rs.SetConfig(wrap.config) return envoy.CollectNodes( rs, diff --git a/pkg/envoy/yaml/compose_chart.go b/pkg/envoy/yaml/compose_chart.go index 18c7b94a7..820dc1414 100644 --- a/pkg/envoy/yaml/compose_chart.go +++ b/pkg/envoy/yaml/compose_chart.go @@ -11,8 +11,10 @@ import ( type ( composeChart struct { - res *types.Chart - ts *resource.Timestamps + res *types.Chart + ts *resource.Timestamps + config *resource.EnvoyConfig + refNamespace string // pointer to report and module reference @@ -98,6 +100,10 @@ func (wrap *composeChart) UnmarshalYAML(n *yaml.Node) (err error) { return } + if wrap.config, err = decodeEnvoyConfig(n); err != nil { + return + } + if wrap.ts, err = decodeTimestamps(n); err != nil { return } @@ -135,6 +141,7 @@ func (wrap composeChart) MarshalEnvoy() ([]resource.Interface, error) { } rs := resource.NewComposeChart(wrap.res, wrap.refNamespace, vv) rs.SetTimestamps(wrap.ts) + rs.SetConfig(wrap.config) return envoy.CollectNodes( rs, wrap.rbac.bindResource(rs), diff --git a/pkg/envoy/yaml/compose_module.go b/pkg/envoy/yaml/compose_module.go index de3488bc4..5a6cdecf6 100644 --- a/pkg/envoy/yaml/compose_module.go +++ b/pkg/envoy/yaml/compose_module.go @@ -24,8 +24,10 @@ type ( } composeModule struct { - res *types.Module - ts *resource.Timestamps + res *types.Module + ts *resource.Timestamps + config *resource.EnvoyConfig + refNamespace string rbac rbacRuleSet @@ -108,6 +110,10 @@ func (wrap *composeModule) UnmarshalYAML(n *yaml.Node) (err error) { return } + if wrap.config, err = decodeEnvoyConfig(n); err != nil { + return + } + if wrap.ts, err = decodeTimestamps(n); err != nil { return } @@ -152,6 +158,7 @@ func (wrap *composeModule) UnmarshalYAML(n *yaml.Node) (err error) { func (wrap composeModule) MarshalEnvoy() ([]resource.Interface, error) { rs := resource.NewComposeModule(wrap.res, wrap.refNamespace) rs.SetTimestamps(wrap.ts) + rs.SetConfig(wrap.config) var crs *resource.ComposeRecordTemplate if wrap.recTpl != nil { diff --git a/pkg/envoy/yaml/compose_namespace.go b/pkg/envoy/yaml/compose_namespace.go index 6bdfea45d..691419053 100644 --- a/pkg/envoy/yaml/compose_namespace.go +++ b/pkg/envoy/yaml/compose_namespace.go @@ -11,8 +11,9 @@ import ( type ( composeNamespace struct { // when namespace is at least partially defined - res *types.Namespace `yaml:",inline"` - ts *resource.Timestamps + res *types.Namespace `yaml:",inline"` + ts *resource.Timestamps + config *resource.EnvoyConfig // all known modules on a namespace modules composeModuleSet @@ -104,6 +105,10 @@ func (wrap *composeNamespace) UnmarshalYAML(n *yaml.Node) (err error) { return } + if wrap.config, err = decodeEnvoyConfig(n); err != nil { + return + } + if wrap.ts, err = decodeTimestamps(n); err != nil { return } @@ -131,6 +136,7 @@ func (wrap *composeNamespace) UnmarshalYAML(n *yaml.Node) (err error) { func (wrap composeNamespace) MarshalEnvoy() ([]resource.Interface, error) { nsr := resource.NewComposeNamespace(wrap.res) nsr.SetTimestamps(wrap.ts) + nsr.SetConfig(wrap.config) return envoy.CollectNodes( nsr, diff --git a/pkg/envoy/yaml/compose_page.go b/pkg/envoy/yaml/compose_page.go index 0321a29a0..f85680012 100644 --- a/pkg/envoy/yaml/compose_page.go +++ b/pkg/envoy/yaml/compose_page.go @@ -11,8 +11,10 @@ import ( type ( composePage struct { - res *types.Page - ts *resource.Timestamps + res *types.Page + ts *resource.Timestamps + config *resource.EnvoyConfig + children composePageSet refNamespace string @@ -110,6 +112,10 @@ func (wrap *composePage) UnmarshalYAML(n *yaml.Node) (err error) { return } + if wrap.config, err = decodeEnvoyConfig(n); err != nil { + return + } + if wrap.ts, err = decodeTimestamps(n); err != nil { return } @@ -155,14 +161,12 @@ func (wrap *composePage) UnmarshalYAML(n *yaml.Node) (err error) { func (wrap composePage) MarshalEnvoy() ([]resource.Interface, error) { rs := resource.NewComposePage(wrap.res, wrap.refNamespace, wrap.refModule, wrap.refParent) rs.SetTimestamps(wrap.ts) + rs.SetConfig(wrap.config) return envoy.CollectNodes( rs, wrap.children.bindParent(rs), wrap.rbac.bindResource(rs), - - // @todo Not sure yet - // wrap.children, ) } diff --git a/pkg/envoy/yaml/compose_record.go b/pkg/envoy/yaml/compose_record.go index a9ac65599..27ac8331b 100644 --- a/pkg/envoy/yaml/compose_record.go +++ b/pkg/envoy/yaml/compose_record.go @@ -13,8 +13,7 @@ type ( values map[string]string ts *resource.Timestamps us *resource.Userstamps - - eCfg *resource.EnvoyConfig + config *resource.EnvoyConfig refModule string refNamespace string @@ -99,7 +98,7 @@ func (wset composeRecordSet) MarshalEnvoy() ([]resource.Interface, error) { r := &resource.ComposeRecordRaw{ // @todo change this probably ID: res.values["id"], - Config: res.eCfg, + Config: res.config, Values: res.values, Ts: res.ts, Us: res.us, @@ -160,7 +159,7 @@ func (wrap *composeRecord) UnmarshalYAML(n *yaml.Node) (err error) { // return //} - if wrap.eCfg, err = decodeEnvoyConfig(n); err != nil { + if wrap.config, err = decodeEnvoyConfig(n); err != nil { return } diff --git a/pkg/envoy/yaml/messaging_channel.go b/pkg/envoy/yaml/messaging_channel.go index dddd24402..b054efc11 100644 --- a/pkg/envoy/yaml/messaging_channel.go +++ b/pkg/envoy/yaml/messaging_channel.go @@ -10,9 +10,10 @@ import ( type ( messagingChannel struct { // when messagingChannel is at least partially defined - res *types.Channel `yaml:",inline"` - ts *resource.Timestamps - us *resource.Userstamps + res *types.Channel `yaml:",inline"` + ts *resource.Timestamps + us *resource.Userstamps + config *resource.EnvoyConfig // module's RBAC rules rbac rbacRuleSet @@ -78,6 +79,10 @@ func (wrap *messagingChannel) UnmarshalYAML(n *yaml.Node) (err error) { return } + if wrap.config, err = decodeEnvoyConfig(n); err != nil { + return + } + if wrap.ts, err = decodeTimestamps(n); err != nil { return } @@ -92,6 +97,7 @@ func (wrap messagingChannel) MarshalEnvoy() ([]resource.Interface, error) { rs := resource.NewMessagingChannel(wrap.res) rs.SetTimestamps(wrap.ts) rs.SetUserstamps(wrap.us) + rs.SetConfig(wrap.config) return envoy.CollectNodes( rs, wrap.rbac.bindResource(rs), diff --git a/pkg/envoy/yaml/resource.go b/pkg/envoy/yaml/resource.go index ccc4d674d..7c8e31299 100644 --- a/pkg/envoy/yaml/resource.go +++ b/pkg/envoy/yaml/resource.go @@ -1,6 +1,8 @@ package yaml import ( + "strings" + "github.com/cortezaproject/corteza-server/pkg/envoy/resource" "gopkg.in/yaml.v3" ) @@ -24,8 +26,41 @@ func decodeEnvoyConfig(n *yaml.Node) (*resource.EnvoyConfig, error) { switch k.Value { case "skipIf", "skip": return decodeScalar(v, "decode skip if", &ec.SkipIf) + case "onExisting", "mergeAlg": + return decodeMergeAlg(v, "decode merge alg", &ec.OnExisting) } return nil }) } + +func decodeMergeAlg(n *yaml.Node, refType string, val *resource.MergeAlg) error { + if n == nil { + return nil + } + + if !isKind(n, yaml.ScalarNode) { + return nodeErr(n, "%s reference must be scalar", refType) + } + + switch strings.ToLower(n.Value) { + case "skip", + "s": + *val = resource.Skip + case "replace", + "r": + *val = resource.Replace + case "mergeleft", + "left", + "ml": + *val = resource.MergeLeft + case "mergeright", + "right", + "mr": + *val = resource.MergeRight + default: + return nodeErr(n, "%s unknown algorithm", refType) + } + + return nil +} diff --git a/pkg/envoy/yaml/role.go b/pkg/envoy/yaml/role.go index 71aea4652..34efd088f 100644 --- a/pkg/envoy/yaml/role.go +++ b/pkg/envoy/yaml/role.go @@ -10,8 +10,9 @@ import ( type ( role struct { // when role is at least partially defined - res *types.Role - ts *resource.Timestamps + res *types.Role + ts *resource.Timestamps + config *resource.EnvoyConfig // all known modules on a role modules composeModuleSet @@ -94,6 +95,10 @@ func (wrap *role) UnmarshalYAML(n *yaml.Node) (err error) { return } + if wrap.config, err = decodeEnvoyConfig(n); err != nil { + return + } + if wrap.ts, err = decodeTimestamps(n); err != nil { return } @@ -104,6 +109,7 @@ func (wrap *role) UnmarshalYAML(n *yaml.Node) (err error) { func (wrap role) MarshalEnvoy() ([]resource.Interface, error) { rs := resource.NewRole(wrap.res) rs.SetTimestamps(wrap.ts) + rs.SetConfig(wrap.config) return envoy.CollectNodes( rs, diff --git a/pkg/envoy/yaml/settings.go b/pkg/envoy/yaml/settings.go index de5de7455..c17dbb0b7 100644 --- a/pkg/envoy/yaml/settings.go +++ b/pkg/envoy/yaml/settings.go @@ -7,9 +7,11 @@ import ( type ( settings struct { - res map[string]interface{} - ts *resource.Timestamps - us *resource.Userstamps + res map[string]interface{} + ts *resource.Timestamps + config *resource.EnvoyConfig + + us *resource.Userstamps } ) @@ -26,6 +28,10 @@ func (wrap *settings) UnmarshalYAML(n *yaml.Node) (err error) { return } + if wrap.config, err = decodeEnvoyConfig(n); err != nil { + return + } + if wrap.ts, err = decodeTimestamps(n); err != nil { return } @@ -39,6 +45,7 @@ func (wrap settings) MarshalEnvoy() (nn []resource.Interface, err error) { n := resource.NewSettings(wrap.res) n.SetTimestamps(wrap.ts) n.SetUserstamps(wrap.us) + n.SetConfig(wrap.config) return []resource.Interface{n}, nil } diff --git a/pkg/envoy/yaml/user.go b/pkg/envoy/yaml/user.go index 1e1a1ac74..d2f6b465e 100644 --- a/pkg/envoy/yaml/user.go +++ b/pkg/envoy/yaml/user.go @@ -10,8 +10,9 @@ import ( type ( user struct { // when user is at least partially defined - res *types.User `yaml:",inline"` - ts *resource.Timestamps + res *types.User `yaml:",inline"` + ts *resource.Timestamps + config *resource.EnvoyConfig // module's RBAC rules rbac rbacRuleSet @@ -95,6 +96,10 @@ func (wrap *user) UnmarshalYAML(n *yaml.Node) (err error) { return } + if wrap.config, err = decodeEnvoyConfig(n); err != nil { + return + } + if wrap.ts, err = decodeTimestamps(n); err != nil { return } @@ -105,6 +110,7 @@ func (wrap *user) UnmarshalYAML(n *yaml.Node) (err error) { func (wrap user) MarshalEnvoy() ([]resource.Interface, error) { rs := resource.NewUser(wrap.res) rs.SetTimestamps(wrap.ts) + rs.SetConfig(wrap.config) return envoy.CollectNodes( rs, diff --git a/pkg/provision/config.go b/pkg/provision/config.go index 2e533c96b..eaa43b2e1 100644 --- a/pkg/provision/config.go +++ b/pkg/provision/config.go @@ -2,6 +2,9 @@ package provision import ( "context" + "path/filepath" + "strings" + "github.com/cortezaproject/corteza-server/pkg/envoy" "github.com/cortezaproject/corteza-server/pkg/envoy/directory" "github.com/cortezaproject/corteza-server/pkg/envoy/resource" @@ -9,8 +12,6 @@ import ( "github.com/cortezaproject/corteza-server/pkg/envoy/yaml" "github.com/cortezaproject/corteza-server/store" "go.uber.org/zap" - "path/filepath" - "strings" ) // imports configuration files from path(s) @@ -20,7 +21,7 @@ func importConfig(ctx context.Context, log *zap.Logger, s store.Storer, paths st var ( yd = yaml.Decoder() nn = make([]resource.Interface, 0, 200) - se = es.NewStoreEncoder(s, &es.EncoderConfig{OnExisting: es.Skip}) + se = es.NewStoreEncoder(s, &es.EncoderConfig{OnExisting: resource.MergeLeft}) bld = envoy.NewBuilder(se) sources = make([]string, 0, 16) diff --git a/system/commands/importer.go b/system/commands/importer.go index 34964dbdc..66f4fc857 100644 --- a/system/commands/importer.go +++ b/system/commands/importer.go @@ -55,17 +55,17 @@ func Import(storeInit func(ctx context.Context) (store.Storer, error)) *cobra.Co } opt := &es.EncoderConfig{ - OnExisting: es.Skip, + OnExisting: resource.Skip, } if replaceOnExisting { - opt.OnExisting = es.Replace + opt.OnExisting = resource.Replace } if mergeLeftOnExisting { - opt.OnExisting = es.MergeLeft + opt.OnExisting = resource.MergeLeft } if mergeRightOnExisting { - opt.OnExisting = es.MergeRight + opt.OnExisting = resource.MergeRight } se := es.NewStoreEncoder(s, opt) diff --git a/tests/envoy/provision_overwrite_test.go b/tests/envoy/provision_overwrite_test.go index 12448f71a..8aa6e8765 100644 --- a/tests/envoy/provision_overwrite_test.go +++ b/tests/envoy/provision_overwrite_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/cortezaproject/corteza-server/pkg/envoy/resource" su "github.com/cortezaproject/corteza-server/pkg/envoy/store" "github.com/cortezaproject/corteza-server/store" "github.com/stretchr/testify/require" @@ -65,22 +66,22 @@ func TestProvision_overwriting(t *testing.T) { req.NoError(err) store.TruncateComposeRecords(ctx, s, nil) - req, err = prepare(ctx, s, t, "provision_batch/app_1", &su.EncoderConfig{OnExisting: su.Replace}) + req, err = prepare(ctx, s, t, "provision_batch/app_1", &su.EncoderConfig{OnExisting: resource.Replace}) req.NoError(err) checkBatchProvision(ctx, t, req, s, "ns1") store.TruncateComposeRecords(ctx, s, nil) - req, err = prepare(ctx, s, t, "provision_batch/app_1", &su.EncoderConfig{OnExisting: su.Skip}) + req, err = prepare(ctx, s, t, "provision_batch/app_1", &su.EncoderConfig{OnExisting: resource.Skip}) req.NoError(err) checkBatchProvision(ctx, t, req, s, "ns1") store.TruncateComposeRecords(ctx, s, nil) - req, err = prepare(ctx, s, t, "provision_batch/app_1", &su.EncoderConfig{OnExisting: su.MergeLeft}) + req, err = prepare(ctx, s, t, "provision_batch/app_1", &su.EncoderConfig{OnExisting: resource.MergeLeft}) req.NoError(err) checkBatchProvision(ctx, t, req, s, "ns1") store.TruncateComposeRecords(ctx, s, nil) - req, err = prepare(ctx, s, t, "provision_batch/app_1", &su.EncoderConfig{OnExisting: su.MergeRight}) + req, err = prepare(ctx, s, t, "provision_batch/app_1", &su.EncoderConfig{OnExisting: resource.MergeRight}) req.NoError(err) checkBatchProvision(ctx, t, req, s, "ns1") store.TruncateComposeRecords(ctx, s, nil) diff --git a/tests/envoy/simple_test.go b/tests/envoy/simple_test.go index 9b08ccbdd..a694f0d02 100644 --- a/tests/envoy/simple_test.go +++ b/tests/envoy/simple_test.go @@ -113,6 +113,44 @@ func TestSimpleCases(t *testing.T) { }, }, + { + name: "simple mods; conditional", + suite: "simple", + file: "modules_conditional", + pre: func() (err error) { + return ce( + s.TruncateComposeRecords(ctx, nil), + s.TruncateComposeModuleFields(ctx), + s.TruncateComposeModules(ctx), + s.TruncateComposeNamespaces(ctx), + + storeNamespace(ctx, s, 100, "ns1"), + + storeModule(ctx, s, 100, 200, "mod1", "mod1 name"), + storeModuleField(ctx, s, 200, 300, "f1"), + + storeModule(ctx, s, 100, 201, "mod2", "mod2 name"), + storeModuleField(ctx, s, 201, 301, "f1"), + ) + }, + post: func(req *require.Assertions, err error) { + req.NoError(err) + }, + check: func(req *require.Assertions) { + mod1, err := store.LookupComposeModuleByID(ctx, s, 200) + req.NoError(err) + req.NotNil(mod1) + + mod2, err := store.LookupComposeModuleByID(ctx, s, 201) + req.NoError(err) + req.NotNil(mod2) + + // The first one overwrites merge alg to replace, the second one defaults to skip + req.Equal("mod1 name (EDITED)", mod1.Name) + req.Equal("mod2 name", mod2.Name) + }, + }, + { name: "simple charts; no ns", suite: "simple", diff --git a/tests/envoy/testdata/simple/modules_conditional.yaml b/tests/envoy/testdata/simple/modules_conditional.yaml new file mode 100644 index 000000000..71d0ce5ac --- /dev/null +++ b/tests/envoy/testdata/simple/modules_conditional.yaml @@ -0,0 +1,17 @@ +namespace: ns1 +modules: + mod1: + (envoy): { onExisting: "replace" } + name: mod1 name (EDITED) + fields: + f1: + label: f1 label + kind: String + required: true + mod2: + name: mod2 name (EDITED) + fields: + f1: + label: f1 label + kind: String + required: true