Files
upstream-ory-kratos/courier/template/load_template.go
upstream-mirror c60cc7ca75
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
2026-09-15 21:19:12 +05:00

191 lines
4.4 KiB
Go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package template
import (
"bytes"
"context"
"embed"
htemplate "html/template"
"io"
"io/fs"
"path/filepath"
"text/template"
"github.com/ory/x/fetcher"
"github.com/ory/x/httpx"
"github.com/Masterminds/sprig/v3"
lru "github.com/hashicorp/golang-lru/v2"
"github.com/pkg/errors"
)
//go:embed courier/builtin/templates/*
var templates embed.FS
var Cache, _ = lru.New[string, Template](16)
type Template interface {
Execute(wr io.Writer, data interface{}) error
}
type templateDependencies interface {
httpx.ClientProvider
}
func loadBuiltInTemplate(filesystem fs.FS, name string, html bool) (Template, error) {
if t, found := Cache.Get(name); found {
return t, nil
}
file, err := filesystem.Open(name)
if err != nil {
// try to fallback to bundled templates
var fallbackErr error
file, fallbackErr = templates.Open(filepath.Join("courier/builtin/templates", name))
if fallbackErr != nil {
// return original error from os.DirFS
return nil, errors.WithStack(err)
}
}
defer func() { _ = file.Close() }()
var b bytes.Buffer
if _, err := io.Copy(&b, file); err != nil {
return nil, errors.WithStack(err)
}
var tpl Template
if html {
t, err := htemplate.New(name).Funcs(sprig.HtmlFuncMap()).Parse(b.String())
if err != nil {
return nil, errors.WithStack(err)
}
tpl = t
} else {
t, err := template.New(name).Funcs(sprig.TxtFuncMap()).Parse(b.String())
if err != nil {
return nil, errors.WithStack(err)
}
tpl = t
}
_ = Cache.Add(name, tpl)
return tpl, nil
}
func loadRemoteTemplate(ctx context.Context, d templateDependencies, url string, html bool) (t Template, err error) {
if t, found := Cache.Get(url); found {
return t, nil
}
f := fetcher.NewFetcher(fetcher.WithClient(d.HTTPClient(ctx)))
bb, err := f.FetchContext(ctx, url)
if err != nil {
return nil, errors.WithStack(err)
}
b := bb.Bytes()
if html {
t, err = htemplate.New(url).Funcs(sprig.HermeticHtmlFuncMap()).Parse(string(b))
if err != nil {
return nil, errors.WithStack(err)
}
} else {
t, err = template.New(url).Funcs(sprig.HermeticTxtFuncMap()).Parse(string(b))
if err != nil {
return nil, errors.WithStack(err)
}
}
Cache.Add(url, t)
return t, nil
}
func loadTemplate(filesystem fs.FS, name, pattern string, html bool) (Template, error) {
if t, found := Cache.Get(name); found {
return t, nil
}
matches, _ := fs.Glob(filesystem, name)
// make sure the file exists in the fs, otherwise fallback to built in templates
if matches == nil {
return loadBuiltInTemplate(filesystem, name, html)
}
glob := name
if pattern != "" {
// pattern matching is used when we have more than one gotmpl for different use cases, such as i18n support
// e.g. some_template/template_name* will match some_template/template_name.body.en_US.gotmpl
matches, _ = fs.Glob(filesystem, pattern)
// set the glob string to match patterns
if matches != nil {
glob = pattern
}
}
var tpl Template
if html {
t, err := htemplate.New(filepath.Base(name)).Funcs(sprig.HermeticHtmlFuncMap()).ParseFS(filesystem, glob)
if err != nil {
return nil, errors.WithStack(err)
}
tpl = t
} else {
t, err := template.New(filepath.Base(name)).Funcs(sprig.HermeticTxtFuncMap()).ParseFS(filesystem, glob)
if err != nil {
return nil, errors.WithStack(err)
}
tpl = t
}
_ = Cache.Add(name, tpl)
return tpl, nil
}
func LoadText(ctx context.Context, d templateDependencies, filesystem fs.FS, name, pattern string, model interface{}, remoteURL string) (string, error) {
var t Template
var err error
if remoteURL != "" {
t, err = loadRemoteTemplate(ctx, d, remoteURL, false)
if err != nil {
return "", err
}
} else {
t, err = loadTemplate(filesystem, name, pattern, false)
if err != nil {
return "", err
}
}
var b bytes.Buffer
if err := t.Execute(&b, model); err != nil {
return "", err
}
return b.String(), nil
}
func LoadHTML(ctx context.Context, d templateDependencies, filesystem fs.FS, name, pattern string, model interface{}, remoteURL string) (string, error) {
var t Template
var err error
if remoteURL != "" {
t, err = loadRemoteTemplate(ctx, d, remoteURL, true)
if err != nil {
return "", err
}
} else {
t, err = loadTemplate(filesystem, name, pattern, true)
if err != nil {
return "", err
}
}
var b bytes.Buffer
if err := t.Execute(&b, model); err != nil {
return "", err
}
return b.String(), nil
}