Support for templating the priority header
This commit is contained in:
@@ -2643,7 +2643,7 @@ You can enable templating by setting the `X-Template` header (or its aliases `Te
|
||||
will use a custom template file from the template directory (defaults to `/etc/ntfy/templates`, can be overridden with `template-dir`).
|
||||
See [custom templates](#custom-templates) for more details.
|
||||
* **Inline templating**: Setting the `X-Template` header or query parameter to `yes` or `1` (e.g. `?template=yes`)
|
||||
will enable inline templating, which means that the `message` and/or `title` will be parsed as a Go template.
|
||||
will enable inline templating, which means that the `message`, `title`, and/or `priority` will be parsed as a Go template.
|
||||
See [inline templating](#inline-templating) for more details.
|
||||
|
||||
To learn the basics of Go's templating language, please see [template syntax](#template-syntax).
|
||||
@@ -2686,7 +2686,7 @@ and set the `X-Template` header or query parameter to the name of the template f
|
||||
For example, if you have a template file `/etc/ntfy/templates/myapp.yml`, you can set the header `X-Template: myapp` or
|
||||
the query parameter `?template=myapp` to use it.
|
||||
|
||||
Template files must have the `.yml` (not: `.yaml`!) extension and must be formatted as YAML. They may contain `title` and `message` keys,
|
||||
Template files must have the `.yml` (not: `.yaml`!) extension and must be formatted as YAML. They may contain `title`, `message`, and `priority` keys,
|
||||
which are interpreted as Go templates.
|
||||
|
||||
Here's an **example custom template**:
|
||||
@@ -2704,6 +2704,11 @@ Here's an **example custom template**:
|
||||
Status: {{ .status }}
|
||||
Type: {{ .type | upper }} ({{ .percent }}%)
|
||||
Server: {{ .server }}
|
||||
priority: |
|
||||
{{ if gt .percent 90.0 }}5
|
||||
{{ else if gt .percent 75.0 }}4
|
||||
{{ else }}3
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
Once you have the template file in place, you can send the payload to your topic using the `X-Template`
|
||||
@@ -2785,7 +2790,7 @@ Which will result in a notification that looks like this:
|
||||
|
||||
### Inline templating
|
||||
|
||||
When `X-Template: yes` (aliases: `Template: yes`, `Tpl: yes`) or `?template=yes` is set, you can use Go templates in the `message` and `title` fields of your
|
||||
When `X-Template: yes` (aliases: `Template: yes`, `Tpl: yes`) or `?template=yes` is set, you can use Go templates in the `message`, `title`, and `priority` fields of your
|
||||
webhook payload.
|
||||
|
||||
Inline templates are most useful for templated one-off messages, or if you do not control the ntfy server (e.g., if you're using ntfy.sh).
|
||||
@@ -2841,12 +2846,12 @@ Here's an **easier example with a shorter JSON payload**:
|
||||
curl \
|
||||
--globoff \
|
||||
-d '{"hostname": "phil-pc", "error": {"level": "severe", "desc": "Disk has run out of space"}}' \
|
||||
'ntfy.sh/mytopic?tpl=yes&t={{.hostname}}:+A+{{.error.level}}+error+has+occurred&m=Error+message:+{{.error.desc}}'
|
||||
'ntfy.sh/mytopic?tpl=yes&t={{.hostname}}:+A+{{.error.level}}+error+has+occurred&m=Error+message:+{{.error.desc}}&p={{if+eq+.error.level+"severe"}}5{{else}}3{{end}}'
|
||||
```
|
||||
|
||||
=== "HTTP"
|
||||
``` http
|
||||
POST /mytopic?tpl=yes&t={{.hostname}}:+A+{{.error.level}}+error+has+occurred&m=Error+message:+{{.error.desc}} HTTP/1.1
|
||||
POST /mytopic?tpl=yes&t={{.hostname}}:+A+{{.error.level}}+error+has+occurred&m=Error+message:+{{.error.desc}}&p={{if+eq+.error.level+"severe"}}5{{else}}3{{end}} HTTP/1.1
|
||||
Host: ntfy.sh
|
||||
|
||||
{"hostname": "phil-pc", "error": {"level": "severe", "desc": "Disk has run out of space"}}
|
||||
@@ -2854,7 +2859,7 @@ Here's an **easier example with a shorter JSON payload**:
|
||||
|
||||
=== "JavaScript"
|
||||
``` javascript
|
||||
fetch('https://ntfy.sh/mytopic?tpl=yes&t={{.hostname}}:+A+{{.error.level}}+error+has+occurred&m=Error+message:+{{.error.desc}}', {
|
||||
fetch('https://ntfy.sh/mytopic?tpl=yes&t={{.hostname}}:+A+{{.error.level}}+error+has+occurred&m=Error+message:+{{.error.desc}}&p={{if+eq+.error.level+"severe"}}5{{else}}3{{end}}', {
|
||||
method: 'POST',
|
||||
body: '{"hostname": "phil-pc", "error": {"level": "severe", "desc": "Disk has run out of space"}}'
|
||||
})
|
||||
@@ -2863,7 +2868,7 @@ Here's an **easier example with a shorter JSON payload**:
|
||||
=== "Go"
|
||||
``` go
|
||||
body := `{"hostname": "phil-pc", "error": {"level": "severe", "desc": "Disk has run out of space"}}`
|
||||
uri := "https://ntfy.sh/mytopic?tpl=yes&t={{.hostname}}:+A+{{.error.level}}+error+has+occurred&m=Error+message:+{{.error.desc}}"
|
||||
uri := `https://ntfy.sh/mytopic?tpl=yes&t={{.hostname}}:+A+{{.error.level}}+error+has+occurred&m=Error+message:+{{.error.desc}}&p={{if eq .error.level "severe"}}5{{else}}3{{end}}`
|
||||
req, _ := http.NewRequest("POST", uri, strings.NewReader(body))
|
||||
http.DefaultClient.Do(req)
|
||||
```
|
||||
@@ -2873,7 +2878,7 @@ Here's an **easier example with a shorter JSON payload**:
|
||||
``` powershell
|
||||
$Request = @{
|
||||
Method = "POST"
|
||||
URI = "https://ntfy.sh/mytopic?tpl=yes&t={{.hostname}}:+A+{{.error.level}}+error+has+occurred&m=Error+message:+{{.error.desc}}"
|
||||
URI = 'https://ntfy.sh/mytopic?tpl=yes&t={{.hostname}}:+A+{{.error.level}}+error+has+occurred&m=Error+message:+{{.error.desc}}&p={{if+eq+.error.level+"severe"}}5{{else}}3{{end}}'
|
||||
Body = '{"hostname": "phil-pc", "error": {"level": "severe", "desc": "Disk has run out of space"}}'
|
||||
ContentType = "application/json"
|
||||
}
|
||||
@@ -2883,14 +2888,14 @@ Here's an **easier example with a shorter JSON payload**:
|
||||
=== "Python"
|
||||
``` python
|
||||
requests.post(
|
||||
"https://ntfy.sh/mytopic?tpl=yes&t={{.hostname}}:+A+{{.error.level}}+error+has+occurred&m=Error+message:+{{.error.desc}}",
|
||||
'https://ntfy.sh/mytopic?tpl=yes&t={{.hostname}}:+A+{{.error.level}}+error+has+occurred&m=Error+message:+{{.error.desc}}&p={{if+eq+.error.level+"severe"}}5{{else}}3{{end}}',
|
||||
data='{"hostname": "phil-pc", "error": {"level": "severe", "desc": "Disk has run out of space"}}'
|
||||
)
|
||||
```
|
||||
|
||||
=== "PHP"
|
||||
``` php-inline
|
||||
file_get_contents("https://ntfy.sh/mytopic?tpl=yes&t={{.hostname}}:+A+{{.error.level}}+error+has+occurred&m=Error+message:+{{.error.desc}}", false, stream_context_create([
|
||||
file_get_contents('https://ntfy.sh/mytopic?tpl=yes&t={{.hostname}}:+A+{{.error.level}}+error+has+occurred&m=Error+message:+{{.error.desc}}&p={{if+eq+.error.level+"severe"}}5{{else}}3{{end}}', false, stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'POST',
|
||||
'header' => "Content-Type: application/json",
|
||||
@@ -2899,9 +2904,9 @@ Here's an **easier example with a shorter JSON payload**:
|
||||
]));
|
||||
```
|
||||
|
||||
This example uses the `message`/`m` and `title`/`t` query parameters, but obviously this also works with the corresponding
|
||||
`Message`/`Title` headers. It will send a notification with a title `phil-pc: A severe error has occurred` and a message
|
||||
`Error message: Disk has run out of space`.
|
||||
This example uses the `message`/`m`, `title`/`t`, and `priority`/`p` query parameters, but obviously this also works with the
|
||||
corresponding headers. It will send a notification with a title `phil-pc: A severe error has occurred`, a message
|
||||
`Error message: Disk has run out of space`, and priority `5` (max) if the level is "severe", or `3` (default) otherwise.
|
||||
|
||||
### Template syntax
|
||||
ntfy uses [Go templates](https://pkg.go.dev/text/template) for its templates, which is arguably one of the most powerful,
|
||||
@@ -2920,7 +2925,7 @@ your templates there first ([example for Grafana alert](https://repeatit.io/#/sh
|
||||
ntfy supports a subset of the **[Sprig template functions](publish/template-functions.md)** (originally copied from [Sprig](https://github.com/Masterminds/sprig),
|
||||
thank you to the Sprig developers 🙏). This is useful for advanced message templating and for transforming the data provided through the JSON payload.
|
||||
|
||||
Below are the functions that are available to use inside your message/title templates.
|
||||
Below are the functions that are available to use inside your message, title, and priority templates.
|
||||
|
||||
* [String Functions](publish/template-functions.md#string-functions): `trim`, `trunc`, `substr`, `plural`, etc.
|
||||
* [String List Functions](publish/template-functions.md#string-list-functions): `splitList`, `sortAlpha`, etc.
|
||||
|
||||
@@ -1681,6 +1681,10 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
|
||||
|
||||
### ntfy server v2.17.x (UNRELEASED)
|
||||
|
||||
**Features:**
|
||||
|
||||
* Support templating in the priority field ([#1426](https://github.com/binwiederhier/ntfy/issues/1426), thanks to [@seantomburke](https://github.com/seantomburke) for reporting)
|
||||
|
||||
**Bug fixes + maintenance:**
|
||||
|
||||
* Fix crash when commit string is shorter than 7 characters in non-GitHub-Action builds ([#1493](https://github.com/binwiederhier/ntfy/issues/1493), thanks to [@cyrinux](https://github.com/cyrinux) for reporting)
|
||||
|
||||
@@ -793,7 +793,7 @@ func (s *Server) handlePublishInternal(r *http.Request, v *visitor) (*message, e
|
||||
return nil, err
|
||||
}
|
||||
m := newDefaultMessage(t.ID, "")
|
||||
cache, firebase, email, call, template, unifiedpush, e := s.parsePublishParams(r, m)
|
||||
cache, firebase, email, call, template, unifiedpush, priorityStr, e := s.parsePublishParams(r, m)
|
||||
if e != nil {
|
||||
return nil, e.With(t)
|
||||
}
|
||||
@@ -824,7 +824,7 @@ func (s *Server) handlePublishInternal(r *http.Request, v *visitor) (*message, e
|
||||
if cache {
|
||||
m.Expires = time.Unix(m.Time, 0).Add(v.Limits().MessageExpiryDuration).Unix()
|
||||
}
|
||||
if err := s.handlePublishBody(r, v, m, body, template, unifiedpush); err != nil {
|
||||
if err := s.handlePublishBody(r, v, m, body, template, unifiedpush, priorityStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if m.Message == "" {
|
||||
@@ -1055,11 +1055,11 @@ func (s *Server) forwardPollRequest(v *visitor, m *message) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, firebase bool, email, call string, template templateMode, unifiedpush bool, err *errHTTP) {
|
||||
func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, firebase bool, email, call string, template templateMode, unifiedpush bool, priorityStr string, err *errHTTP) {
|
||||
if r.Method != http.MethodGet && updatePathRegex.MatchString(r.URL.Path) {
|
||||
pathSequenceID, err := s.sequenceIDFromPath(r.URL.Path)
|
||||
if err != nil {
|
||||
return false, false, "", "", "", false, err
|
||||
return false, false, "", "", "", false, "", err
|
||||
}
|
||||
m.SequenceID = pathSequenceID
|
||||
} else {
|
||||
@@ -1068,7 +1068,7 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
|
||||
if sequenceIDRegex.MatchString(sequenceID) {
|
||||
m.SequenceID = sequenceID
|
||||
} else {
|
||||
return false, false, "", "", "", false, errHTTPBadRequestSequenceIDInvalid
|
||||
return false, false, "", "", "", false, "", errHTTPBadRequestSequenceIDInvalid
|
||||
}
|
||||
} else {
|
||||
m.SequenceID = m.ID
|
||||
@@ -1089,7 +1089,7 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
|
||||
}
|
||||
if attach != "" {
|
||||
if !urlRegex.MatchString(attach) {
|
||||
return false, false, "", "", "", false, errHTTPBadRequestAttachmentURLInvalid
|
||||
return false, false, "", "", "", false, "", errHTTPBadRequestAttachmentURLInvalid
|
||||
}
|
||||
m.Attachment.URL = attach
|
||||
if m.Attachment.Name == "" {
|
||||
@@ -1107,19 +1107,19 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
|
||||
}
|
||||
if icon != "" {
|
||||
if !urlRegex.MatchString(icon) {
|
||||
return false, false, "", "", "", false, errHTTPBadRequestIconURLInvalid
|
||||
return false, false, "", "", "", false, "", errHTTPBadRequestIconURLInvalid
|
||||
}
|
||||
m.Icon = icon
|
||||
}
|
||||
email = readParam(r, "x-email", "x-e-mail", "email", "e-mail", "mail", "e")
|
||||
if s.smtpSender == nil && email != "" {
|
||||
return false, false, "", "", "", false, errHTTPBadRequestEmailDisabled
|
||||
return false, false, "", "", "", false, "", errHTTPBadRequestEmailDisabled
|
||||
}
|
||||
call = readParam(r, "x-call", "call")
|
||||
if call != "" && (s.config.TwilioAccount == "" || s.userManager == nil) {
|
||||
return false, false, "", "", "", false, errHTTPBadRequestPhoneCallsDisabled
|
||||
return false, false, "", "", "", false, "", errHTTPBadRequestPhoneCallsDisabled
|
||||
} else if call != "" && !isBoolValue(call) && !phoneNumberRegex.MatchString(call) {
|
||||
return false, false, "", "", "", false, errHTTPBadRequestPhoneNumberInvalid
|
||||
return false, false, "", "", "", false, "", errHTTPBadRequestPhoneNumberInvalid
|
||||
}
|
||||
template = templateMode(readParam(r, "x-template", "template", "tpl"))
|
||||
messageStr := readParam(r, "x-message", "message", "m")
|
||||
@@ -1131,29 +1131,33 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
|
||||
m.Message = messageStr
|
||||
}
|
||||
var e error
|
||||
m.Priority, e = util.ParsePriority(readParam(r, "x-priority", "priority", "prio", "p"))
|
||||
if e != nil {
|
||||
return false, false, "", "", "", false, errHTTPBadRequestPriorityInvalid
|
||||
priorityStr = readParam(r, "x-priority", "priority", "prio", "p")
|
||||
if !template.Enabled() {
|
||||
m.Priority, e = util.ParsePriority(priorityStr)
|
||||
if e != nil {
|
||||
return false, false, "", "", "", false, "", errHTTPBadRequestPriorityInvalid
|
||||
}
|
||||
priorityStr = "" // Clear since it's already parsed
|
||||
}
|
||||
m.Tags = readCommaSeparatedParam(r, "x-tags", "tags", "tag", "ta")
|
||||
delayStr := readParam(r, "x-delay", "delay", "x-at", "at", "x-in", "in")
|
||||
if delayStr != "" {
|
||||
if !cache {
|
||||
return false, false, "", "", "", false, errHTTPBadRequestDelayNoCache
|
||||
return false, false, "", "", "", false, "", errHTTPBadRequestDelayNoCache
|
||||
}
|
||||
if email != "" {
|
||||
return false, false, "", "", "", false, errHTTPBadRequestDelayNoEmail // we cannot store the email address (yet)
|
||||
return false, false, "", "", "", false, "", errHTTPBadRequestDelayNoEmail // we cannot store the email address (yet)
|
||||
}
|
||||
if call != "" {
|
||||
return false, false, "", "", "", false, errHTTPBadRequestDelayNoCall // we cannot store the phone number (yet)
|
||||
return false, false, "", "", "", false, "", errHTTPBadRequestDelayNoCall // we cannot store the phone number (yet)
|
||||
}
|
||||
delay, err := util.ParseFutureTime(delayStr, time.Now())
|
||||
if err != nil {
|
||||
return false, false, "", "", "", false, errHTTPBadRequestDelayCannotParse
|
||||
return false, false, "", "", "", false, "", errHTTPBadRequestDelayCannotParse
|
||||
} else if delay.Unix() < time.Now().Add(s.config.MessageDelayMin).Unix() {
|
||||
return false, false, "", "", "", false, errHTTPBadRequestDelayTooSmall
|
||||
return false, false, "", "", "", false, "", errHTTPBadRequestDelayTooSmall
|
||||
} else if delay.Unix() > time.Now().Add(s.config.MessageDelayMax).Unix() {
|
||||
return false, false, "", "", "", false, errHTTPBadRequestDelayTooLarge
|
||||
return false, false, "", "", "", false, "", errHTTPBadRequestDelayTooLarge
|
||||
}
|
||||
m.Time = delay.Unix()
|
||||
}
|
||||
@@ -1161,7 +1165,7 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
|
||||
if actionsStr != "" {
|
||||
m.Actions, e = parseActions(actionsStr)
|
||||
if e != nil {
|
||||
return false, false, "", "", "", false, errHTTPBadRequestActionsInvalid.Wrap("%s", e.Error())
|
||||
return false, false, "", "", "", false, "", errHTTPBadRequestActionsInvalid.Wrap("%s", e.Error())
|
||||
}
|
||||
}
|
||||
contentType, markdown := readParam(r, "content-type", "content_type"), readBoolParam(r, false, "x-markdown", "markdown", "md")
|
||||
@@ -1180,7 +1184,7 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
|
||||
cache = false
|
||||
email = ""
|
||||
}
|
||||
return cache, firebase, email, call, template, unifiedpush, nil
|
||||
return cache, firebase, email, call, template, unifiedpush, priorityStr, nil
|
||||
}
|
||||
|
||||
// handlePublishBody consumes the PUT/POST body and decides whether the body is an attachment or the message.
|
||||
@@ -1199,7 +1203,7 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
|
||||
// If file.txt is <= 4096 (message limit) and valid UTF-8, treat it as a message
|
||||
// 7. curl -T file.txt ntfy.sh/mytopic
|
||||
// In all other cases, mostly if file.txt is > message limit, treat it as an attachment
|
||||
func (s *Server) handlePublishBody(r *http.Request, v *visitor, m *message, body *util.PeekedReadCloser, template templateMode, unifiedpush bool) error {
|
||||
func (s *Server) handlePublishBody(r *http.Request, v *visitor, m *message, body *util.PeekedReadCloser, template templateMode, unifiedpush bool, priorityStr string) error {
|
||||
if m.Event == pollRequestEvent { // Case 1
|
||||
return s.handleBodyDiscard(body)
|
||||
} else if unifiedpush {
|
||||
@@ -1209,7 +1213,7 @@ func (s *Server) handlePublishBody(r *http.Request, v *visitor, m *message, body
|
||||
} else if m.Attachment != nil && m.Attachment.Name != "" {
|
||||
return s.handleBodyAsAttachment(r, v, m, body) // Case 4
|
||||
} else if template.Enabled() {
|
||||
return s.handleBodyAsTemplatedTextMessage(m, template, body) // Case 5
|
||||
return s.handleBodyAsTemplatedTextMessage(m, template, body, priorityStr) // Case 5
|
||||
} else if !body.LimitReached && utf8.Valid(body.PeekedBytes) {
|
||||
return s.handleBodyAsTextMessage(m, body) // Case 6
|
||||
}
|
||||
@@ -1245,7 +1249,7 @@ func (s *Server) handleBodyAsTextMessage(m *message, body *util.PeekedReadCloser
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) handleBodyAsTemplatedTextMessage(m *message, template templateMode, body *util.PeekedReadCloser) error {
|
||||
func (s *Server) handleBodyAsTemplatedTextMessage(m *message, template templateMode, body *util.PeekedReadCloser, priorityStr string) error {
|
||||
body, err := util.Peek(body, max(s.config.MessageSizeLimit, jsonBodyBytesLimit))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1258,7 +1262,7 @@ func (s *Server) handleBodyAsTemplatedTextMessage(m *message, template templateM
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := s.renderTemplateFromParams(m, peekedBody); err != nil {
|
||||
if err := s.renderTemplateFromParams(m, peekedBody, priorityStr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1289,33 +1293,51 @@ func (s *Server) renderTemplateFromFile(m *message, templateName, peekedBody str
|
||||
}
|
||||
var err error
|
||||
if tpl.Message != nil {
|
||||
if m.Message, err = s.renderTemplate(*tpl.Message, peekedBody); err != nil {
|
||||
if m.Message, err = s.renderTemplate(templateName+" (message)", *tpl.Message, peekedBody); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if tpl.Title != nil {
|
||||
if m.Title, err = s.renderTemplate(*tpl.Title, peekedBody); err != nil {
|
||||
if m.Title, err = s.renderTemplate(templateName+" (title)", *tpl.Title, peekedBody); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if tpl.Priority != nil {
|
||||
renderedPriority, err := s.renderTemplate(templateName+" (priority)", *tpl.Priority, peekedBody)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if m.Priority, err = util.ParsePriority(renderedPriority); err != nil {
|
||||
return errHTTPBadRequestPriorityInvalid
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// renderTemplateFromParams transforms the JSON message body according to the inline template in the
|
||||
// message and title parameters.
|
||||
func (s *Server) renderTemplateFromParams(m *message, peekedBody string) error {
|
||||
// message, title, and priority parameters.
|
||||
func (s *Server) renderTemplateFromParams(m *message, peekedBody string, priorityStr string) error {
|
||||
var err error
|
||||
if m.Message, err = s.renderTemplate(m.Message, peekedBody); err != nil {
|
||||
if m.Message, err = s.renderTemplate("priority query parameter", m.Message, peekedBody); err != nil {
|
||||
return err
|
||||
}
|
||||
if m.Title, err = s.renderTemplate(m.Title, peekedBody); err != nil {
|
||||
if m.Title, err = s.renderTemplate("title query parameter", m.Title, peekedBody); err != nil {
|
||||
return err
|
||||
}
|
||||
if priorityStr != "" {
|
||||
renderedPriority, err := s.renderTemplate("priority query parameter", priorityStr, peekedBody)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if m.Priority, err = util.ParsePriority(renderedPriority); err != nil {
|
||||
return errHTTPBadRequestPriorityInvalid
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// renderTemplate renders a template with the given JSON source data.
|
||||
func (s *Server) renderTemplate(tpl string, source string) (string, error) {
|
||||
func (s *Server) renderTemplate(name, tpl, source string) (string, error) {
|
||||
if templateDisallowedRegex.MatchString(tpl) {
|
||||
return "", errHTTPBadRequestTemplateDisallowedFunctionCalls
|
||||
}
|
||||
@@ -1330,7 +1352,7 @@ func (s *Server) renderTemplate(tpl string, source string) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
limitWriter := util.NewLimitWriter(util.NewTimeoutWriter(&buf, templateMaxExecutionTime), util.NewFixedLimiter(templateMaxOutputBytes))
|
||||
if err := t.Execute(limitWriter, data); err != nil {
|
||||
return "", errHTTPBadRequestTemplateExecuteFailed.Wrap("%s", err.Error())
|
||||
return "", errHTTPBadRequestTemplateExecuteFailed.Wrap("template %s: %s", name, err.Error())
|
||||
}
|
||||
return strings.TrimSpace(strings.ReplaceAll(buf.String(), "\\n", "\n")), nil // replace any remaining "\n" (those outside of template curly braces) with newlines
|
||||
}
|
||||
|
||||
@@ -3290,6 +3290,117 @@ func TestServer_MessageTemplate_Until100_000(t *testing.T) {
|
||||
require.Contains(t, toHTTPError(t, response.Body.String()).Message, "too many iterations")
|
||||
}
|
||||
|
||||
func TestServer_MessageTemplate_Priority(t *testing.T) {
|
||||
t.Parallel()
|
||||
s := newTestServer(t, newTestConfig(t))
|
||||
response := request(t, s, "PUT", "/mytopic", `{"priority":"5"}`, map[string]string{
|
||||
"X-Message": "Test message",
|
||||
"X-Priority": "{{.priority}}",
|
||||
"X-Template": "1",
|
||||
})
|
||||
|
||||
require.Equal(t, 200, response.Code)
|
||||
m := toMessage(t, response.Body.String())
|
||||
require.Equal(t, "Test message", m.Message)
|
||||
require.Equal(t, 5, m.Priority)
|
||||
}
|
||||
|
||||
func TestServer_MessageTemplate_Priority_Conditional(t *testing.T) {
|
||||
t.Parallel()
|
||||
s := newTestServer(t, newTestConfig(t))
|
||||
|
||||
// Test with error status -> priority 5
|
||||
response := request(t, s, "PUT", "/mytopic", `{"status":"Error","message":"Something went wrong"}`, map[string]string{
|
||||
"X-Message": "Status: {{.status}} - {{.message}}",
|
||||
"X-Priority": `{{if eq .status "Error"}}5{{else}}3{{end}}`,
|
||||
"X-Template": "1",
|
||||
})
|
||||
require.Equal(t, 200, response.Code)
|
||||
m := toMessage(t, response.Body.String())
|
||||
require.Equal(t, "Status: Error - Something went wrong", m.Message)
|
||||
require.Equal(t, 5, m.Priority)
|
||||
|
||||
// Test with success status -> priority 3
|
||||
response = request(t, s, "PUT", "/mytopic", `{"status":"Success","message":"All good"}`, map[string]string{
|
||||
"X-Message": "Status: {{.status}} - {{.message}}",
|
||||
"X-Priority": `{{if eq .status "Error"}}5{{else}}3{{end}}`,
|
||||
"X-Template": "1",
|
||||
})
|
||||
require.Equal(t, 200, response.Code)
|
||||
m = toMessage(t, response.Body.String())
|
||||
require.Equal(t, "Status: Success - All good", m.Message)
|
||||
require.Equal(t, 3, m.Priority)
|
||||
}
|
||||
|
||||
func TestServer_MessageTemplate_Priority_NamedValue(t *testing.T) {
|
||||
t.Parallel()
|
||||
s := newTestServer(t, newTestConfig(t))
|
||||
response := request(t, s, "PUT", "/mytopic", `{"severity":"high"}`, map[string]string{
|
||||
"X-Message": "Alert",
|
||||
"X-Priority": "{{.severity}}",
|
||||
"X-Template": "1",
|
||||
})
|
||||
|
||||
require.Equal(t, 200, response.Code)
|
||||
m := toMessage(t, response.Body.String())
|
||||
require.Equal(t, 4, m.Priority) // "high" = 4
|
||||
}
|
||||
|
||||
func TestServer_MessageTemplate_Priority_Invalid(t *testing.T) {
|
||||
t.Parallel()
|
||||
s := newTestServer(t, newTestConfig(t))
|
||||
response := request(t, s, "PUT", "/mytopic", `{"priority":"invalid"}`, map[string]string{
|
||||
"X-Message": "Test message",
|
||||
"X-Priority": "{{.priority}}",
|
||||
"X-Template": "1",
|
||||
})
|
||||
|
||||
require.Equal(t, 400, response.Code)
|
||||
require.Equal(t, 40007, toHTTPError(t, response.Body.String()).Code)
|
||||
}
|
||||
|
||||
func TestServer_MessageTemplate_Priority_QueryParam(t *testing.T) {
|
||||
t.Parallel()
|
||||
s := newTestServer(t, newTestConfig(t))
|
||||
response := request(t, s, "PUT", "/mytopic?template=1&priority={{.priority}}", `{"priority":"max"}`, nil)
|
||||
|
||||
require.Equal(t, 200, response.Code)
|
||||
m := toMessage(t, response.Body.String())
|
||||
require.Equal(t, 5, m.Priority) // "max" = 5
|
||||
}
|
||||
|
||||
func TestServer_MessageTemplate_Priority_FromTemplateFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := newTestConfig(t)
|
||||
c.TemplateDir = t.TempDir()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(c.TemplateDir, "priority-test.yml"), []byte(`
|
||||
title: "{{.title}}"
|
||||
message: "{{.message}}"
|
||||
priority: '{{if eq .level "critical"}}5{{else if eq .level "warning"}}4{{else}}3{{end}}'
|
||||
`), 0644))
|
||||
s := newTestServer(t, c)
|
||||
|
||||
// Test with critical level
|
||||
response := request(t, s, "POST", "/mytopic?template=priority-test", `{"title":"Alert","message":"System down","level":"critical"}`, nil)
|
||||
require.Equal(t, 200, response.Code)
|
||||
m := toMessage(t, response.Body.String())
|
||||
require.Equal(t, "Alert", m.Title)
|
||||
require.Equal(t, "System down", m.Message)
|
||||
require.Equal(t, 5, m.Priority)
|
||||
|
||||
// Test with warning level
|
||||
response = request(t, s, "POST", "/mytopic?template=priority-test", `{"title":"Alert","message":"High load","level":"warning"}`, nil)
|
||||
require.Equal(t, 200, response.Code)
|
||||
m = toMessage(t, response.Body.String())
|
||||
require.Equal(t, 4, m.Priority)
|
||||
|
||||
// Test with info level
|
||||
response = request(t, s, "POST", "/mytopic?template=priority-test", `{"title":"Alert","message":"All good","level":"info"}`, nil)
|
||||
require.Equal(t, 200, response.Code)
|
||||
m = toMessage(t, response.Body.String())
|
||||
require.Equal(t, 3, m.Priority)
|
||||
}
|
||||
|
||||
func TestServer_DeleteMessage(t *testing.T) {
|
||||
t.Parallel()
|
||||
s := newTestServer(t, newTestConfig(t))
|
||||
|
||||
@@ -299,7 +299,7 @@ func (t templateMode) FileName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// templateFile represents a template file with title and message
|
||||
// templateFile represents a template file with title, message, and priority
|
||||
// It is used for file-based templates, e.g. grafana, influxdb, etc.
|
||||
//
|
||||
// Example YAML:
|
||||
@@ -308,9 +308,11 @@ func (t templateMode) FileName() string {
|
||||
// message: |
|
||||
// This is a {{ .Type }} alert.
|
||||
// It can be multiline.
|
||||
// priority: '{{ if eq .status "Error" }}5{{ else }}3{{ end }}'
|
||||
type templateFile struct {
|
||||
Title *string `yaml:"title"`
|
||||
Message *string `yaml:"message"`
|
||||
Title *string `yaml:"title"`
|
||||
Message *string `yaml:"message"`
|
||||
Priority *string `yaml:"priority"`
|
||||
}
|
||||
|
||||
type apiHealthResponse struct {
|
||||
|
||||
Reference in New Issue
Block a user