Add template provisioning
This commit is contained in:
parent
c4995dd8eb
commit
1d5bedc2be
65
pkg/envoy/resource/template.go
Normal file
65
pkg/envoy/resource/template.go
Normal file
@ -0,0 +1,65 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
|
||||
type (
|
||||
// Template represents a Template
|
||||
Template struct {
|
||||
*base
|
||||
Res *types.Template
|
||||
}
|
||||
)
|
||||
|
||||
func NewTemplate(t *types.Template) *Template {
|
||||
r := &Template{base: &base{}}
|
||||
r.SetResourceType(TEMPLATE_RESOURCE_TYPE)
|
||||
r.Res = t
|
||||
|
||||
r.AddIdentifier(identifiers(t.Handle, t.Meta.Short, t.ID)...)
|
||||
|
||||
// Initial timestamps
|
||||
r.SetTimestamps(MakeCUDASTimestamps(&t.CreatedAt, t.UpdatedAt, t.DeletedAt, nil, nil))
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Template) SysID() uint64 {
|
||||
return r.Res.ID
|
||||
}
|
||||
|
||||
func (r *Template) Ref() string {
|
||||
return FirstOkString(r.Res.Handle, r.Res.Meta.Short, strconv.FormatUint(r.Res.ID, 10))
|
||||
}
|
||||
|
||||
// FindTemplate looks for the template in the resources
|
||||
func FindTemplate(rr InterfaceSet, ii Identifiers) (u *types.Template) {
|
||||
var tRes *Template
|
||||
|
||||
rr.Walk(func(r Interface) error {
|
||||
tr, ok := r.(*Template)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if tr.Identifiers().HasAny(ii) {
|
||||
tRes = tr
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// Found it
|
||||
if tRes != nil {
|
||||
return tRes.Res
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TemplateErrUnresolved(ii Identifiers) error {
|
||||
return fmt.Errorf("template unresolved %v", ii.StringSlice())
|
||||
}
|
||||
@ -49,6 +49,7 @@ var (
|
||||
ROLE_RESOURCE_TYPE = st.RoleRBACResource.String()
|
||||
SETTINGS_RESOURCE_TYPE = "system:setting:"
|
||||
USER_RESOURCE_TYPE = st.UserRBACResource.String()
|
||||
TEMPLATE_RESOURCE_TYPE = st.TemplateRBACResource.String()
|
||||
DATA_SOURCE_RESOURCE_TYPE = "data:raw:"
|
||||
)
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@ type (
|
||||
// System stuff
|
||||
roles []*roleFilter
|
||||
users []*userFilter
|
||||
templates []*templateFilter
|
||||
applications []*applicationFilter
|
||||
settings []*settingFilter
|
||||
rbac []*rbacFilter
|
||||
@ -87,6 +88,7 @@ func (d *decoder) Decode(ctx context.Context, s store.Storer, f *DecodeFilter) (
|
||||
|
||||
system.decodeRoles(ctx, s, f.roles),
|
||||
system.decodeUsers(ctx, s, f.users),
|
||||
system.decodeTemplates(ctx, s, f.templates),
|
||||
system.decodeApplications(ctx, s, f.applications),
|
||||
system.decodeSettings(ctx, s, f.settings),
|
||||
)
|
||||
|
||||
@ -132,6 +132,8 @@ func (se *storeEncoder) Prepare(ctx context.Context, ee ...*envoy.ResourceState)
|
||||
// System resources
|
||||
case *resource.User:
|
||||
err = f(NewUserFromResource(res, se.cfg), ers)
|
||||
case *resource.Template:
|
||||
err = f(NewTemplateFromResource(res, se.cfg), ers)
|
||||
case *resource.Role:
|
||||
err = f(NewRoleFromResource(res, se.cfg), ers)
|
||||
case *resource.Application:
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
type (
|
||||
roleFilter types.RoleFilter
|
||||
userFilter types.UserFilter
|
||||
templateFilter types.TemplateFilter
|
||||
applicationFilter types.ApplicationFilter
|
||||
settingFilter types.SettingsFilter
|
||||
rbacFilter struct {
|
||||
@ -28,6 +29,7 @@ type (
|
||||
store.Roles
|
||||
store.Settings
|
||||
store.Users
|
||||
store.Templates
|
||||
}
|
||||
|
||||
systemDecoder struct {
|
||||
@ -130,6 +132,52 @@ func (d *systemDecoder) decodeUsers(ctx context.Context, s systemStore, ff []*us
|
||||
mm: mm,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *systemDecoder) decodeTemplates(ctx context.Context, s systemStore, ff []*templateFilter) *auxRsp {
|
||||
mm := make([]envoy.Marshaller, 0, 100)
|
||||
if ff == nil {
|
||||
return &auxRsp{
|
||||
mm: mm,
|
||||
}
|
||||
}
|
||||
|
||||
var nn types.TemplateSet
|
||||
var fn types.TemplateFilter
|
||||
var err error
|
||||
|
||||
for _, f := range ff {
|
||||
aux := *f
|
||||
|
||||
if aux.Limit == 0 {
|
||||
aux.Limit = 1000
|
||||
}
|
||||
|
||||
for {
|
||||
nn, fn, err = s.SearchTemplates(ctx, types.TemplateFilter(aux))
|
||||
if err != nil {
|
||||
return &auxRsp{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
for _, n := range nn {
|
||||
mm = append(mm, newTemplate(n))
|
||||
d.resourceID = append(d.resourceID, n.ID)
|
||||
}
|
||||
|
||||
if fn.NextPage != nil {
|
||||
aux.PageCursor = fn.NextPage
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &auxRsp{
|
||||
mm: mm,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *systemDecoder) decodeApplications(ctx context.Context, s systemStore, ff []*applicationFilter) *auxRsp {
|
||||
mm := make([]envoy.Marshaller, 0, 100)
|
||||
if ff == nil {
|
||||
@ -276,6 +324,15 @@ func (df *DecodeFilter) Users(f *types.UserFilter) *DecodeFilter {
|
||||
return df
|
||||
}
|
||||
|
||||
// Templates adds a new TemplateFilter
|
||||
func (df *DecodeFilter) Templates(f *types.TemplateFilter) *DecodeFilter {
|
||||
if df.templates == nil {
|
||||
df.templates = make([]*templateFilter, 0, 1)
|
||||
}
|
||||
df.templates = append(df.templates, (*templateFilter)(f))
|
||||
return df
|
||||
}
|
||||
|
||||
// Applications adds a new ApplicationFilter
|
||||
func (df *DecodeFilter) Applications(f *types.ApplicationFilter) *DecodeFilter {
|
||||
if df.applications == nil {
|
||||
|
||||
98
pkg/envoy/store/template.go
Normal file
98
pkg/envoy/store/template.go
Normal file
@ -0,0 +1,98 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
|
||||
type (
|
||||
template struct {
|
||||
cfg *EncoderConfig
|
||||
|
||||
res *resource.Template
|
||||
t *types.Template
|
||||
}
|
||||
)
|
||||
|
||||
// mergeTemplates merges b into a, prioritising a
|
||||
func mergeTemplates(a, b *types.Template) *types.Template {
|
||||
c := *a
|
||||
|
||||
if c.Handle == "" {
|
||||
c.Handle = b.Handle
|
||||
}
|
||||
if c.Language == "" {
|
||||
c.Language = b.Language
|
||||
}
|
||||
if c.Type == "" {
|
||||
c.Type = b.Type
|
||||
}
|
||||
if c.Meta.Short == "" {
|
||||
c.Meta.Short = b.Meta.Short
|
||||
}
|
||||
if c.Meta.Description == "" {
|
||||
c.Meta.Description = b.Meta.Description
|
||||
}
|
||||
if c.Template == "" {
|
||||
c.Template = b.Template
|
||||
}
|
||||
|
||||
if c.OwnerID == 0 {
|
||||
c.OwnerID = b.OwnerID
|
||||
}
|
||||
|
||||
if c.CreatedAt.IsZero() {
|
||||
c.CreatedAt = b.CreatedAt
|
||||
}
|
||||
if c.UpdatedAt == nil {
|
||||
c.UpdatedAt = b.UpdatedAt
|
||||
}
|
||||
if c.DeletedAt == nil {
|
||||
c.DeletedAt = b.DeletedAt
|
||||
}
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
// findTemplateRS looks for the template in the resources & the store
|
||||
//
|
||||
// Provided resources are prioritized.
|
||||
func findTemplateRS(ctx context.Context, s store.Storer, rr resource.InterfaceSet, ii resource.Identifiers) (u *types.Template, err error) {
|
||||
u = resource.FindTemplate(rr, ii)
|
||||
if u != nil {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
return findTemplateS(ctx, s, makeGenericFilter(ii))
|
||||
}
|
||||
|
||||
// findTemplateS looks for the template in the store
|
||||
func findTemplateS(ctx context.Context, s store.Storer, gf genericFilter) (t *types.Template, err error) {
|
||||
if gf.id > 0 {
|
||||
t, err = store.LookupTemplateByID(ctx, s, gf.id)
|
||||
if err != nil && err != store.ErrNotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if t != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, i := range gf.identifiers {
|
||||
// Handle & templatename
|
||||
t, err = store.LookupTemplateByHandle(ctx, s, i)
|
||||
if err != nil && err != store.ErrNotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if t != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
106
pkg/envoy/store/template_marshal.go
Normal file
106
pkg/envoy/store/template_marshal.go
Normal file
@ -0,0 +1,106 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
)
|
||||
|
||||
func NewTemplateFromResource(res *resource.Template, cfg *EncoderConfig) resourceState {
|
||||
return &template{
|
||||
cfg: mergeConfig(cfg, res.Config()),
|
||||
|
||||
res: res,
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare prepares the template to be encoded
|
||||
//
|
||||
// Any validation, additional constraining should be performed here.
|
||||
func (n *template) Prepare(ctx context.Context, pl *payload) (err error) {
|
||||
// Try to get the original template
|
||||
n.t, err = findTemplateS(ctx, pl.s, makeGenericFilter(n.res.Identifiers()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if n.t != nil {
|
||||
n.res.Res.ID = n.t.ID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode encodes the template to the store
|
||||
//
|
||||
// Encode is allowed to do some data manipulation, but no resource constraints
|
||||
// should be changed.
|
||||
func (n *template) Encode(ctx context.Context, pl *payload) (err error) {
|
||||
res := n.res.Res
|
||||
exists := n.t != nil && n.t.ID > 0
|
||||
|
||||
// Determine the ID
|
||||
if res.ID <= 0 && exists {
|
||||
res.ID = n.t.ID
|
||||
}
|
||||
if res.ID <= 0 {
|
||||
res.ID = NextID()
|
||||
}
|
||||
|
||||
// Timestamps
|
||||
ts := n.res.Timestamps()
|
||||
if ts != nil {
|
||||
if ts.CreatedAt != nil {
|
||||
res.CreatedAt = *ts.CreatedAt.T
|
||||
} else {
|
||||
res.CreatedAt = *now()
|
||||
}
|
||||
if ts.UpdatedAt != nil {
|
||||
res.UpdatedAt = ts.UpdatedAt.T
|
||||
}
|
||||
if ts.DeletedAt != nil {
|
||||
res.DeletedAt = ts.DeletedAt.T
|
||||
}
|
||||
}
|
||||
|
||||
// Userstamps
|
||||
us := n.res.Userstamps()
|
||||
if us != nil {
|
||||
if us.OwnedBy != nil {
|
||||
res.OwnerID = us.OwnedBy.UserID
|
||||
}
|
||||
}
|
||||
|
||||
// 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 template
|
||||
if !exists {
|
||||
return store.CreateTemplate(ctx, pl.s, res)
|
||||
}
|
||||
|
||||
// Update existing template
|
||||
switch n.cfg.OnExisting {
|
||||
case resource.Skip:
|
||||
return nil
|
||||
|
||||
case resource.MergeLeft:
|
||||
res = mergeTemplates(n.t, res)
|
||||
|
||||
case resource.MergeRight:
|
||||
res = mergeTemplates(res, n.t)
|
||||
}
|
||||
|
||||
err = store.UpdateTemplate(ctx, pl.s, res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.res.Res = res
|
||||
return nil
|
||||
}
|
||||
20
pkg/envoy/store/template_unmarshal.go
Normal file
20
pkg/envoy/store/template_unmarshal.go
Normal file
@ -0,0 +1,20 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza-server/pkg/envoy"
|
||||
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
|
||||
func newTemplate(t *types.Template) *template {
|
||||
return &template{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalEnvoy converts the template struct to a resource
|
||||
func (u *template) MarshalEnvoy() ([]resource.Interface, error) {
|
||||
return envoy.CollectNodes(
|
||||
resource.NewTemplate(u.t),
|
||||
)
|
||||
}
|
||||
@ -52,7 +52,10 @@ func (y *decoder) Decode(ctx context.Context, r io.Reader, dctx *envoy.DecoderOp
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return doc.Decode(ctx)
|
||||
aaa, err := doc.Decode(ctx)
|
||||
|
||||
return aaa, err
|
||||
|
||||
}
|
||||
|
||||
// Checks validity of ref node and sets the value to given arg ptr
|
||||
|
||||
@ -15,6 +15,7 @@ type (
|
||||
compose *compose
|
||||
roles roleSet
|
||||
users userSet
|
||||
templates templateSet
|
||||
applications applicationSet
|
||||
settings settingSet
|
||||
rbac rbacRuleSet
|
||||
@ -40,6 +41,9 @@ func (doc *Document) UnmarshalYAML(n *yaml.Node) (err error) {
|
||||
case "users":
|
||||
return v.Decode(&doc.users)
|
||||
|
||||
case "templates":
|
||||
return v.Decode(&doc.templates)
|
||||
|
||||
case "applications":
|
||||
return v.Decode(&doc.applications)
|
||||
|
||||
@ -85,6 +89,15 @@ func (doc *Document) MarshalYAML() (interface{}, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if doc.templates != nil && len(doc.templates) > 0 {
|
||||
doc.templates.ConfigureEncoder(doc.cfg)
|
||||
|
||||
dn, err = encodeResource(dn, "templates", doc.templates, doc.cfg.MappedOutput, "handle")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if doc.applications != nil && len(doc.applications) > 0 {
|
||||
doc.applications.ConfigureEncoder(doc.cfg)
|
||||
|
||||
@ -133,6 +146,9 @@ func (doc *Document) Decode(ctx context.Context) ([]resource.Interface, error) {
|
||||
if doc.users != nil {
|
||||
mm = append(mm, doc.users)
|
||||
}
|
||||
if doc.templates != nil {
|
||||
mm = append(mm, doc.templates)
|
||||
}
|
||||
if doc.applications != nil {
|
||||
mm = append(mm, doc.applications)
|
||||
}
|
||||
@ -236,6 +252,15 @@ func (doc *Document) AddUser(u *user) {
|
||||
doc.users = append(doc.users, u)
|
||||
}
|
||||
|
||||
// AddTemplate adds a new template to the document
|
||||
func (doc *Document) AddTemplate(u *template) {
|
||||
if doc.templates == nil {
|
||||
doc.templates = make(templateSet, 0, 20)
|
||||
}
|
||||
|
||||
doc.templates = append(doc.templates, u)
|
||||
}
|
||||
|
||||
// AddApplication adds a new application to the document
|
||||
func (doc *Document) AddApplication(a *application) {
|
||||
if doc.applications == nil {
|
||||
|
||||
@ -125,6 +125,8 @@ func (ye *yamlEncoder) Prepare(ctx context.Context, ee ...*envoy.ResourceState)
|
||||
err = f(roleFromResource(res, ye.cfg), e)
|
||||
case *resource.User:
|
||||
err = f(userFromResource(res, ye.cfg), e)
|
||||
case *resource.Template:
|
||||
err = f(templateFromResource(res, ye.cfg), e)
|
||||
case *resource.Application:
|
||||
err = f(applicationFromResource(res, ye.cfg), e)
|
||||
case *resource.Setting:
|
||||
|
||||
25
pkg/envoy/yaml/template.go
Normal file
25
pkg/envoy/yaml/template.go
Normal file
@ -0,0 +1,25 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
|
||||
type (
|
||||
template struct {
|
||||
res *types.Template
|
||||
ts *resource.Timestamps
|
||||
|
||||
envoyConfig *resource.EnvoyConfig
|
||||
encoderConfig *EncoderConfig
|
||||
|
||||
rbac rbacRuleSet
|
||||
}
|
||||
templateSet []*template
|
||||
)
|
||||
|
||||
func (nn templateSet) ConfigureEncoder(cfg *EncoderConfig) {
|
||||
for _, n := range nn {
|
||||
n.encoderConfig = cfg
|
||||
}
|
||||
}
|
||||
85
pkg/envoy/yaml/template_marshal.go
Normal file
85
pkg/envoy/yaml/template_marshal.go
Normal file
@ -0,0 +1,85 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/envoy"
|
||||
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
|
||||
"github.com/cortezaproject/corteza-server/pkg/envoy/util"
|
||||
)
|
||||
|
||||
func templateFromResource(r *resource.Template, cfg *EncoderConfig) *template {
|
||||
return &template{
|
||||
res: r.Res,
|
||||
encoderConfig: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare prepares the template to be encoded
|
||||
//
|
||||
// Any validation, additional constraining should be performed here.
|
||||
func (u *template) Prepare(ctx context.Context, state *envoy.ResourceState) (err error) {
|
||||
us, ok := state.Res.(*resource.Template)
|
||||
if !ok {
|
||||
return encoderErrInvalidResource(resource.TEMPLATE_RESOURCE_TYPE, state.Res.ResourceType())
|
||||
}
|
||||
|
||||
u.res = us.Res
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode encodes the template to the document
|
||||
//
|
||||
// Encode is allowed to do some data manipulation, but no resource constraints
|
||||
// should be changed.
|
||||
func (u *template) Encode(ctx context.Context, doc *Document, state *envoy.ResourceState) (err error) {
|
||||
if u.res.ID <= 0 {
|
||||
u.res.ID = util.NextID()
|
||||
}
|
||||
|
||||
// Encode timestamps
|
||||
u.ts, err = resource.MakeCUDASTimestamps(&u.res.CreatedAt, u.res.UpdatedAt, u.res.DeletedAt, nil, nil).
|
||||
Model(u.encoderConfig.TimeLayout, u.encoderConfig.Timezone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// @todo implement resource skipping?
|
||||
|
||||
doc.AddTemplate(u)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *template) MarshalYAML() (interface{}, error) {
|
||||
var err error
|
||||
|
||||
meta, err := makeMap(
|
||||
"short", c.res.Meta.Short,
|
||||
"description", c.res.Meta.Description,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nsn, err := makeMap(
|
||||
"handle", c.res.Handle,
|
||||
"language", c.res.Language,
|
||||
"type", c.res.Type,
|
||||
"partial", c.res.Partial,
|
||||
"template", c.res.Template,
|
||||
|
||||
"meta", meta,
|
||||
"Labels", c.res.Labels,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nsn, err = mapTimestamps(nsn, c.ts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nsn, nil
|
||||
}
|
||||
95
pkg/envoy/yaml/template_unmarshal.go
Normal file
95
pkg/envoy/yaml/template_unmarshal.go
Normal file
@ -0,0 +1,95 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza-server/pkg/envoy"
|
||||
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
|
||||
"github.com/cortezaproject/corteza-server/pkg/y7s"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func (wset *templateSet) UnmarshalYAML(n *yaml.Node) error {
|
||||
return y7s.Each(n, func(k, v *yaml.Node) (err error) {
|
||||
var (
|
||||
wrap = &template{}
|
||||
)
|
||||
|
||||
if v == nil {
|
||||
return y7s.NodeErr(n, "malformed template definition")
|
||||
}
|
||||
|
||||
wrap.res = &types.Template{}
|
||||
|
||||
switch v.Kind {
|
||||
case yaml.MappingNode:
|
||||
if err = v.Decode(&wrap); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
return y7s.NodeErr(n, "expecting scalar or map with template definitions")
|
||||
|
||||
}
|
||||
|
||||
if err = decodeRef(k, "template handle", &wrap.res.Handle); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*wset = append(*wset, wrap)
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
func (wset templateSet) MarshalEnvoy() ([]resource.Interface, error) {
|
||||
nn := make([]resource.Interface, 0, len(wset))
|
||||
|
||||
for _, res := range wset {
|
||||
if tmp, err := res.MarshalEnvoy(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
nn = append(nn, tmp...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nn, nil
|
||||
}
|
||||
|
||||
func (wrap *template) UnmarshalYAML(n *yaml.Node) (err error) {
|
||||
if !y7s.IsKind(n, yaml.MappingNode) {
|
||||
return y7s.NodeErr(n, "template definition must be a map")
|
||||
}
|
||||
|
||||
if wrap.res == nil {
|
||||
wrap.res = &types.Template{}
|
||||
}
|
||||
|
||||
if err = n.Decode(&wrap.res); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if wrap.rbac, err = decodeRbac(n); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if wrap.envoyConfig, err = decodeEnvoyConfig(n); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if wrap.ts, err = decodeTimestamps(n); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wrap template) MarshalEnvoy() ([]resource.Interface, error) {
|
||||
rs := resource.NewTemplate(wrap.res)
|
||||
rs.SetTimestamps(wrap.ts)
|
||||
rs.SetConfig(wrap.envoyConfig)
|
||||
|
||||
return envoy.CollectNodes(
|
||||
rs,
|
||||
wrap.rbac.bindResource(rs),
|
||||
)
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
settings:
|
||||
auth.mail.email-confirmation.subject.en: Confirm your email address
|
||||
auth.mail.email-confirmation.body.en: |-
|
||||
{{.EmailHeaderEn}}
|
||||
<h2 style="color: #568ba2;text-align: center;">Confirm your email address</h2>
|
||||
<p>Hello,</p>
|
||||
<p>Follow <a href="{{ .URL }}" style="color:#568ba2;">this link</a> to confirm your email address.</p>
|
||||
<p>You will be logged-in after successful confirmation.</p>
|
||||
{{.EmailFooterEn}}
|
||||
|
||||
auth.mail.password-reset.subject.en: Reset your password
|
||||
auth.mail.password-reset.body.en: |-
|
||||
{{.EmailHeaderEn}}
|
||||
<h2 style="color: #568ba2;text-align: center;">Reset your password</h2>
|
||||
<p>Hello,</p>
|
||||
<p>Follow <a href="{{ .URL }}" style="color:#568ba2;">this link</a> and reset your password.</p>
|
||||
<p>You will be logged-in after successful reset.</p>
|
||||
{{.EmailFooterEn}}
|
||||
53
provision/002_templates/auth.yml
Normal file
53
provision/002_templates/auth.yml
Normal file
@ -0,0 +1,53 @@
|
||||
templates:
|
||||
auth_email_password_reset_subject:
|
||||
type: text/plain
|
||||
meta:
|
||||
short: Password reset subject
|
||||
template: Reset your password
|
||||
|
||||
auth_email_password_reset_content:
|
||||
type: text/html
|
||||
meta:
|
||||
short: Password reset content
|
||||
template: |-
|
||||
{{template "email_general_header" .}}
|
||||
<h2 style="color: #568ba2;text-align: center;">Reset your password</h2>
|
||||
<p>Hello,</p>
|
||||
<p>Follow <a href="{{ .URL }}" style="color:#568ba2;">this link</a> and reset your password.</p>
|
||||
<p>You will be logged-in after successful reset.</p>
|
||||
{{template "email_general_footer" .}}
|
||||
|
||||
auth_email_email_confirm_subject:
|
||||
type: text/plain
|
||||
meta:
|
||||
short: Email confirmation subject
|
||||
template: Reset your password
|
||||
|
||||
auth_email_email_confirm_content:
|
||||
type: text/html
|
||||
meta:
|
||||
short: Email confirmation content
|
||||
template: |-
|
||||
{{template "email_general_header" .}}
|
||||
<h2 style="color: #568ba2;text-align: center;">Confirm your email address</h2>
|
||||
<p>Hello,</p>
|
||||
<p>Follow <a href="{{ .URL }}" style="color:#568ba2;">this link</a> to confirm your email address.</p>
|
||||
<p>You will be logged-in after successful confirmation.</p>
|
||||
{{template "email_general_footer" .}}
|
||||
|
||||
auth_email_email_mfa_subject:
|
||||
type: text/plain
|
||||
meta:
|
||||
short: MFA login code subject
|
||||
template: Login code
|
||||
|
||||
auth_email_email_mfa_content:
|
||||
type: text/html
|
||||
meta:
|
||||
short: MFA login code content
|
||||
template: |-
|
||||
{{template "email_general_header" .}}
|
||||
<h2 style="color: #568ba2;text-align: center;">Reset your password</h2>
|
||||
<p>Hello,</p>
|
||||
<p>Enter this code into your login form: <code>{{.Code}}</code></p>
|
||||
{{template "email_general_footer" .}}
|
||||
56
provision/002_templates/general.yml
Normal file
56
provision/002_templates/general.yml
Normal file
@ -0,0 +1,56 @@
|
||||
templates:
|
||||
email_general_header:
|
||||
type: text/html
|
||||
partial: true
|
||||
meta:
|
||||
short: General template header
|
||||
description: General template header to use with system email notifications
|
||||
template: |-
|
||||
<div style="width:100%;min-height:100%;margin:0;padding:0;color:#3a393c;font-size:12px;line-height:18px;font-family:Verdana,Arial,sans-serif">
|
||||
<table width="100%" align="center" style="width:100%;height:100%;border-collapse:collapse;border:0;padding:60px" border="0" cellspacing="0" cellpadding="0" summary="">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td valign="top" align="center" style="padding: 20px 0;">
|
||||
<table width="800" cellspacing="0" cellpadding="0" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="800" bgcolor="#ffffff" style="color:#3a393c;font-size:14px;line-height:20px;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;text-align:left">
|
||||
<table width="800" cellspacing="0" cellpadding="0" border="0">
|
||||
<tbody>
|
||||
<tr style="background-color:#ffffff;height:50px;">
|
||||
<td style="border-bottom:2px solid #568ba2;">
|
||||
<a href="{{ .BaseURL }}" style="text-decoration:none" target="_blank">
|
||||
<img src="{{ .Logo }}" style="display: block;margin: 0 auto;padding: 10px;">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="800" style="padding:40px 30px">
|
||||
|
||||
email_general_footer:
|
||||
type: text/html
|
||||
partial: true
|
||||
meta:
|
||||
short: General template footer
|
||||
description: General template footer to use with system email notifications
|
||||
template: |-
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:30px;border-top: 1px solid #F3F3F5">
|
||||
<p>If you have any questions, please contact <a href="mailto:{{ .SignatureEmail }}" style="color:#568ba2;">{{ .SignatureEmail }}</a>.</p>
|
||||
<p>Kind regards, <br>
|
||||
{{ .SignatureName }}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -92,6 +92,7 @@ func truncateStore(ctx context.Context, s store.Storer, t *testing.T) {
|
||||
|
||||
s.TruncateRoles(ctx),
|
||||
s.TruncateUsers(ctx),
|
||||
s.TruncateTemplates(ctx),
|
||||
s.TruncateApplications(ctx),
|
||||
s.TruncateSettings(ctx),
|
||||
s.TruncateRbacRules(ctx),
|
||||
|
||||
@ -447,6 +447,32 @@ func TestStoreYaml_base(t *testing.T) {
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "base templates",
|
||||
pre: func(ctx context.Context, s store.Storer) (error, *su.DecodeFilter) {
|
||||
sTestTemplate(ctx, t, s, "base")
|
||||
|
||||
df := su.NewDecodeFilter().
|
||||
Templates(&stypes.TemplateFilter{
|
||||
Handle: "base_template",
|
||||
})
|
||||
return nil, df
|
||||
},
|
||||
check: func(ctx context.Context, s store.Storer, req *require.Assertions) {
|
||||
tpl, err := store.LookupTemplateByHandle(ctx, s, "base_template")
|
||||
req.NoError(err)
|
||||
|
||||
req.Equal("base_template", tpl.Handle)
|
||||
req.Equal(stypes.DocumentTypeHTML, tpl.Type)
|
||||
req.Equal(true, tpl.Partial)
|
||||
req.Equal("base_short", tpl.Meta.Short)
|
||||
req.Equal("base_description", tpl.Meta.Description)
|
||||
req.Equal("base_template content", tpl.Template)
|
||||
req.Equal(createdAt.Format(time.RFC3339), tpl.CreatedAt.Format(time.RFC3339))
|
||||
req.Equal(updatedAt.Format(time.RFC3339), tpl.UpdatedAt.Format(time.RFC3339))
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "base applications",
|
||||
pre: func(ctx context.Context, s store.Storer) (error, *su.DecodeFilter) {
|
||||
|
||||
@ -36,6 +36,29 @@ func sTestUser(ctx context.Context, t *testing.T, s store.Storer, pfx string) *t
|
||||
return usr
|
||||
}
|
||||
|
||||
func sTestTemplate(ctx context.Context, t *testing.T, s store.Storer, pfx string) *types.Template {
|
||||
tpl := &types.Template{
|
||||
ID: su.NextID(),
|
||||
Handle: pfx + "_template",
|
||||
Type: types.DocumentTypeHTML,
|
||||
Partial: true,
|
||||
Meta: types.TemplateMeta{
|
||||
Short: pfx + "_short",
|
||||
Description: pfx + "_description",
|
||||
},
|
||||
Template: pfx + "_template content",
|
||||
CreatedAt: createdAt,
|
||||
UpdatedAt: &updatedAt,
|
||||
}
|
||||
|
||||
err := store.CreateTemplate(ctx, s, tpl)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return tpl
|
||||
}
|
||||
|
||||
func sTestRole(ctx context.Context, t *testing.T, s store.Storer, pfx string) *types.Role {
|
||||
rl := &types.Role{
|
||||
ID: su.NextID(),
|
||||
|
||||
30
tests/envoy/testdata/provision/simple/0001_templates.yaml
vendored
Normal file
30
tests/envoy/testdata/provision/simple/0001_templates.yaml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
templates:
|
||||
email_general_header:
|
||||
type: text/html
|
||||
partial: true
|
||||
meta:
|
||||
short: General template header
|
||||
description: General template header to use with system email notifications
|
||||
template: <p>header</p>
|
||||
|
||||
email_general_footer:
|
||||
type: text/html
|
||||
partial: true
|
||||
meta:
|
||||
short: General template footer
|
||||
description: General template footer to use with system email notifications
|
||||
template: <p>footer</p>
|
||||
|
||||
email_general_content:
|
||||
type: text/html
|
||||
meta:
|
||||
short: General template content
|
||||
description: General template content to use with system email notifications
|
||||
template: <p>content</p>
|
||||
|
||||
email_general_subject:
|
||||
type: text/plain
|
||||
meta:
|
||||
short: General template subject
|
||||
description: General template subject to use with system email notifications
|
||||
template: content
|
||||
@ -11,6 +11,7 @@ import (
|
||||
su "github.com/cortezaproject/corteza-server/pkg/envoy/store"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rbac"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
stypes "github.com/cortezaproject/corteza-server/system/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -169,6 +170,12 @@ func TestYamlStore_provision(t *testing.T) {
|
||||
req.Equal("1", sr.Values.Get("s2", 0).Value)
|
||||
})
|
||||
|
||||
t.Run("templates", func(t *testing.T) {
|
||||
tpls, _, err := store.SearchTemplates(ctx, s, stypes.TemplateFilter{})
|
||||
req.NoError(err)
|
||||
req.Len(tpls, 4)
|
||||
})
|
||||
|
||||
truncateStore(ctx, s, t)
|
||||
})
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user