From 1d5bedc2bed7d52b3fd045fda08468e3fe329fb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Jerman?= Date: Tue, 9 Mar 2021 15:49:04 +0100 Subject: [PATCH] Add template provisioning --- pkg/envoy/resource/template.go | 65 +++++++++++ pkg/envoy/resource/types.go | 1 + pkg/envoy/store/decoder.go | 2 + pkg/envoy/store/encoder.go | 2 + pkg/envoy/store/system.go | 57 ++++++++++ pkg/envoy/store/template.go | 98 ++++++++++++++++ pkg/envoy/store/template_marshal.go | 106 ++++++++++++++++++ pkg/envoy/store/template_unmarshal.go | 20 ++++ pkg/envoy/yaml/decoder.go | 5 +- pkg/envoy/yaml/document.go | 25 +++++ pkg/envoy/yaml/encoder.go | 2 + pkg/envoy/yaml/template.go | 25 +++++ pkg/envoy/yaml/template_marshal.go | 85 ++++++++++++++ pkg/envoy/yaml/template_unmarshal.go | 95 ++++++++++++++++ provision/001_settings/auth.yaml | 18 --- provision/002_templates/auth.yml | 53 +++++++++ provision/002_templates/general.yml | 56 +++++++++ tests/envoy/main.go | 1 + tests/envoy/store_yaml_base_test.go | 26 +++++ tests/envoy/system.go | 23 ++++ .../provision/simple/0001_templates.yaml | 30 +++++ tests/envoy/yaml_store_provision_test.go | 7 ++ 22 files changed, 783 insertions(+), 19 deletions(-) create mode 100644 pkg/envoy/resource/template.go create mode 100644 pkg/envoy/store/template.go create mode 100644 pkg/envoy/store/template_marshal.go create mode 100644 pkg/envoy/store/template_unmarshal.go create mode 100644 pkg/envoy/yaml/template.go create mode 100644 pkg/envoy/yaml/template_marshal.go create mode 100644 pkg/envoy/yaml/template_unmarshal.go delete mode 100644 provision/001_settings/auth.yaml create mode 100644 provision/002_templates/auth.yml create mode 100644 provision/002_templates/general.yml create mode 100644 tests/envoy/testdata/provision/simple/0001_templates.yaml diff --git a/pkg/envoy/resource/template.go b/pkg/envoy/resource/template.go new file mode 100644 index 000000000..11520306b --- /dev/null +++ b/pkg/envoy/resource/template.go @@ -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()) +} diff --git a/pkg/envoy/resource/types.go b/pkg/envoy/resource/types.go index 9d9cf3446..59fe842ec 100644 --- a/pkg/envoy/resource/types.go +++ b/pkg/envoy/resource/types.go @@ -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:" ) diff --git a/pkg/envoy/store/decoder.go b/pkg/envoy/store/decoder.go index a6e28dbc0..9cd01d7ef 100644 --- a/pkg/envoy/store/decoder.go +++ b/pkg/envoy/store/decoder.go @@ -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), ) diff --git a/pkg/envoy/store/encoder.go b/pkg/envoy/store/encoder.go index 1f6f46547..5097b8672 100644 --- a/pkg/envoy/store/encoder.go +++ b/pkg/envoy/store/encoder.go @@ -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: diff --git a/pkg/envoy/store/system.go b/pkg/envoy/store/system.go index 9b483f540..b72a00e83 100644 --- a/pkg/envoy/store/system.go +++ b/pkg/envoy/store/system.go @@ -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 { diff --git a/pkg/envoy/store/template.go b/pkg/envoy/store/template.go new file mode 100644 index 000000000..e5f83b414 --- /dev/null +++ b/pkg/envoy/store/template.go @@ -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 +} diff --git a/pkg/envoy/store/template_marshal.go b/pkg/envoy/store/template_marshal.go new file mode 100644 index 000000000..54f1342c6 --- /dev/null +++ b/pkg/envoy/store/template_marshal.go @@ -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 +} diff --git a/pkg/envoy/store/template_unmarshal.go b/pkg/envoy/store/template_unmarshal.go new file mode 100644 index 000000000..32ca0cd24 --- /dev/null +++ b/pkg/envoy/store/template_unmarshal.go @@ -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), + ) +} diff --git a/pkg/envoy/yaml/decoder.go b/pkg/envoy/yaml/decoder.go index b7ab4d496..ce94048f6 100644 --- a/pkg/envoy/yaml/decoder.go +++ b/pkg/envoy/yaml/decoder.go @@ -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 diff --git a/pkg/envoy/yaml/document.go b/pkg/envoy/yaml/document.go index 26a13f811..1ca61d414 100644 --- a/pkg/envoy/yaml/document.go +++ b/pkg/envoy/yaml/document.go @@ -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 { diff --git a/pkg/envoy/yaml/encoder.go b/pkg/envoy/yaml/encoder.go index 69380cb21..e850ef823 100644 --- a/pkg/envoy/yaml/encoder.go +++ b/pkg/envoy/yaml/encoder.go @@ -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: diff --git a/pkg/envoy/yaml/template.go b/pkg/envoy/yaml/template.go new file mode 100644 index 000000000..975b187ee --- /dev/null +++ b/pkg/envoy/yaml/template.go @@ -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 + } +} diff --git a/pkg/envoy/yaml/template_marshal.go b/pkg/envoy/yaml/template_marshal.go new file mode 100644 index 000000000..2a56b40a9 --- /dev/null +++ b/pkg/envoy/yaml/template_marshal.go @@ -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 +} diff --git a/pkg/envoy/yaml/template_unmarshal.go b/pkg/envoy/yaml/template_unmarshal.go new file mode 100644 index 000000000..2dae5ef6c --- /dev/null +++ b/pkg/envoy/yaml/template_unmarshal.go @@ -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), + ) +} diff --git a/provision/001_settings/auth.yaml b/provision/001_settings/auth.yaml deleted file mode 100644 index f7c16306a..000000000 --- a/provision/001_settings/auth.yaml +++ /dev/null @@ -1,18 +0,0 @@ -settings: - auth.mail.email-confirmation.subject.en: Confirm your email address - auth.mail.email-confirmation.body.en: |- - {{.EmailHeaderEn}} -

Confirm your email address

-

Hello,

-

Follow this link to confirm your email address.

-

You will be logged-in after successful confirmation.

- {{.EmailFooterEn}} - - auth.mail.password-reset.subject.en: Reset your password - auth.mail.password-reset.body.en: |- - {{.EmailHeaderEn}} -

Reset your password

-

Hello,

-

Follow this link and reset your password.

-

You will be logged-in after successful reset.

- {{.EmailFooterEn}} diff --git a/provision/002_templates/auth.yml b/provision/002_templates/auth.yml new file mode 100644 index 000000000..616e7804b --- /dev/null +++ b/provision/002_templates/auth.yml @@ -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" .}} +

Reset your password

+

Hello,

+

Follow this link and reset your password.

+

You will be logged-in after successful reset.

+ {{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" .}} +

Confirm your email address

+

Hello,

+

Follow this link to confirm your email address.

+

You will be logged-in after successful confirmation.

+ {{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" .}} +

Reset your password

+

Hello,

+

Enter this code into your login form: {{.Code}}

+ {{template "email_general_footer" .}} diff --git a/provision/002_templates/general.yml b/provision/002_templates/general.yml new file mode 100644 index 000000000..33ed8338e --- /dev/null +++ b/provision/002_templates/general.yml @@ -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: |- +
+ + + + + + +
+ + + + + + +
+ + + + + + + + + + + + +
+ + + +
+ + email_general_footer: + type: text/html + partial: true + meta: + short: General template footer + description: General template footer to use with system email notifications + template: |- +
+

If you have any questions, please contact {{ .SignatureEmail }}.

+

Kind regards,
+ {{ .SignatureName }}

+
+
+
+
diff --git a/tests/envoy/main.go b/tests/envoy/main.go index 402c3b838..191a9bfa3 100644 --- a/tests/envoy/main.go +++ b/tests/envoy/main.go @@ -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), diff --git a/tests/envoy/store_yaml_base_test.go b/tests/envoy/store_yaml_base_test.go index 107dc6080..14260c892 100644 --- a/tests/envoy/store_yaml_base_test.go +++ b/tests/envoy/store_yaml_base_test.go @@ -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) { diff --git a/tests/envoy/system.go b/tests/envoy/system.go index 4a5d4a4df..e2af1eb38 100644 --- a/tests/envoy/system.go +++ b/tests/envoy/system.go @@ -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(), diff --git a/tests/envoy/testdata/provision/simple/0001_templates.yaml b/tests/envoy/testdata/provision/simple/0001_templates.yaml new file mode 100644 index 000000000..924006bb0 --- /dev/null +++ b/tests/envoy/testdata/provision/simple/0001_templates.yaml @@ -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:

header

+ + email_general_footer: + type: text/html + partial: true + meta: + short: General template footer + description: General template footer to use with system email notifications + template:

footer

+ + email_general_content: + type: text/html + meta: + short: General template content + description: General template content to use with system email notifications + template:

content

+ + email_general_subject: + type: text/plain + meta: + short: General template subject + description: General template subject to use with system email notifications + template: content diff --git a/tests/envoy/yaml_store_provision_test.go b/tests/envoy/yaml_store_provision_test.go index f070be52b..d4a1a96b2 100644 --- a/tests/envoy/yaml_store_provision_test.go +++ b/tests/envoy/yaml_store_provision_test.go @@ -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) })