mirror of
https://github.com/binwiederhier/ntfy.git
synced 2026-01-18 16:17:26 +01:00
Docs docs docs
This commit is contained in:
@@ -77,6 +77,12 @@ func WithMarkdown() PublishOption {
|
|||||||
return WithHeader("X-Markdown", "yes")
|
return WithHeader("X-Markdown", "yes")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithTemplate instructs the server to use a specific template for the message. If templateName is is "yes" or "1",
|
||||||
|
// the server will interpret the message and title as a template.
|
||||||
|
func WithTemplate(templateName string) PublishOption {
|
||||||
|
return WithHeader("X-Template", templateName)
|
||||||
|
}
|
||||||
|
|
||||||
// WithFilename sets a filename for the attachment, and/or forces the HTTP body to interpreted as an attachment
|
// WithFilename sets a filename for the attachment, and/or forces the HTTP body to interpreted as an attachment
|
||||||
func WithFilename(filename string) PublishOption {
|
func WithFilename(filename string) PublishOption {
|
||||||
return WithHeader("X-Filename", filename)
|
return WithHeader("X-Filename", filename)
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ var flagsPublish = append(
|
|||||||
&cli.StringFlag{Name: "actions", Aliases: []string{"A"}, EnvVars: []string{"NTFY_ACTIONS"}, Usage: "actions JSON array or simple definition"},
|
&cli.StringFlag{Name: "actions", Aliases: []string{"A"}, EnvVars: []string{"NTFY_ACTIONS"}, Usage: "actions JSON array or simple definition"},
|
||||||
&cli.StringFlag{Name: "attach", Aliases: []string{"a"}, EnvVars: []string{"NTFY_ATTACH"}, Usage: "URL to send as an external attachment"},
|
&cli.StringFlag{Name: "attach", Aliases: []string{"a"}, EnvVars: []string{"NTFY_ATTACH"}, Usage: "URL to send as an external attachment"},
|
||||||
&cli.BoolFlag{Name: "markdown", Aliases: []string{"md"}, EnvVars: []string{"NTFY_MARKDOWN"}, Usage: "Message is formatted as Markdown"},
|
&cli.BoolFlag{Name: "markdown", Aliases: []string{"md"}, EnvVars: []string{"NTFY_MARKDOWN"}, Usage: "Message is formatted as Markdown"},
|
||||||
|
&cli.StringFlag{Name: "template", Aliases: []string{"tpl"}, EnvVars: []string{"NTFY_TEMPLATE"}, Usage: "use templates to transform JSON message body"},
|
||||||
&cli.StringFlag{Name: "filename", Aliases: []string{"name", "n"}, EnvVars: []string{"NTFY_FILENAME"}, Usage: "filename for the attachment"},
|
&cli.StringFlag{Name: "filename", Aliases: []string{"name", "n"}, EnvVars: []string{"NTFY_FILENAME"}, Usage: "filename for the attachment"},
|
||||||
&cli.StringFlag{Name: "file", Aliases: []string{"f"}, EnvVars: []string{"NTFY_FILE"}, Usage: "file to upload as an attachment"},
|
&cli.StringFlag{Name: "file", Aliases: []string{"f"}, EnvVars: []string{"NTFY_FILE"}, Usage: "file to upload as an attachment"},
|
||||||
&cli.StringFlag{Name: "email", Aliases: []string{"mail", "e"}, EnvVars: []string{"NTFY_EMAIL"}, Usage: "also send to e-mail address"},
|
&cli.StringFlag{Name: "email", Aliases: []string{"mail", "e"}, EnvVars: []string{"NTFY_EMAIL"}, Usage: "also send to e-mail address"},
|
||||||
@@ -98,6 +99,7 @@ func execPublish(c *cli.Context) error {
|
|||||||
actions := c.String("actions")
|
actions := c.String("actions")
|
||||||
attach := c.String("attach")
|
attach := c.String("attach")
|
||||||
markdown := c.Bool("markdown")
|
markdown := c.Bool("markdown")
|
||||||
|
template := c.String("template")
|
||||||
filename := c.String("filename")
|
filename := c.String("filename")
|
||||||
file := c.String("file")
|
file := c.String("file")
|
||||||
email := c.String("email")
|
email := c.String("email")
|
||||||
@@ -146,6 +148,9 @@ func execPublish(c *cli.Context) error {
|
|||||||
if markdown {
|
if markdown {
|
||||||
options = append(options, client.WithMarkdown())
|
options = append(options, client.WithMarkdown())
|
||||||
}
|
}
|
||||||
|
if template != "" {
|
||||||
|
options = append(options, client.WithTemplate(template))
|
||||||
|
}
|
||||||
if filename != "" {
|
if filename != "" {
|
||||||
options = append(options, client.WithFilename(filename))
|
options = append(options, client.WithFilename(filename))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,13 +29,9 @@ func init() {
|
|||||||
commands = append(commands, cmdServe)
|
commands = append(commands, cmdServe)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
defaultServerConfigFile = "/etc/ntfy/server.yml"
|
|
||||||
)
|
|
||||||
|
|
||||||
var flagsServe = append(
|
var flagsServe = append(
|
||||||
append([]cli.Flag{}, flagsDefault...),
|
append([]cli.Flag{}, flagsDefault...),
|
||||||
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: defaultServerConfigFile, Usage: "config file"},
|
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: server.DefaultConfigFile, Usage: "config file"},
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "base-url", Aliases: []string{"base_url", "B"}, EnvVars: []string{"NTFY_BASE_URL"}, Usage: "externally visible base URL for this host (e.g. https://ntfy.sh)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "base-url", Aliases: []string{"base_url", "B"}, EnvVars: []string{"NTFY_BASE_URL"}, Usage: "externally visible base URL for this host (e.g. https://ntfy.sh)"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-http", Aliases: []string{"listen_http", "l"}, EnvVars: []string{"NTFY_LISTEN_HTTP"}, Value: server.DefaultListenHTTP, Usage: "ip:port used as HTTP listen address"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-http", Aliases: []string{"listen_http", "l"}, EnvVars: []string{"NTFY_LISTEN_HTTP"}, Value: server.DefaultListenHTTP, Usage: "ip:port used as HTTP listen address"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-https", Aliases: []string{"listen_https", "L"}, EnvVars: []string{"NTFY_LISTEN_HTTPS"}, Usage: "ip:port used as HTTPS listen address"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-https", Aliases: []string{"listen_https", "L"}, EnvVars: []string{"NTFY_LISTEN_HTTPS"}, Usage: "ip:port used as HTTPS listen address"}),
|
||||||
@@ -56,7 +52,7 @@ var flagsServe = append(
|
|||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-total-size-limit", Aliases: []string{"attachment_total_size_limit", "A"}, EnvVars: []string{"NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT"}, Value: util.FormatSize(server.DefaultAttachmentTotalSizeLimit), Usage: "limit of the on-disk attachment cache"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-total-size-limit", Aliases: []string{"attachment_total_size_limit", "A"}, EnvVars: []string{"NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT"}, Value: util.FormatSize(server.DefaultAttachmentTotalSizeLimit), Usage: "limit of the on-disk attachment cache"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-file-size-limit", Aliases: []string{"attachment_file_size_limit", "Y"}, EnvVars: []string{"NTFY_ATTACHMENT_FILE_SIZE_LIMIT"}, Value: util.FormatSize(server.DefaultAttachmentFileSizeLimit), Usage: "per-file attachment size limit (e.g. 300k, 2M, 100M)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-file-size-limit", Aliases: []string{"attachment_file_size_limit", "Y"}, EnvVars: []string{"NTFY_ATTACHMENT_FILE_SIZE_LIMIT"}, Value: util.FormatSize(server.DefaultAttachmentFileSizeLimit), Usage: "per-file attachment size limit (e.g. 300k, 2M, 100M)"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-expiry-duration", Aliases: []string{"attachment_expiry_duration", "X"}, EnvVars: []string{"NTFY_ATTACHMENT_EXPIRY_DURATION"}, Value: util.FormatDuration(server.DefaultAttachmentExpiryDuration), Usage: "duration after which uploaded attachments will be deleted (e.g. 3h, 20h)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-expiry-duration", Aliases: []string{"attachment_expiry_duration", "X"}, EnvVars: []string{"NTFY_ATTACHMENT_EXPIRY_DURATION"}, Value: util.FormatDuration(server.DefaultAttachmentExpiryDuration), Usage: "duration after which uploaded attachments will be deleted (e.g. 3h, 20h)"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "template-dir", Aliases: []string{"template_dir"}, EnvVars: []string{"NTFY_TEMPLATE_DIR"}, Usage: "directory to load named message templates from"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "template-dir", Aliases: []string{"template_dir"}, EnvVars: []string{"NTFY_TEMPLATE_DIR"}, Value: server.DefaultTemplateDir, Usage: "directory to load named message templates from"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "keepalive-interval", Aliases: []string{"keepalive_interval", "k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: util.FormatDuration(server.DefaultKeepaliveInterval), Usage: "interval of keepalive messages"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "keepalive-interval", Aliases: []string{"keepalive_interval", "k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: util.FormatDuration(server.DefaultKeepaliveInterval), Usage: "interval of keepalive messages"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "manager-interval", Aliases: []string{"manager_interval", "m"}, EnvVars: []string{"NTFY_MANAGER_INTERVAL"}, Value: util.FormatDuration(server.DefaultManagerInterval), Usage: "interval of for message pruning and stats printing"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "manager-interval", Aliases: []string{"manager_interval", "m"}, EnvVars: []string{"NTFY_MANAGER_INTERVAL"}, Value: util.FormatDuration(server.DefaultManagerInterval), Usage: "interval of for message pruning and stats printing"}),
|
||||||
altsrc.NewStringSliceFlag(&cli.StringSliceFlag{Name: "disallowed-topics", Aliases: []string{"disallowed_topics"}, EnvVars: []string{"NTFY_DISALLOWED_TOPICS"}, Usage: "topics that are not allowed to be used"}),
|
altsrc.NewStringSliceFlag(&cli.StringSliceFlag{Name: "disallowed-topics", Aliases: []string{"disallowed_topics"}, EnvVars: []string{"NTFY_DISALLOWED_TOPICS"}, Usage: "topics that are not allowed to be used"}),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"heckel.io/ntfy/v2/server"
|
||||||
"heckel.io/ntfy/v2/user"
|
"heckel.io/ntfy/v2/user"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -25,7 +26,7 @@ func init() {
|
|||||||
|
|
||||||
var flagsUser = append(
|
var flagsUser = append(
|
||||||
append([]cli.Flag{}, flagsDefault...),
|
append([]cli.Flag{}, flagsDefault...),
|
||||||
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: defaultServerConfigFile, DefaultText: defaultServerConfigFile, Usage: "config file"},
|
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: server.DefaultConfigFile, DefaultText: server.DefaultConfigFile, Usage: "config file"},
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-file", Aliases: []string{"auth_file", "H"}, EnvVars: []string{"NTFY_AUTH_FILE"}, Usage: "auth database file used for access control"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-file", Aliases: []string{"auth_file", "H"}, EnvVars: []string{"NTFY_AUTH_FILE"}, Usage: "auth database file used for access control"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-default-access", Aliases: []string{"auth_default_access", "p"}, EnvVars: []string{"NTFY_AUTH_DEFAULT_ACCESS"}, Value: "read-write", Usage: "default permissions if no matching entries in the auth database are found"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-default-access", Aliases: []string{"auth_default_access", "p"}, EnvVars: []string{"NTFY_AUTH_DEFAULT_ACCESS"}, Value: "read-write", Usage: "default permissions if no matching entries in the auth database are found"}),
|
||||||
)
|
)
|
||||||
|
|||||||
196
docs/publish.md
196
docs/publish.md
@@ -944,27 +944,165 @@ Templating lets you **format a JSON message body into human-friendly message and
|
|||||||
[Go templates](https://pkg.go.dev/text/template) (see tutorials [here](https://blog.gopheracademy.com/advent-2017/using-go-templates/),
|
[Go templates](https://pkg.go.dev/text/template) (see tutorials [here](https://blog.gopheracademy.com/advent-2017/using-go-templates/),
|
||||||
[here](https://www.digitalocean.com/community/tutorials/how-to-use-templates-in-go), and
|
[here](https://www.digitalocean.com/community/tutorials/how-to-use-templates-in-go), and
|
||||||
[here](https://developer.hashicorp.com/nomad/tutorials/templates/go-template-syntax)). This is specifically useful when
|
[here](https://developer.hashicorp.com/nomad/tutorials/templates/go-template-syntax)). This is specifically useful when
|
||||||
**combined with webhooks** from services such as GitHub, Grafana, or other services that emit JSON webhooks.
|
**combined with webhooks** from services such as [GitHub](https://docs.github.com/en/webhooks/about-webhooks),
|
||||||
|
[Grafana](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier/),
|
||||||
|
[Alertmanager](https://prometheus.io/docs/alerting/latest/configuration/#webhook_config), or other services that emit JSON webhooks.
|
||||||
|
|
||||||
Instead of using a separate bridge program to parse the webhook body into the format ntfy expects, you can include a templated
|
Instead of using a separate bridge program to parse the webhook body into the format ntfy expects, you can include a templated
|
||||||
message and/or a templated title which will be populated based on the fields of the webhook body (so long as the webhook body
|
message and/or a templated title which will be populated based on the fields of the webhook body (so long as the webhook body
|
||||||
is valid JSON).
|
is valid JSON).
|
||||||
|
|
||||||
You can enable templating by setting the `X-Template` header (or its aliases `Template` or `tpl`):
|
You can enable templating by setting the `X-Template` header (or its aliases `Template` or `tpl`, or the query parameter `?template=...`):
|
||||||
|
|
||||||
|
* **Pre-defined template files**: Setting the `X-Template` header or query parameter to a template name (e.g. `?template=github`)
|
||||||
|
to a pre-defined template name (e.g. `github`, `grafana`, or `alertmanager`) will use the template with that name.
|
||||||
|
See [pre-defined templates](#pre-defined-templates) for more details.
|
||||||
|
* **Custom template files**: Setting the `X-Template` header or query parameter to a custom template name (e.g. `?template=myapp`)
|
||||||
|
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`)
|
* **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` and/or `title` will be parsed as a Go template.
|
||||||
See [Inline templating](#inline-templating) and [Template syntax](#template-syntax) for details on how to use Go
|
See [inline templating](#inline-templating) for more details.
|
||||||
templates in your messages and titles.
|
|
||||||
* **Pre-defined template files**: You can also set `X-Template` header or query parameter to a template name (e.g. `?template=github`).
|
To learn the basics of Go's templating language, please see [template syntax](#template-syntax).
|
||||||
ntfy will then read the template from either the built-in pre-defined template files, or from the template files defined in
|
|
||||||
the `template-dir`. See [Template files](#pre-defined-templates) for more details.
|
### Pre-defined templates
|
||||||
|
|
||||||
|
When `X-Template: <name>` (aliases: `Template: <name>`, `Tpl: <name>`) or `?template=<name>` is set, ntfy will transform the
|
||||||
|
message and/or title based on one of the built-in pre-defined templates
|
||||||
|
|
||||||
|
The following **pre-defined templates** are available:
|
||||||
|
|
||||||
|
* `github`: Formats a subset of [GitHub webhook](https://docs.github.com/en/webhooks/about-webhooks) payloads (PRs, issues, new star, new watcher, new comment)
|
||||||
|
* `grafana`: Formats [Grafana webhook](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier/) payloads (firing/resolved alerts)
|
||||||
|
* `alertmanager`: Formats [Alertmanager webhook](https://prometheus.io/docs/alerting/latest/configuration/#webhook_config) payloads (firing/resolved alerts)
|
||||||
|
|
||||||
|
Here's an example of how to use the pre-defined `github` template: First, configure the webhook in GitHub to send a webhook to your ntfy topic, e.g. `https://ntfy.sh/mytopic?template=github`.
|
||||||
|
<figure markdown>
|
||||||
|
{ width=600 }
|
||||||
|
<figcaption>GitHub webhook configuration</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
After that, when GitHub publishes a JSON webhook to the topic, ntfy will transform it according to the template rules
|
||||||
|
and you'll receive notifications in the ntfy app. Here's an example for when somebody stars your repository:
|
||||||
|
|
||||||
|
<figure markdown>
|
||||||
|
{ width=500 }
|
||||||
|
<figcaption>Receiving a webhook, formatted using the pre-defined "github" template</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
### Custom templates
|
||||||
|
|
||||||
|
To define **your own custom templates**, place a template file in the template directory (defaults to `/etc/ntfy/templates`, can be overridden with `template-dir`)
|
||||||
|
and set the `X-Template` header or query parameter to the name of the template file (without the `.yml` extension).
|
||||||
|
|
||||||
|
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,
|
||||||
|
which are interpreted as Go templates.
|
||||||
|
|
||||||
|
Here's an **example custom template**:
|
||||||
|
|
||||||
|
=== "Custom template (/etc/ntfy/templates/myapp.yml)"
|
||||||
|
```yaml
|
||||||
|
title: |
|
||||||
|
{{- if eq .status "firing" }}
|
||||||
|
{{- if gt .percent 90.0 }}🚨 Critical alert
|
||||||
|
{{- else }}⚠️ Alert{{- end }}
|
||||||
|
{{- else if eq .status "resolved" }}
|
||||||
|
✅ Alert resolved
|
||||||
|
{{- end }}
|
||||||
|
message: |
|
||||||
|
Status: {{ .status }}
|
||||||
|
Type: {{ .type | upper }} ({{ .percent }}%)
|
||||||
|
Server: {{ .server }}
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you have the template file in place, you can send the payload to your topic using the `X-Template`
|
||||||
|
header or query parameter:
|
||||||
|
|
||||||
|
=== "Command line (curl)"
|
||||||
|
```
|
||||||
|
echo '{"status":"firing","type":"cpu","server":"ntfy.sh","percent":99}' | \
|
||||||
|
curl -sT- "https://ntfy.example.com/mytopic?template=myapp"
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "ntfy CLI"
|
||||||
|
```
|
||||||
|
echo '{"status":"firing","type":"cpu","server":"ntfy.sh","percent":99}' | \
|
||||||
|
ntfy publish --template=myapp https://ntfy.example.com/mytopic
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "HTTP"
|
||||||
|
``` http
|
||||||
|
POST /mytopic?template=myapp HTTP/1.1
|
||||||
|
Host: ntfy.example.com
|
||||||
|
|
||||||
|
{
|
||||||
|
"status": "firing",
|
||||||
|
"type": "cpu",
|
||||||
|
"server": "ntfy.sh",
|
||||||
|
"percent": 99
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "JavaScript"
|
||||||
|
``` javascript
|
||||||
|
fetch('https://ntfy.example.com/mytopic?template=myapp', {
|
||||||
|
method: 'POST',
|
||||||
|
body: '{"status":"firing","type":"cpu","server":"ntfy.sh","percent":99}'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Go"
|
||||||
|
``` go
|
||||||
|
payload := `{"status":"firing","type":"cpu","server":"ntfy.sh","percent":99}`
|
||||||
|
req, _ := http.NewRequest("POST", "https://ntfy.example.com/mytopic?template=myapp", strings.NewReader(payload))
|
||||||
|
http.DefaultClient.Do(req)
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "PowerShell"
|
||||||
|
``` powershell
|
||||||
|
$Request = @{
|
||||||
|
Method = "POST"
|
||||||
|
Uri = "https://ntfy.example.com/mytopic?template=myapp"
|
||||||
|
Body = '{"status":"firing","type":"cpu","server":"ntfy.sh","percent":99}'
|
||||||
|
}
|
||||||
|
Invoke-RestMethod @Request
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Python"
|
||||||
|
``` python
|
||||||
|
requests.post("https://ntfy.example.com/mytopic?template=myapp",
|
||||||
|
json={"status":"firing","type":"cpu","server":"ntfy.sh","percent":99})
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "PHP"
|
||||||
|
``` php-inline
|
||||||
|
file_get_contents('https://ntfy.example.com/mytopic?template=myapp', false, stream_context_create([
|
||||||
|
'http' => [
|
||||||
|
'method' => 'POST',
|
||||||
|
'header' => "Content-Type: application/json",
|
||||||
|
'content' => '{"status":"firing","type":"cpu","server":"ntfy.sh","percent":99}'
|
||||||
|
]
|
||||||
|
]));
|
||||||
|
```
|
||||||
|
|
||||||
|
Which will result in a notification that looks like this:
|
||||||
|
|
||||||
|
<figure markdown>
|
||||||
|
{ width=500 }
|
||||||
|
<figcaption>JSON webhook, transformed using a custom template</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
### Inline templating
|
### Inline templating
|
||||||
|
|
||||||
When `X-Template: 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` and `title` fields of your
|
||||||
webhook payload. This is most useful if no [pre-defined template](#pre-defined-templates) exists, for templated one-off messages,
|
webhook payload.
|
||||||
of if you do not control the ntfy server (e.g., if you're using ntfy.sh). Please consider using [template files](#pre-defined-templates)
|
|
||||||
|
Inline templates are most useful for templated one-off messages, of if you do not control the ntfy server (e.g., if you're using ntfy.sh).
|
||||||
|
Consider using [pre-defined templates](#pre-defined-templates) or [custom templates](#custom-templates) instead,
|
||||||
if you control the ntfy server, as templates are much easier to maintain.
|
if you control the ntfy server, as templates are much easier to maintain.
|
||||||
|
|
||||||
Here's an **example for a Grafana alert**:
|
Here's an **example for a Grafana alert**:
|
||||||
@@ -1078,10 +1216,6 @@ This example uses the `message`/`m` and `title`/`t` query parameters, but obviou
|
|||||||
`Message`/`Title` headers. It will send a notification with a title `phil-pc: A severe error has occurred` and a message
|
`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`.
|
`Error message: Disk has run out of space`.
|
||||||
|
|
||||||
### Pre-defined templates
|
|
||||||
|
|
||||||
XXXXXXXXXXXXxx
|
|
||||||
|
|
||||||
### Template syntax
|
### Template syntax
|
||||||
ntfy uses [Go templates](https://pkg.go.dev/text/template) for its templates, which is arguably one of the most powerful,
|
ntfy uses [Go templates](https://pkg.go.dev/text/template) for its templates, which is arguably one of the most powerful,
|
||||||
yet also one of the worst templating languages out there.
|
yet also one of the worst templating languages out there.
|
||||||
@@ -1101,23 +1235,23 @@ message templating and for transforming the data provided through the JSON paylo
|
|||||||
|
|
||||||
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 templates.
|
||||||
|
|
||||||
* [String Functions](./sprig/strings.md): `trim`, `trunc`, `substr`, `plural`, etc.
|
* [String Functions](publish/template-functions.md#string-functions): `trim`, `trunc`, `substr`, `plural`, etc.
|
||||||
* [String List Functions](./sprig/string_slice.md): `splitList`, `sortAlpha`, etc.
|
* [String List Functions](publish/template-functions.md#string-list-functions): `splitList`, `sortAlpha`, etc.
|
||||||
* [Integer Math Functions](./sprig/math.md): `add`, `max`, `mul`, etc.
|
* [Integer Math Functions](publish/template-functions.md#integer-math-functions): `add`, `max`, `mul`, etc.
|
||||||
* [Integer List Functions](./sprig/integer_slice.md): `until`, `untilStep`
|
* [Integer List Functions](publish/template-functions.md#integer-list-functions): `until`, `untilStep`
|
||||||
* [Date Functions](./sprig/date.md): `now`, `date`, etc.
|
* [Date Functions](publish/template-functions.md#date-functions): `now`, `date`, etc.
|
||||||
* [Defaults Functions](./sprig/defaults.md): `default`, `empty`, `coalesce`, `fromJSON`, `toJSON`, `toPrettyJSON`, `toRawJSON`, `ternary`
|
* [Defaults Functions](publish/template-functions.md#default-functions): `default`, `empty`, `coalesce`, `fromJSON`, `toJSON`, `toPrettyJSON`, `toRawJSON`, `ternary`
|
||||||
* [Encoding Functions](./sprig/encoding.md): `b64enc`, `b64dec`, etc.
|
* [Encoding Functions](publish/template-functions.md#encoding-functions): `b64enc`, `b64dec`, etc.
|
||||||
* [Lists and List Functions](./sprig/lists.md): `list`, `first`, `uniq`, etc.
|
* [Lists and List Functions](publish/template-functions.md#lists-and-list-functions): `list`, `first`, `uniq`, etc.
|
||||||
* [Dictionaries and Dict Functions](./sprig/dicts.md): `get`, `set`, `dict`, `hasKey`, `pluck`, `dig`, etc.
|
* [Dictionaries and Dict Functions](publish/template-functions.md#dictionaries-and-dict-functions): `get`, `set`, `dict`, `hasKey`, `pluck`, `dig`, etc.
|
||||||
* [Type Conversion Functions](./sprig/conversion.md): `atoi`, `int64`, `toString`, etc.
|
* [Type Conversion Functions](publish/template-functions.md#type-conversion-functions): `atoi`, `int64`, `toString`, etc.
|
||||||
* [Path and Filepath Functions](./sprig/paths.md): `base`, `dir`, `ext`, `clean`, `isAbs`, `osBase`, `osDir`, `osExt`, `osClean`, `osIsAbs`
|
* [Path and Filepath Functions](publish/template-functions.md#path-and-filepath-functions): `base`, `dir`, `ext`, `clean`, `isAbs`, `osBase`, `osDir`, `osExt`, `osClean`, `osIsAbs`
|
||||||
* [Flow Control Functions]( ./sprig/flow_control.md): `fail`
|
* [Flow Control Functions](publish/template-functions.md#flow-control-functions): `fail`
|
||||||
* Advanced Functions
|
* Advanced Functions
|
||||||
* [UUID Functions](./sprig/uuid.md): `uuidv4`
|
* [UUID Functions](publish/template-functions.md#uuid-functions): `uuidv4`
|
||||||
* [Reflection](./sprig/reflection.md): `typeOf`, `kindIs`, `typeIsLike`, etc.
|
* [Reflection](publish/template-functions.md#reflection-functions): `typeOf`, `kindIs`, `typeIsLike`, etc.
|
||||||
* [Cryptographic and Security Functions](./sprig/crypto.md): `sha256sum`, etc.
|
* [Cryptographic and Security Functions](publish/template-functions.md#cryptographic-and-security-functions): `sha256sum`, etc.
|
||||||
* [URL](./sprig/url.md): `urlParse`, `urlJoin`
|
* [URL](publish/template-functions.md#url-functions): `urlParse`, `urlJoin`
|
||||||
|
|
||||||
|
|
||||||
## Publish as JSON
|
## Publish as JSON
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,24 +0,0 @@
|
|||||||
# Template Functions
|
|
||||||
|
|
||||||
ntfy includes a (reduced) version of [Sprig](https://github.com/Masterminds/sprig) to add functions that can be used
|
|
||||||
when you are using the [message template](publish.md#message-templating) feature.
|
|
||||||
|
|
||||||
Below are the functions that are available to use inside your message/title templates.
|
|
||||||
|
|
||||||
* [String Functions](./sprig/strings.md): `trim`, `trunc`, `substr`, `plural`, etc.
|
|
||||||
* [String List Functions](./sprig/string_slice.md): `splitList`, `sortAlpha`, etc.
|
|
||||||
* [Integer Math Functions](./sprig/math.md): `add`, `max`, `mul`, etc.
|
|
||||||
* [Integer List Functions](./sprig/integer_slice.md): `until`, `untilStep`
|
|
||||||
* [Date Functions](./sprig/date.md): `now`, `date`, etc.
|
|
||||||
* [Defaults Functions](./sprig/defaults.md): `default`, `empty`, `coalesce`, `fromJSON`, `toJSON`, `toPrettyJSON`, `toRawJSON`, `ternary`
|
|
||||||
* [Encoding Functions](./sprig/encoding.md): `b64enc`, `b64dec`, etc.
|
|
||||||
* [Lists and List Functions](./sprig/lists.md): `list`, `first`, `uniq`, etc.
|
|
||||||
* [Dictionaries and Dict Functions](./sprig/dicts.md): `get`, `set`, `dict`, `hasKey`, `pluck`, `dig`, etc.
|
|
||||||
* [Type Conversion Functions](./sprig/conversion.md): `atoi`, `int64`, `toString`, etc.
|
|
||||||
* [Path and Filepath Functions](./sprig/paths.md): `base`, `dir`, `ext`, `clean`, `isAbs`, `osBase`, `osDir`, `osExt`, `osClean`, `osIsAbs`
|
|
||||||
* [Flow Control Functions](./sprig/flow_control.md): `fail`
|
|
||||||
* Advanced Functions
|
|
||||||
* [UUID Functions](./sprig/uuid.md): `uuidv4`
|
|
||||||
* [Reflection](./sprig/reflection.md): `typeOf`, `kindIs`, `typeIsLike`, etc.
|
|
||||||
* [Cryptographic and Security Functions](./sprig/crypto.md): `sha256sum`, etc.
|
|
||||||
* [URL](./sprig/url.md): `urlParse`, `urlJoin`
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# Type Conversion Functions
|
|
||||||
|
|
||||||
The following type conversion functions are provided by Sprig:
|
|
||||||
|
|
||||||
- `atoi`: Convert a string to an integer.
|
|
||||||
- `float64`: Convert to a `float64`.
|
|
||||||
- `int`: Convert to an `int` at the system's width.
|
|
||||||
- `int64`: Convert to an `int64`.
|
|
||||||
- `toDecimal`: Convert a unix octal to a `int64`.
|
|
||||||
- `toString`: Convert to a string.
|
|
||||||
- `toStrings`: Convert a list, slice, or array to a list of strings.
|
|
||||||
|
|
||||||
Only `atoi` requires that the input be a specific type. The others will attempt
|
|
||||||
to convert from any type to the destination type. For example, `int64` can convert
|
|
||||||
floats to ints, and it can also convert strings to ints.
|
|
||||||
|
|
||||||
## toStrings
|
|
||||||
|
|
||||||
Given a list-like collection, produce a slice of strings.
|
|
||||||
|
|
||||||
```
|
|
||||||
list 1 2 3 | toStrings
|
|
||||||
```
|
|
||||||
|
|
||||||
The above converts `1` to `"1"`, `2` to `"2"`, and so on, and then returns
|
|
||||||
them as a list.
|
|
||||||
|
|
||||||
## toDecimal
|
|
||||||
|
|
||||||
Given a unix octal permission, produce a decimal.
|
|
||||||
|
|
||||||
```
|
|
||||||
"0777" | toDecimal
|
|
||||||
```
|
|
||||||
|
|
||||||
The above converts `0777` to `511` and returns the value as an int64.
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Cryptographic and Security Functions
|
|
||||||
|
|
||||||
Sprig provides a couple of advanced cryptographic functions.
|
|
||||||
|
|
||||||
## sha1sum
|
|
||||||
|
|
||||||
The `sha1sum` function receives a string, and computes it's SHA1 digest.
|
|
||||||
|
|
||||||
```
|
|
||||||
sha1sum "Hello world!"
|
|
||||||
```
|
|
||||||
|
|
||||||
## sha256sum
|
|
||||||
|
|
||||||
The `sha256sum` function receives a string, and computes it's SHA256 digest.
|
|
||||||
|
|
||||||
```
|
|
||||||
sha256sum "Hello world!"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above will compute the SHA 256 sum in an "ASCII armored" format that is
|
|
||||||
safe to print.
|
|
||||||
|
|
||||||
## sha512sum
|
|
||||||
|
|
||||||
The `sha512sum` function receives a string, and computes it's SHA512 digest.
|
|
||||||
|
|
||||||
```
|
|
||||||
sha512sum "Hello world!"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above will compute the SHA 512 sum in an "ASCII armored" format that is
|
|
||||||
safe to print.
|
|
||||||
|
|
||||||
## adler32sum
|
|
||||||
|
|
||||||
The `adler32sum` function receives a string, and computes its Adler-32 checksum.
|
|
||||||
|
|
||||||
```
|
|
||||||
adler32sum "Hello world!"
|
|
||||||
```
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
# Date Functions
|
|
||||||
|
|
||||||
## now
|
|
||||||
|
|
||||||
The current date/time. Use this in conjunction with other date functions.
|
|
||||||
|
|
||||||
## ago
|
|
||||||
|
|
||||||
The `ago` function returns duration from time.Now in seconds resolution.
|
|
||||||
|
|
||||||
```
|
|
||||||
ago .CreatedAt
|
|
||||||
```
|
|
||||||
|
|
||||||
returns in `time.Duration` String() format
|
|
||||||
|
|
||||||
```
|
|
||||||
2h34m7s
|
|
||||||
```
|
|
||||||
|
|
||||||
## date
|
|
||||||
|
|
||||||
The `date` function formats a date.
|
|
||||||
|
|
||||||
Format the date to YEAR-MONTH-DAY:
|
|
||||||
|
|
||||||
```
|
|
||||||
now | date "2006-01-02"
|
|
||||||
```
|
|
||||||
|
|
||||||
Date formatting in Go is a [little bit different](https://pauladamsmith.com/blog/2011/05/go_time.html).
|
|
||||||
|
|
||||||
In short, take this as the base date:
|
|
||||||
|
|
||||||
```
|
|
||||||
Mon Jan 2 15:04:05 MST 2006
|
|
||||||
```
|
|
||||||
|
|
||||||
Write it in the format you want. Above, `2006-01-02` is the same date, but
|
|
||||||
in the format we want.
|
|
||||||
|
|
||||||
## dateInZone
|
|
||||||
|
|
||||||
Same as `date`, but with a timezone.
|
|
||||||
|
|
||||||
```
|
|
||||||
dateInZone "2006-01-02" (now) "UTC"
|
|
||||||
```
|
|
||||||
|
|
||||||
## duration
|
|
||||||
|
|
||||||
Formats a given amount of seconds as a `time.Duration`.
|
|
||||||
|
|
||||||
This returns 1m35s
|
|
||||||
|
|
||||||
```
|
|
||||||
duration "95"
|
|
||||||
```
|
|
||||||
|
|
||||||
## durationRound
|
|
||||||
|
|
||||||
Rounds a given duration to the most significant unit. Strings and `time.Duration`
|
|
||||||
gets parsed as a duration, while a `time.Time` is calculated as the duration since.
|
|
||||||
|
|
||||||
This return 2h
|
|
||||||
|
|
||||||
```
|
|
||||||
durationRound "2h10m5s"
|
|
||||||
```
|
|
||||||
|
|
||||||
This returns 3mo
|
|
||||||
|
|
||||||
```
|
|
||||||
durationRound "2400h10m5s"
|
|
||||||
```
|
|
||||||
|
|
||||||
## unixEpoch
|
|
||||||
|
|
||||||
Returns the seconds since the unix epoch for a `time.Time`.
|
|
||||||
|
|
||||||
```
|
|
||||||
now | unixEpoch
|
|
||||||
```
|
|
||||||
|
|
||||||
## dateModify, mustDateModify
|
|
||||||
|
|
||||||
The `dateModify` takes a modification and a date and returns the timestamp.
|
|
||||||
|
|
||||||
Subtract an hour and thirty minutes from the current time:
|
|
||||||
|
|
||||||
```
|
|
||||||
now | date_modify "-1.5h"
|
|
||||||
```
|
|
||||||
|
|
||||||
If the modification format is wrong `dateModify` will return the date unmodified. `mustDateModify` will return an error otherwise.
|
|
||||||
|
|
||||||
## htmlDate
|
|
||||||
|
|
||||||
The `htmlDate` function formats a date for inserting into an HTML date picker
|
|
||||||
input field.
|
|
||||||
|
|
||||||
```
|
|
||||||
now | htmlDate
|
|
||||||
```
|
|
||||||
|
|
||||||
## htmlDateInZone
|
|
||||||
|
|
||||||
Same as htmlDate, but with a timezone.
|
|
||||||
|
|
||||||
```
|
|
||||||
htmlDateInZone (now) "UTC"
|
|
||||||
```
|
|
||||||
|
|
||||||
## toDate, mustToDate
|
|
||||||
|
|
||||||
`toDate` converts a string to a date. The first argument is the date layout and
|
|
||||||
the second the date string. If the string can't be convert it returns the zero
|
|
||||||
value.
|
|
||||||
`mustToDate` will return an error in case the string cannot be converted.
|
|
||||||
|
|
||||||
This is useful when you want to convert a string date to another format
|
|
||||||
(using pipe). The example below converts "2017-12-31" to "31/12/2017".
|
|
||||||
|
|
||||||
```
|
|
||||||
toDate "2006-01-02" "2017-12-31" | date "02/01/2006"
|
|
||||||
```
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
# Default Functions
|
|
||||||
|
|
||||||
Sprig provides tools for setting default values for templates.
|
|
||||||
|
|
||||||
## default
|
|
||||||
|
|
||||||
To set a simple default value, use `default`:
|
|
||||||
|
|
||||||
```
|
|
||||||
default "foo" .Bar
|
|
||||||
```
|
|
||||||
|
|
||||||
In the above, if `.Bar` evaluates to a non-empty value, it will be used. But if
|
|
||||||
it is empty, `foo` will be returned instead.
|
|
||||||
|
|
||||||
The definition of "empty" depends on type:
|
|
||||||
|
|
||||||
- Numeric: 0
|
|
||||||
- String: ""
|
|
||||||
- Lists: `[]`
|
|
||||||
- Dicts: `{}`
|
|
||||||
- Boolean: `false`
|
|
||||||
- And always `nil` (aka null)
|
|
||||||
|
|
||||||
For structs, there is no definition of empty, so a struct will never return the
|
|
||||||
default.
|
|
||||||
|
|
||||||
## empty
|
|
||||||
|
|
||||||
The `empty` function returns `true` if the given value is considered empty, and
|
|
||||||
`false` otherwise. The empty values are listed in the `default` section.
|
|
||||||
|
|
||||||
```
|
|
||||||
empty .Foo
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that in Go template conditionals, emptiness is calculated for you. Thus,
|
|
||||||
you rarely need `if empty .Foo`. Instead, just use `if .Foo`.
|
|
||||||
|
|
||||||
## coalesce
|
|
||||||
|
|
||||||
The `coalesce` function takes a list of values and returns the first non-empty
|
|
||||||
one.
|
|
||||||
|
|
||||||
```
|
|
||||||
coalesce 0 1 2
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `1`.
|
|
||||||
|
|
||||||
This function is useful for scanning through multiple variables or values:
|
|
||||||
|
|
||||||
```
|
|
||||||
coalesce .name .parent.name "Matt"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above will first check to see if `.name` is empty. If it is not, it will return
|
|
||||||
that value. If it _is_ empty, `coalesce` will evaluate `.parent.name` for emptiness.
|
|
||||||
Finally, if both `.name` and `.parent.name` are empty, it will return `Matt`.
|
|
||||||
|
|
||||||
## all
|
|
||||||
|
|
||||||
The `all` function takes a list of values and returns true if all values are non-empty.
|
|
||||||
|
|
||||||
```
|
|
||||||
all 0 1 2
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `false`.
|
|
||||||
|
|
||||||
This function is useful for evaluating multiple conditions of variables or values:
|
|
||||||
|
|
||||||
```
|
|
||||||
all (eq .Request.TLS.Version 0x0304) (.Request.ProtoAtLeast 2 0) (eq .Request.Method "POST")
|
|
||||||
```
|
|
||||||
|
|
||||||
The above will check http.Request is POST with tls 1.3 and http/2.
|
|
||||||
|
|
||||||
## any
|
|
||||||
|
|
||||||
The `any` function takes a list of values and returns true if any value is non-empty.
|
|
||||||
|
|
||||||
```
|
|
||||||
any 0 1 2
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `true`.
|
|
||||||
|
|
||||||
This function is useful for evaluating multiple conditions of variables or values:
|
|
||||||
|
|
||||||
```
|
|
||||||
any (eq .Request.Method "GET") (eq .Request.Method "POST") (eq .Request.Method "OPTIONS")
|
|
||||||
```
|
|
||||||
|
|
||||||
The above will check http.Request method is one of GET/POST/OPTIONS.
|
|
||||||
|
|
||||||
## fromJSON, mustFromJSON
|
|
||||||
|
|
||||||
`fromJSON` decodes a JSON document into a structure. If the input cannot be decoded as JSON the function will return an empty string.
|
|
||||||
`mustFromJSON` will return an error in case the JSON is invalid.
|
|
||||||
|
|
||||||
```
|
|
||||||
fromJSON "{\"foo\": 55}"
|
|
||||||
```
|
|
||||||
|
|
||||||
## toJSON, mustToJSON
|
|
||||||
|
|
||||||
The `toJSON` function encodes an item into a JSON string. If the item cannot be converted to JSON the function will return an empty string.
|
|
||||||
`mustToJSON` will return an error in case the item cannot be encoded in JSON.
|
|
||||||
|
|
||||||
```
|
|
||||||
toJSON .Item
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns JSON string representation of `.Item`.
|
|
||||||
|
|
||||||
## toPrettyJSON, mustToPrettyJSON
|
|
||||||
|
|
||||||
The `toPrettyJSON` function encodes an item into a pretty (indented) JSON string.
|
|
||||||
|
|
||||||
```
|
|
||||||
toPrettyJSON .Item
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns indented JSON string representation of `.Item`.
|
|
||||||
|
|
||||||
## toRawJSON, mustToRawJSON
|
|
||||||
|
|
||||||
The `toRawJSON` function encodes an item into JSON string with HTML characters unescaped.
|
|
||||||
|
|
||||||
```
|
|
||||||
toRawJSON .Item
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns unescaped JSON string representation of `.Item`.
|
|
||||||
|
|
||||||
## ternary
|
|
||||||
|
|
||||||
The `ternary` function takes two values, and a test value. If the test value is
|
|
||||||
true, the first value will be returned. If the test value is empty, the second
|
|
||||||
value will be returned. This is similar to the c ternary operator.
|
|
||||||
|
|
||||||
### true test value
|
|
||||||
|
|
||||||
```
|
|
||||||
ternary "foo" "bar" true
|
|
||||||
```
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
```
|
|
||||||
true | ternary "foo" "bar"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `"foo"`.
|
|
||||||
|
|
||||||
### false test value
|
|
||||||
|
|
||||||
```
|
|
||||||
ternary "foo" "bar" false
|
|
||||||
```
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
```
|
|
||||||
false | ternary "foo" "bar"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `"bar"`.
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
# Dictionaries and Dict Functions
|
|
||||||
|
|
||||||
Sprig provides a key/value storage type called a `dict` (short for "dictionary",
|
|
||||||
as in Python). A `dict` is an _unorder_ type.
|
|
||||||
|
|
||||||
The key to a dictionary **must be a string**. However, the value can be any
|
|
||||||
type, even another `dict` or `list`.
|
|
||||||
|
|
||||||
Unlike `list`s, `dict`s are not immutable. The `set` and `unset` functions will
|
|
||||||
modify the contents of a dictionary.
|
|
||||||
|
|
||||||
## dict
|
|
||||||
|
|
||||||
Creating dictionaries is done by calling the `dict` function and passing it a
|
|
||||||
list of pairs.
|
|
||||||
|
|
||||||
The following creates a dictionary with three items:
|
|
||||||
|
|
||||||
```
|
|
||||||
$myDict := dict "name1" "value1" "name2" "value2" "name3" "value 3"
|
|
||||||
```
|
|
||||||
|
|
||||||
## get
|
|
||||||
|
|
||||||
Given a map and a key, get the value from the map.
|
|
||||||
|
|
||||||
```
|
|
||||||
get $myDict "name1"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `"value1"`
|
|
||||||
|
|
||||||
Note that if the key is not found, this operation will simply return `""`. No error
|
|
||||||
will be generated.
|
|
||||||
|
|
||||||
## set
|
|
||||||
|
|
||||||
Use `set` to add a new key/value pair to a dictionary.
|
|
||||||
|
|
||||||
```
|
|
||||||
$_ := set $myDict "name4" "value4"
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that `set` _returns the dictionary_ (a requirement of Go template functions),
|
|
||||||
so you may need to trap the value as done above with the `$_` assignment.
|
|
||||||
|
|
||||||
## unset
|
|
||||||
|
|
||||||
Given a map and a key, delete the key from the map.
|
|
||||||
|
|
||||||
```
|
|
||||||
$_ := unset $myDict "name4"
|
|
||||||
```
|
|
||||||
|
|
||||||
As with `set`, this returns the dictionary.
|
|
||||||
|
|
||||||
Note that if the key is not found, this operation will simply return. No error
|
|
||||||
will be generated.
|
|
||||||
|
|
||||||
## hasKey
|
|
||||||
|
|
||||||
The `hasKey` function returns `true` if the given dict contains the given key.
|
|
||||||
|
|
||||||
```
|
|
||||||
hasKey $myDict "name1"
|
|
||||||
```
|
|
||||||
|
|
||||||
If the key is not found, this returns `false`.
|
|
||||||
|
|
||||||
## pluck
|
|
||||||
|
|
||||||
The `pluck` function makes it possible to give one key and multiple maps, and
|
|
||||||
get a list of all of the matches:
|
|
||||||
|
|
||||||
```
|
|
||||||
pluck "name1" $myDict $myOtherDict
|
|
||||||
```
|
|
||||||
|
|
||||||
The above will return a `list` containing every found value (`[value1 otherValue1]`).
|
|
||||||
|
|
||||||
If the give key is _not found_ in a map, that map will not have an item in the
|
|
||||||
list (and the length of the returned list will be less than the number of dicts
|
|
||||||
in the call to `pluck`.
|
|
||||||
|
|
||||||
If the key is _found_ but the value is an empty value, that value will be
|
|
||||||
inserted.
|
|
||||||
|
|
||||||
A common idiom in Sprig templates is to uses `pluck... | first` to get the first
|
|
||||||
matching key out of a collection of dictionaries.
|
|
||||||
|
|
||||||
## dig
|
|
||||||
|
|
||||||
The `dig` function traverses a nested set of dicts, selecting keys from a list
|
|
||||||
of values. It returns a default value if any of the keys are not found at the
|
|
||||||
associated dict.
|
|
||||||
|
|
||||||
```
|
|
||||||
dig "user" "role" "humanName" "guest" $dict
|
|
||||||
```
|
|
||||||
|
|
||||||
Given a dict structured like
|
|
||||||
```
|
|
||||||
{
|
|
||||||
user: {
|
|
||||||
role: {
|
|
||||||
humanName: "curator"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
the above would return `"curator"`. If the dict lacked even a `user` field,
|
|
||||||
the result would be `"guest"`.
|
|
||||||
|
|
||||||
Dig can be very useful in cases where you'd like to avoid guard clauses,
|
|
||||||
especially since Go's template package's `and` doesn't shortcut. For instance
|
|
||||||
`and a.maybeNil a.maybeNil.iNeedThis` will always evaluate
|
|
||||||
`a.maybeNil.iNeedThis`, and panic if `a` lacks a `maybeNil` field.)
|
|
||||||
|
|
||||||
`dig` accepts its dict argument last in order to support pipelining.
|
|
||||||
|
|
||||||
## keys
|
|
||||||
|
|
||||||
The `keys` function will return a `list` of all of the keys in one or more `dict`
|
|
||||||
types. Since a dictionary is _unordered_, the keys will not be in a predictable order.
|
|
||||||
They can be sorted with `sortAlpha`.
|
|
||||||
|
|
||||||
```
|
|
||||||
keys $myDict | sortAlpha
|
|
||||||
```
|
|
||||||
|
|
||||||
When supplying multiple dictionaries, the keys will be concatenated. Use the `uniq`
|
|
||||||
function along with `sortAlpha` to get a unqiue, sorted list of keys.
|
|
||||||
|
|
||||||
```
|
|
||||||
keys $myDict $myOtherDict | uniq | sortAlpha
|
|
||||||
```
|
|
||||||
|
|
||||||
## pick
|
|
||||||
|
|
||||||
The `pick` function selects just the given keys out of a dictionary, creating a
|
|
||||||
new `dict`.
|
|
||||||
|
|
||||||
```
|
|
||||||
$new := pick $myDict "name1" "name2"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `{name1: value1, name2: value2}`
|
|
||||||
|
|
||||||
## omit
|
|
||||||
|
|
||||||
The `omit` function is similar to `pick`, except it returns a new `dict` with all
|
|
||||||
the keys that _do not_ match the given keys.
|
|
||||||
|
|
||||||
```
|
|
||||||
$new := omit $myDict "name1" "name3"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `{name2: value2}`
|
|
||||||
|
|
||||||
## values
|
|
||||||
|
|
||||||
The `values` function is similar to `keys`, except it returns a new `list` with
|
|
||||||
all the values of the source `dict` (only one dictionary is supported).
|
|
||||||
|
|
||||||
```
|
|
||||||
$vals := values $myDict
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `list["value1", "value2", "value 3"]`. Note that the `values`
|
|
||||||
function gives no guarantees about the result ordering- if you care about this,
|
|
||||||
then use `sortAlpha`.
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# Encoding Functions
|
|
||||||
|
|
||||||
Sprig has the following encoding and decoding functions:
|
|
||||||
|
|
||||||
- `b64enc`/`b64dec`: Encode or decode with Base64
|
|
||||||
- `b32enc`/`b32dec`: Encode or decode with Base32
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# Flow Control Functions
|
|
||||||
|
|
||||||
## fail
|
|
||||||
|
|
||||||
Unconditionally returns an empty `string` and an `error` with the specified
|
|
||||||
text. This is useful in scenarios where other conditionals have determined that
|
|
||||||
template rendering should fail.
|
|
||||||
|
|
||||||
```
|
|
||||||
fail "Please accept the end user license agreement"
|
|
||||||
```
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Integer List Functions
|
|
||||||
|
|
||||||
## until
|
|
||||||
|
|
||||||
The `until` function builds a range of integers.
|
|
||||||
|
|
||||||
```
|
|
||||||
until 5
|
|
||||||
```
|
|
||||||
|
|
||||||
The above generates the list `[0, 1, 2, 3, 4]`.
|
|
||||||
|
|
||||||
This is useful for looping with `range $i, $e := until 5`.
|
|
||||||
|
|
||||||
## untilStep
|
|
||||||
|
|
||||||
Like `until`, `untilStep` generates a list of counting integers. But it allows
|
|
||||||
you to define a start, stop, and step:
|
|
||||||
|
|
||||||
```
|
|
||||||
untilStep 3 6 2
|
|
||||||
```
|
|
||||||
|
|
||||||
The above will produce `[3 5]` by starting with 3, and adding 2 until it is equal
|
|
||||||
or greater than 6. This is similar to Python's `range` function.
|
|
||||||
|
|
||||||
## seq
|
|
||||||
|
|
||||||
Works like the bash `seq` command.
|
|
||||||
* 1 parameter (end) - will generate all counting integers between 1 and `end` inclusive.
|
|
||||||
* 2 parameters (start, end) - will generate all counting integers between `start` and `end` inclusive incrementing or decrementing by 1.
|
|
||||||
* 3 parameters (start, step, end) - will generate all counting integers between `start` and `end` inclusive incrementing or decrementing by `step`.
|
|
||||||
|
|
||||||
```
|
|
||||||
seq 5 => 1 2 3 4 5
|
|
||||||
seq -3 => 1 0 -1 -2 -3
|
|
||||||
seq 0 2 => 0 1 2
|
|
||||||
seq 2 -2 => 2 1 0 -1 -2
|
|
||||||
seq 0 2 10 => 0 2 4 6 8 10
|
|
||||||
seq 0 -2 -5 => 0 -2 -4
|
|
||||||
```
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
# Lists and List Functions
|
|
||||||
|
|
||||||
Sprig provides a simple `list` type that can contain arbitrary sequential lists
|
|
||||||
of data. This is similar to arrays or slices, but lists are designed to be used
|
|
||||||
as immutable data types.
|
|
||||||
|
|
||||||
Create a list of integers:
|
|
||||||
|
|
||||||
```
|
|
||||||
$myList := list 1 2 3 4 5
|
|
||||||
```
|
|
||||||
|
|
||||||
The above creates a list of `[1 2 3 4 5]`.
|
|
||||||
|
|
||||||
## first, mustFirst
|
|
||||||
|
|
||||||
To get the head item on a list, use `first`.
|
|
||||||
|
|
||||||
`first $myList` returns `1`
|
|
||||||
|
|
||||||
`first` panics if there is a problem while `mustFirst` returns an error to the
|
|
||||||
template engine if there is a problem.
|
|
||||||
|
|
||||||
## rest, mustRest
|
|
||||||
|
|
||||||
To get the tail of the list (everything but the first item), use `rest`.
|
|
||||||
|
|
||||||
`rest $myList` returns `[2 3 4 5]`
|
|
||||||
|
|
||||||
`rest` panics if there is a problem while `mustRest` returns an error to the
|
|
||||||
template engine if there is a problem.
|
|
||||||
|
|
||||||
## last, mustLast
|
|
||||||
|
|
||||||
To get the last item on a list, use `last`:
|
|
||||||
|
|
||||||
`last $myList` returns `5`. This is roughly analogous to reversing a list and
|
|
||||||
then calling `first`.
|
|
||||||
|
|
||||||
`last` panics if there is a problem while `mustLast` returns an error to the
|
|
||||||
template engine if there is a problem.
|
|
||||||
|
|
||||||
## initial, mustInitial
|
|
||||||
|
|
||||||
This compliments `last` by returning all _but_ the last element.
|
|
||||||
`initial $myList` returns `[1 2 3 4]`.
|
|
||||||
|
|
||||||
`initial` panics if there is a problem while `mustInitial` returns an error to the
|
|
||||||
template engine if there is a problem.
|
|
||||||
|
|
||||||
## append, mustAppend
|
|
||||||
|
|
||||||
Append a new item to an existing list, creating a new list.
|
|
||||||
|
|
||||||
```
|
|
||||||
$new = append $myList 6
|
|
||||||
```
|
|
||||||
|
|
||||||
The above would set `$new` to `[1 2 3 4 5 6]`. `$myList` would remain unaltered.
|
|
||||||
|
|
||||||
`append` panics if there is a problem while `mustAppend` returns an error to the
|
|
||||||
template engine if there is a problem.
|
|
||||||
|
|
||||||
## prepend, mustPrepend
|
|
||||||
|
|
||||||
Push an element onto the front of a list, creating a new list.
|
|
||||||
|
|
||||||
```
|
|
||||||
prepend $myList 0
|
|
||||||
```
|
|
||||||
|
|
||||||
The above would produce `[0 1 2 3 4 5]`. `$myList` would remain unaltered.
|
|
||||||
|
|
||||||
`prepend` panics if there is a problem while `mustPrepend` returns an error to the
|
|
||||||
template engine if there is a problem.
|
|
||||||
|
|
||||||
## concat
|
|
||||||
|
|
||||||
Concatenate arbitrary number of lists into one.
|
|
||||||
|
|
||||||
```
|
|
||||||
concat $myList ( list 6 7 ) ( list 8 )
|
|
||||||
```
|
|
||||||
|
|
||||||
The above would produce `[1 2 3 4 5 6 7 8]`. `$myList` would remain unaltered.
|
|
||||||
|
|
||||||
## reverse, mustReverse
|
|
||||||
|
|
||||||
Produce a new list with the reversed elements of the given list.
|
|
||||||
|
|
||||||
```
|
|
||||||
reverse $myList
|
|
||||||
```
|
|
||||||
|
|
||||||
The above would generate the list `[5 4 3 2 1]`.
|
|
||||||
|
|
||||||
`reverse` panics if there is a problem while `mustReverse` returns an error to the
|
|
||||||
template engine if there is a problem.
|
|
||||||
|
|
||||||
## uniq, mustUniq
|
|
||||||
|
|
||||||
Generate a list with all of the duplicates removed.
|
|
||||||
|
|
||||||
```
|
|
||||||
list 1 1 1 2 | uniq
|
|
||||||
```
|
|
||||||
|
|
||||||
The above would produce `[1 2]`
|
|
||||||
|
|
||||||
`uniq` panics if there is a problem while `mustUniq` returns an error to the
|
|
||||||
template engine if there is a problem.
|
|
||||||
|
|
||||||
## without, mustWithout
|
|
||||||
|
|
||||||
The `without` function filters items out of a list.
|
|
||||||
|
|
||||||
```
|
|
||||||
without $myList 3
|
|
||||||
```
|
|
||||||
|
|
||||||
The above would produce `[1 2 4 5]`
|
|
||||||
|
|
||||||
Without can take more than one filter:
|
|
||||||
|
|
||||||
```
|
|
||||||
without $myList 1 3 5
|
|
||||||
```
|
|
||||||
|
|
||||||
That would produce `[2 4]`
|
|
||||||
|
|
||||||
`without` panics if there is a problem while `mustWithout` returns an error to the
|
|
||||||
template engine if there is a problem.
|
|
||||||
|
|
||||||
## has, mustHas
|
|
||||||
|
|
||||||
Test to see if a list has a particular element.
|
|
||||||
|
|
||||||
```
|
|
||||||
has 4 $myList
|
|
||||||
```
|
|
||||||
|
|
||||||
The above would return `true`, while `has "hello" $myList` would return false.
|
|
||||||
|
|
||||||
`has` panics if there is a problem while `mustHas` returns an error to the
|
|
||||||
template engine if there is a problem.
|
|
||||||
|
|
||||||
## compact, mustCompact
|
|
||||||
|
|
||||||
Accepts a list and removes entries with empty values.
|
|
||||||
|
|
||||||
```
|
|
||||||
$list := list 1 "a" "foo" ""
|
|
||||||
$copy := compact $list
|
|
||||||
```
|
|
||||||
|
|
||||||
`compact` will return a new list with the empty (i.e., "") item removed.
|
|
||||||
|
|
||||||
`compact` panics if there is a problem and `mustCompact` returns an error to the
|
|
||||||
template engine if there is a problem.
|
|
||||||
|
|
||||||
## slice, mustSlice
|
|
||||||
|
|
||||||
To get partial elements of a list, use `slice list [n] [m]`. It is
|
|
||||||
equivalent of `list[n:m]`.
|
|
||||||
|
|
||||||
- `slice $myList` returns `[1 2 3 4 5]`. It is same as `myList[:]`.
|
|
||||||
- `slice $myList 3` returns `[4 5]`. It is same as `myList[3:]`.
|
|
||||||
- `slice $myList 1 3` returns `[2 3]`. It is same as `myList[1:3]`.
|
|
||||||
- `slice $myList 0 3` returns `[1 2 3]`. It is same as `myList[:3]`.
|
|
||||||
|
|
||||||
`slice` panics if there is a problem while `mustSlice` returns an error to the
|
|
||||||
template engine if there is a problem.
|
|
||||||
|
|
||||||
## chunk
|
|
||||||
|
|
||||||
To split a list into chunks of given size, use `chunk size list`. This is useful for pagination.
|
|
||||||
|
|
||||||
```
|
|
||||||
chunk 3 (list 1 2 3 4 5 6 7 8)
|
|
||||||
```
|
|
||||||
|
|
||||||
This produces list of lists `[ [ 1 2 3 ] [ 4 5 6 ] [ 7 8 ] ]`.
|
|
||||||
|
|
||||||
## A Note on List Internals
|
|
||||||
|
|
||||||
A list is implemented in Go as a `[]interface{}`. For Go developers embedding
|
|
||||||
Sprig, you may pass `[]interface{}` items into your template context and be
|
|
||||||
able to use all of the `list` functions on those items.
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
# Integer Math Functions
|
|
||||||
|
|
||||||
The following math functions operate on `int64` values.
|
|
||||||
|
|
||||||
## add
|
|
||||||
|
|
||||||
Sum numbers with `add`. Accepts two or more inputs.
|
|
||||||
|
|
||||||
```
|
|
||||||
add 1 2 3
|
|
||||||
```
|
|
||||||
|
|
||||||
## add1
|
|
||||||
|
|
||||||
To increment by 1, use `add1`
|
|
||||||
|
|
||||||
## sub
|
|
||||||
|
|
||||||
To subtract, use `sub`
|
|
||||||
|
|
||||||
## div
|
|
||||||
|
|
||||||
Perform integer division with `div`
|
|
||||||
|
|
||||||
## mod
|
|
||||||
|
|
||||||
Modulo with `mod`
|
|
||||||
|
|
||||||
## mul
|
|
||||||
|
|
||||||
Multiply with `mul`. Accepts two or more inputs.
|
|
||||||
|
|
||||||
```
|
|
||||||
mul 1 2 3
|
|
||||||
```
|
|
||||||
|
|
||||||
## max
|
|
||||||
|
|
||||||
Return the largest of a series of integers:
|
|
||||||
|
|
||||||
This will return `3`:
|
|
||||||
|
|
||||||
```
|
|
||||||
max 1 2 3
|
|
||||||
```
|
|
||||||
|
|
||||||
## min
|
|
||||||
|
|
||||||
Return the smallest of a series of integers.
|
|
||||||
|
|
||||||
`min 1 2 3` will return `1`
|
|
||||||
|
|
||||||
## floor
|
|
||||||
|
|
||||||
Returns the greatest float value less than or equal to input value
|
|
||||||
|
|
||||||
`floor 123.9999` will return `123.0`
|
|
||||||
|
|
||||||
## ceil
|
|
||||||
|
|
||||||
Returns the greatest float value greater than or equal to input value
|
|
||||||
|
|
||||||
`ceil 123.001` will return `124.0`
|
|
||||||
|
|
||||||
## round
|
|
||||||
|
|
||||||
Returns a float value with the remainder rounded to the given number to digits after the decimal point.
|
|
||||||
|
|
||||||
`round 123.555555 3` will return `123.556`
|
|
||||||
|
|
||||||
## randInt
|
|
||||||
Returns a random integer value from min (inclusive) to max (exclusive).
|
|
||||||
|
|
||||||
```
|
|
||||||
randInt 12 30
|
|
||||||
```
|
|
||||||
|
|
||||||
The above will produce a random number in the range [12,30].
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
# Path and Filepath Functions
|
|
||||||
|
|
||||||
While Sprig does not grant access to the filesystem, it does provide functions
|
|
||||||
for working with strings that follow file path conventions.
|
|
||||||
|
|
||||||
## Paths
|
|
||||||
|
|
||||||
Paths separated by the slash character (`/`), processed by the `path` package.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
* The [Linux](https://en.wikipedia.org/wiki/Linux) and
|
|
||||||
[MacOS](https://en.wikipedia.org/wiki/MacOS)
|
|
||||||
[filesystems](https://en.wikipedia.org/wiki/File_system):
|
|
||||||
`/home/user/file`, `/etc/config`;
|
|
||||||
* The path component of
|
|
||||||
[URIs](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier):
|
|
||||||
`https://example.com/some/content/`, `ftp://example.com/file/`.
|
|
||||||
|
|
||||||
### base
|
|
||||||
|
|
||||||
Return the last element of a path.
|
|
||||||
|
|
||||||
```
|
|
||||||
base "foo/bar/baz"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above prints "baz".
|
|
||||||
|
|
||||||
### dir
|
|
||||||
|
|
||||||
Return the directory, stripping the last part of the path. So `dir "foo/bar/baz"`
|
|
||||||
returns `foo/bar`.
|
|
||||||
|
|
||||||
### clean
|
|
||||||
|
|
||||||
Clean up a path.
|
|
||||||
|
|
||||||
```
|
|
||||||
clean "foo/bar/../baz"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above resolves the `..` and returns `foo/baz`.
|
|
||||||
|
|
||||||
### ext
|
|
||||||
|
|
||||||
Return the file extension.
|
|
||||||
|
|
||||||
```
|
|
||||||
ext "foo.bar"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `.bar`.
|
|
||||||
|
|
||||||
### isAbs
|
|
||||||
|
|
||||||
To check whether a path is absolute, use `isAbs`.
|
|
||||||
|
|
||||||
## Filepaths
|
|
||||||
|
|
||||||
Paths separated by the `os.PathSeparator` variable, processed by the `path/filepath` package.
|
|
||||||
|
|
||||||
These are the recommended functions to use when parsing paths of local filesystems, usually when dealing with local files, directories, etc.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
* Running on Linux or MacOS the filesystem path is separated by the slash character (`/`):
|
|
||||||
`/home/user/file`, `/etc/config`;
|
|
||||||
* Running on [Windows](https://en.wikipedia.org/wiki/Microsoft_Windows)
|
|
||||||
the filesystem path is separated by the backslash character (`\`):
|
|
||||||
`C:\Users\Username\`, `C:\Program Files\Application\`;
|
|
||||||
|
|
||||||
### osBase
|
|
||||||
|
|
||||||
Return the last element of a filepath.
|
|
||||||
|
|
||||||
```
|
|
||||||
osBase "/foo/bar/baz"
|
|
||||||
osBase "C:\\foo\\bar\\baz"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above prints "baz" on Linux and Windows, respectively.
|
|
||||||
|
|
||||||
### osDir
|
|
||||||
|
|
||||||
Return the directory, stripping the last part of the path. So `osDir "/foo/bar/baz"`
|
|
||||||
returns `/foo/bar` on Linux, and `osDir "C:\\foo\\bar\\baz"`
|
|
||||||
returns `C:\\foo\\bar` on Windows.
|
|
||||||
|
|
||||||
### osClean
|
|
||||||
|
|
||||||
Clean up a path.
|
|
||||||
|
|
||||||
```
|
|
||||||
osClean "/foo/bar/../baz"
|
|
||||||
osClean "C:\\foo\\bar\\..\\baz"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above resolves the `..` and returns `foo/baz` on Linux and `C:\\foo\\baz` on Windows.
|
|
||||||
|
|
||||||
### osExt
|
|
||||||
|
|
||||||
Return the file extension.
|
|
||||||
|
|
||||||
```
|
|
||||||
osExt "/foo.bar"
|
|
||||||
osExt "C:\\foo.bar"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `.bar` on Linux and Windows, respectively.
|
|
||||||
|
|
||||||
### osIsAbs
|
|
||||||
|
|
||||||
To check whether a file path is absolute, use `osIsAbs`.
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
# Reflection Functions
|
|
||||||
|
|
||||||
Sprig provides rudimentary reflection tools. These help advanced template
|
|
||||||
developers understand the underlying Go type information for a particular value.
|
|
||||||
|
|
||||||
Go has several primitive _kinds_, like `string`, `slice`, `int64`, and `bool`.
|
|
||||||
|
|
||||||
Go has an open _type_ system that allows developers to create their own types.
|
|
||||||
|
|
||||||
Sprig provides a set of functions for each.
|
|
||||||
|
|
||||||
## Kind Functions
|
|
||||||
|
|
||||||
There are two Kind functions: `kindOf` returns the kind of an object.
|
|
||||||
|
|
||||||
```
|
|
||||||
kindOf "hello"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above would return `string`. For simple tests (like in `if` blocks), the
|
|
||||||
`kindIs` function will let you verify that a value is a particular kind:
|
|
||||||
|
|
||||||
```
|
|
||||||
kindIs "int" 123
|
|
||||||
```
|
|
||||||
|
|
||||||
The above will return `true`
|
|
||||||
|
|
||||||
## Type Functions
|
|
||||||
|
|
||||||
Types are slightly harder to work with, so there are three different functions:
|
|
||||||
|
|
||||||
- `typeOf` returns the underlying type of a value: `typeOf $foo`
|
|
||||||
- `typeIs` is like `kindIs`, but for types: `typeIs "*io.Buffer" $myVal`
|
|
||||||
- `typeIsLike` works as `typeIs`, except that it also dereferences pointers.
|
|
||||||
|
|
||||||
**Note:** None of these can test whether or not something implements a given
|
|
||||||
interface, since doing so would require compiling the interface in ahead of time.
|
|
||||||
|
|
||||||
## deepEqual
|
|
||||||
|
|
||||||
`deepEqual` returns true if two values are ["deeply equal"](https://golang.org/pkg/reflect/#DeepEqual)
|
|
||||||
|
|
||||||
Works for non-primitive types as well (compared to the built-in `eq`).
|
|
||||||
|
|
||||||
```
|
|
||||||
deepEqual (list 1 2 3) (list 1 2 3)
|
|
||||||
```
|
|
||||||
|
|
||||||
The above will return `true`
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
# String List Functions
|
|
||||||
|
|
||||||
These function operate on or generate slices of strings. In Go, a slice is a
|
|
||||||
growable array. In Sprig, it's a special case of a `list`.
|
|
||||||
|
|
||||||
## join
|
|
||||||
|
|
||||||
Join a list of strings into a single string, with the given separator.
|
|
||||||
|
|
||||||
```
|
|
||||||
list "hello" "world" | join "_"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above will produce `hello_world`
|
|
||||||
|
|
||||||
`join` will try to convert non-strings to a string value:
|
|
||||||
|
|
||||||
```
|
|
||||||
list 1 2 3 | join "+"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above will produce `1+2+3`
|
|
||||||
|
|
||||||
## splitList and split
|
|
||||||
|
|
||||||
Split a string into a list of strings:
|
|
||||||
|
|
||||||
```
|
|
||||||
splitList "$" "foo$bar$baz"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above will return `[foo bar baz]`
|
|
||||||
|
|
||||||
The older `split` function splits a string into a `dict`. It is designed to make
|
|
||||||
it easy to use template dot notation for accessing members:
|
|
||||||
|
|
||||||
```
|
|
||||||
$a := split "$" "foo$bar$baz"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above produces a map with index keys. `{_0: foo, _1: bar, _2: baz}`
|
|
||||||
|
|
||||||
```
|
|
||||||
$a._0
|
|
||||||
```
|
|
||||||
|
|
||||||
The above produces `foo`
|
|
||||||
|
|
||||||
## splitn
|
|
||||||
|
|
||||||
`splitn` function splits a string into a `dict` with `n` keys. It is designed to make
|
|
||||||
it easy to use template dot notation for accessing members:
|
|
||||||
|
|
||||||
```
|
|
||||||
$a := splitn "$" 2 "foo$bar$baz"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above produces a map with index keys. `{_0: foo, _1: bar$baz}`
|
|
||||||
|
|
||||||
```
|
|
||||||
$a._0
|
|
||||||
```
|
|
||||||
|
|
||||||
The above produces `foo`
|
|
||||||
|
|
||||||
## sortAlpha
|
|
||||||
|
|
||||||
The `sortAlpha` function sorts a list of strings into alphabetical (lexicographical)
|
|
||||||
order.
|
|
||||||
|
|
||||||
It does _not_ sort in place, but returns a sorted copy of the list, in keeping
|
|
||||||
with the immutability of lists.
|
|
||||||
@@ -1,309 +0,0 @@
|
|||||||
# String Functions
|
|
||||||
|
|
||||||
Sprig has a number of string manipulation functions.
|
|
||||||
|
|
||||||
## trim
|
|
||||||
|
|
||||||
The `trim` function removes space from either side of a string:
|
|
||||||
|
|
||||||
```
|
|
||||||
trim " hello "
|
|
||||||
```
|
|
||||||
|
|
||||||
The above produces `hello`
|
|
||||||
|
|
||||||
## trimAll
|
|
||||||
|
|
||||||
Remove given characters from the front or back of a string:
|
|
||||||
|
|
||||||
```
|
|
||||||
trimAll "$" "$5.00"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `5.00` (as a string).
|
|
||||||
|
|
||||||
## trimSuffix
|
|
||||||
|
|
||||||
Trim just the suffix from a string:
|
|
||||||
|
|
||||||
```
|
|
||||||
trimSuffix "-" "hello-"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `hello`
|
|
||||||
|
|
||||||
## trimPrefix
|
|
||||||
|
|
||||||
Trim just the prefix from a string:
|
|
||||||
|
|
||||||
```
|
|
||||||
trimPrefix "-" "-hello"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `hello`
|
|
||||||
|
|
||||||
## upper
|
|
||||||
|
|
||||||
Convert the entire string to uppercase:
|
|
||||||
|
|
||||||
```
|
|
||||||
upper "hello"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `HELLO`
|
|
||||||
|
|
||||||
## lower
|
|
||||||
|
|
||||||
Convert the entire string to lowercase:
|
|
||||||
|
|
||||||
```
|
|
||||||
lower "HELLO"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `hello`
|
|
||||||
|
|
||||||
## title
|
|
||||||
|
|
||||||
Convert to title case:
|
|
||||||
|
|
||||||
```
|
|
||||||
title "hello world"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `Hello World`
|
|
||||||
|
|
||||||
## repeat
|
|
||||||
|
|
||||||
Repeat a string multiple times:
|
|
||||||
|
|
||||||
```
|
|
||||||
repeat 3 "hello"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `hellohellohello`
|
|
||||||
|
|
||||||
## substr
|
|
||||||
|
|
||||||
Get a substring from a string. It takes three parameters:
|
|
||||||
|
|
||||||
- start (int)
|
|
||||||
- end (int)
|
|
||||||
- string (string)
|
|
||||||
|
|
||||||
```
|
|
||||||
substr 0 5 "hello world"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `hello`
|
|
||||||
|
|
||||||
## trunc
|
|
||||||
|
|
||||||
Truncate a string (and add no suffix)
|
|
||||||
|
|
||||||
```
|
|
||||||
trunc 5 "hello world"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above produces `hello`.
|
|
||||||
|
|
||||||
```
|
|
||||||
trunc -5 "hello world"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above produces `world`.
|
|
||||||
|
|
||||||
## contains
|
|
||||||
|
|
||||||
Test to see if one string is contained inside of another:
|
|
||||||
|
|
||||||
```
|
|
||||||
contains "cat" "catch"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `true` because `catch` contains `cat`.
|
|
||||||
|
|
||||||
## hasPrefix and hasSuffix
|
|
||||||
|
|
||||||
The `hasPrefix` and `hasSuffix` functions test whether a string has a given
|
|
||||||
prefix or suffix:
|
|
||||||
|
|
||||||
```
|
|
||||||
hasPrefix "cat" "catch"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns `true` because `catch` has the prefix `cat`.
|
|
||||||
|
|
||||||
## quote and squote
|
|
||||||
|
|
||||||
These functions wrap a string in double quotes (`quote`) or single quotes
|
|
||||||
(`squote`).
|
|
||||||
|
|
||||||
## cat
|
|
||||||
|
|
||||||
The `cat` function concatenates multiple strings together into one, separating
|
|
||||||
them with spaces:
|
|
||||||
|
|
||||||
```
|
|
||||||
cat "hello" "beautiful" "world"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above produces `hello beautiful world`
|
|
||||||
|
|
||||||
## indent
|
|
||||||
|
|
||||||
The `indent` function indents every line in a given string to the specified
|
|
||||||
indent width. This is useful when aligning multi-line strings:
|
|
||||||
|
|
||||||
```
|
|
||||||
indent 4 $lots_of_text
|
|
||||||
```
|
|
||||||
|
|
||||||
The above will indent every line of text by 4 space characters.
|
|
||||||
|
|
||||||
## nindent
|
|
||||||
|
|
||||||
The `nindent` function is the same as the indent function, but prepends a new
|
|
||||||
line to the beginning of the string.
|
|
||||||
|
|
||||||
```
|
|
||||||
nindent 4 $lots_of_text
|
|
||||||
```
|
|
||||||
|
|
||||||
The above will indent every line of text by 4 space characters and add a new
|
|
||||||
line to the beginning.
|
|
||||||
|
|
||||||
## replace
|
|
||||||
|
|
||||||
Perform simple string replacement.
|
|
||||||
|
|
||||||
It takes three arguments:
|
|
||||||
|
|
||||||
- string to replace
|
|
||||||
- string to replace with
|
|
||||||
- source string
|
|
||||||
|
|
||||||
```
|
|
||||||
"I Am Henry VIII" | replace " " "-"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above will produce `I-Am-Henry-VIII`
|
|
||||||
|
|
||||||
## plural
|
|
||||||
|
|
||||||
Pluralize a string.
|
|
||||||
|
|
||||||
```
|
|
||||||
len $fish | plural "one anchovy" "many anchovies"
|
|
||||||
```
|
|
||||||
|
|
||||||
In the above, if the length of the string is 1, the first argument will be
|
|
||||||
printed (`one anchovy`). Otherwise, the second argument will be printed
|
|
||||||
(`many anchovies`).
|
|
||||||
|
|
||||||
The arguments are:
|
|
||||||
|
|
||||||
- singular string
|
|
||||||
- plural string
|
|
||||||
- length integer
|
|
||||||
|
|
||||||
NOTE: Sprig does not currently support languages with more complex pluralization
|
|
||||||
rules. And `0` is considered a plural because the English language treats it
|
|
||||||
as such (`zero anchovies`). The Sprig developers are working on a solution for
|
|
||||||
better internationalization.
|
|
||||||
|
|
||||||
## regexMatch, mustRegexMatch
|
|
||||||
|
|
||||||
Returns true if the input string contains any match of the regular expression.
|
|
||||||
|
|
||||||
```
|
|
||||||
regexMatch "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$" "test@acme.com"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above produces `true`
|
|
||||||
|
|
||||||
`regexMatch` panics if there is a problem and `mustRegexMatch` returns an error to the
|
|
||||||
template engine if there is a problem.
|
|
||||||
|
|
||||||
## regexFindAll, mustRegexFindAll
|
|
||||||
|
|
||||||
Returns a slice of all matches of the regular expression in the input string.
|
|
||||||
The last parameter n determines the number of substrings to return, where -1 means return all matches
|
|
||||||
|
|
||||||
```
|
|
||||||
regexFindAll "[2,4,6,8]" "123456789" -1
|
|
||||||
```
|
|
||||||
|
|
||||||
The above produces `[2 4 6 8]`
|
|
||||||
|
|
||||||
`regexFindAll` panics if there is a problem and `mustRegexFindAll` returns an error to the
|
|
||||||
template engine if there is a problem.
|
|
||||||
|
|
||||||
## regexFind, mustRegexFind
|
|
||||||
|
|
||||||
Return the first (left most) match of the regular expression in the input string
|
|
||||||
|
|
||||||
```
|
|
||||||
regexFind "[a-zA-Z][1-9]" "abcd1234"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above produces `d1`
|
|
||||||
|
|
||||||
`regexFind` panics if there is a problem and `mustRegexFind` returns an error to the
|
|
||||||
template engine if there is a problem.
|
|
||||||
|
|
||||||
## regexReplaceAll, mustRegexReplaceAll
|
|
||||||
|
|
||||||
Returns a copy of the input string, replacing matches of the Regexp with the replacement string replacement.
|
|
||||||
Inside string replacement, $ signs are interpreted as in Expand, so for instance $1 represents the text of the first submatch
|
|
||||||
|
|
||||||
```
|
|
||||||
regexReplaceAll "a(x*)b" "-ab-axxb-" "${1}W"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above produces `-W-xxW-`
|
|
||||||
|
|
||||||
`regexReplaceAll` panics if there is a problem and `mustRegexReplaceAll` returns an error to the
|
|
||||||
template engine if there is a problem.
|
|
||||||
|
|
||||||
## regexReplaceAllLiteral, mustRegexReplaceAllLiteral
|
|
||||||
|
|
||||||
Returns a copy of the input string, replacing matches of the Regexp with the replacement string replacement
|
|
||||||
The replacement string is substituted directly, without using Expand
|
|
||||||
|
|
||||||
```
|
|
||||||
regexReplaceAllLiteral "a(x*)b" "-ab-axxb-" "${1}"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above produces `-${1}-${1}-`
|
|
||||||
|
|
||||||
`regexReplaceAllLiteral` panics if there is a problem and `mustRegexReplaceAllLiteral` returns an error to the
|
|
||||||
template engine if there is a problem.
|
|
||||||
|
|
||||||
## regexSplit, mustRegexSplit
|
|
||||||
|
|
||||||
Slices the input string into substrings separated by the expression and returns a slice of the substrings between those expression matches. The last parameter `n` determines the number of substrings to return, where `-1` means return all matches
|
|
||||||
|
|
||||||
```
|
|
||||||
regexSplit "z+" "pizza" -1
|
|
||||||
```
|
|
||||||
|
|
||||||
The above produces `[pi a]`
|
|
||||||
|
|
||||||
`regexSplit` panics if there is a problem and `mustRegexSplit` returns an error to the
|
|
||||||
template engine if there is a problem.
|
|
||||||
|
|
||||||
## regexQuoteMeta
|
|
||||||
|
|
||||||
Returns a string that escapes all regular expression metacharacters inside the argument text;
|
|
||||||
the returned string is a regular expression matching the literal text.
|
|
||||||
|
|
||||||
```
|
|
||||||
regexQuoteMeta "1.2.3"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above produces `1\.2\.3`
|
|
||||||
|
|
||||||
## See Also...
|
|
||||||
|
|
||||||
The [Conversion Functions](conversion.md) contain functions for converting strings. The [String List Functions](string_slice.md) contains
|
|
||||||
functions for working with an array of strings.
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# URL Functions
|
|
||||||
|
|
||||||
## urlParse
|
|
||||||
Parses string for URL and produces dict with URL parts
|
|
||||||
|
|
||||||
```
|
|
||||||
urlParse "http://admin:secret@server.com:8080/api?list=false#anchor"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns a dict, containing URL object:
|
|
||||||
```yaml
|
|
||||||
scheme: 'http'
|
|
||||||
host: 'server.com:8080'
|
|
||||||
path: '/api'
|
|
||||||
query: 'list=false'
|
|
||||||
opaque: nil
|
|
||||||
fragment: 'anchor'
|
|
||||||
userinfo: 'admin:secret'
|
|
||||||
```
|
|
||||||
|
|
||||||
For more info, check https://golang.org/pkg/net/url/#URL
|
|
||||||
|
|
||||||
## urlJoin
|
|
||||||
Joins map (produced by `urlParse`) to produce URL string
|
|
||||||
|
|
||||||
```
|
|
||||||
urlJoin (dict "fragment" "fragment" "host" "host:80" "path" "/path" "query" "query" "scheme" "http")
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns the following string:
|
|
||||||
```
|
|
||||||
proto://host:80/path?query#fragment
|
|
||||||
```
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
# UUID Functions
|
|
||||||
|
|
||||||
Sprig can generate UUID v4 universally unique IDs.
|
|
||||||
|
|
||||||
```
|
|
||||||
uuidv4
|
|
||||||
```
|
|
||||||
|
|
||||||
The above returns a new UUID of the v4 (randomly generated) type.
|
|
||||||
BIN
docs/static/img/android-screenshot-template-custom.png
vendored
Normal file
BIN
docs/static/img/android-screenshot-template-custom.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
BIN
docs/static/img/android-screenshot-template-predefined.png
vendored
Normal file
BIN
docs/static/img/android-screenshot-template-predefined.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
BIN
docs/static/img/screenshot-github-webhook-config.png
vendored
Normal file
BIN
docs/static/img/screenshot-github-webhook-config.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
@@ -94,7 +94,6 @@ nav:
|
|||||||
- "Integrations + projects": integrations.md
|
- "Integrations + projects": integrations.md
|
||||||
- "Release notes": releases.md
|
- "Release notes": releases.md
|
||||||
- "Emojis 🥳 🎉": emojis.md
|
- "Emojis 🥳 🎉": emojis.md
|
||||||
- "Template Functions": sprig.md
|
|
||||||
- "Troubleshooting": troubleshooting.md
|
- "Troubleshooting": troubleshooting.md
|
||||||
- "Known issues": known-issues.md
|
- "Known issues": known-issues.md
|
||||||
- "Deprecation notices": deprecations.md
|
- "Deprecation notices": deprecations.md
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import (
|
|||||||
// Defines default config settings (excluding limits, see below)
|
// Defines default config settings (excluding limits, see below)
|
||||||
const (
|
const (
|
||||||
DefaultListenHTTP = ":80"
|
DefaultListenHTTP = ":80"
|
||||||
|
DefaultConfigFile = "/etc/ntfy/server.yml"
|
||||||
|
DefaultTemplateDir = "/etc/ntfy/templates"
|
||||||
DefaultCacheDuration = 12 * time.Hour
|
DefaultCacheDuration = 12 * time.Hour
|
||||||
DefaultCacheBatchTimeout = time.Duration(0)
|
DefaultCacheBatchTimeout = time.Duration(0)
|
||||||
DefaultKeepaliveInterval = 45 * time.Second // Not too frequently to save battery (Android read timeout used to be 77s!)
|
DefaultKeepaliveInterval = 45 * time.Second // Not too frequently to save battery (Android read timeout used to be 77s!)
|
||||||
@@ -173,7 +175,7 @@ type Config struct {
|
|||||||
// NewConfig instantiates a default new server config
|
// NewConfig instantiates a default new server config
|
||||||
func NewConfig() *Config {
|
func NewConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
File: "", // Only used for testing
|
File: DefaultConfigFile, // Only used for testing
|
||||||
BaseURL: "",
|
BaseURL: "",
|
||||||
ListenHTTP: DefaultListenHTTP,
|
ListenHTTP: DefaultListenHTTP,
|
||||||
ListenHTTPS: "",
|
ListenHTTPS: "",
|
||||||
@@ -196,6 +198,7 @@ func NewConfig() *Config {
|
|||||||
AttachmentTotalSizeLimit: DefaultAttachmentTotalSizeLimit,
|
AttachmentTotalSizeLimit: DefaultAttachmentTotalSizeLimit,
|
||||||
AttachmentFileSizeLimit: DefaultAttachmentFileSizeLimit,
|
AttachmentFileSizeLimit: DefaultAttachmentFileSizeLimit,
|
||||||
AttachmentExpiryDuration: DefaultAttachmentExpiryDuration,
|
AttachmentExpiryDuration: DefaultAttachmentExpiryDuration,
|
||||||
|
TemplateDir: DefaultTemplateDir,
|
||||||
KeepaliveInterval: DefaultKeepaliveInterval,
|
KeepaliveInterval: DefaultKeepaliveInterval,
|
||||||
ManagerInterval: DefaultManagerInterval,
|
ManagerInterval: DefaultManagerInterval,
|
||||||
DisallowedTopics: DefaultDisallowedTopics,
|
DisallowedTopics: DefaultDisallowedTopics,
|
||||||
@@ -258,6 +261,5 @@ func NewConfig() *Config {
|
|||||||
WebPushEmailAddress: "",
|
WebPushEmailAddress: "",
|
||||||
WebPushExpiryDuration: DefaultWebPushExpiryDuration,
|
WebPushExpiryDuration: DefaultWebPushExpiryDuration,
|
||||||
WebPushExpiryWarningDuration: DefaultWebPushExpiryWarningDuration,
|
WebPushExpiryWarningDuration: DefaultWebPushExpiryWarningDuration,
|
||||||
TemplateDir: "",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,6 +126,26 @@
|
|||||||
# attachment-file-size-limit: "15M"
|
# attachment-file-size-limit: "15M"
|
||||||
# attachment-expiry-duration: "3h"
|
# attachment-expiry-duration: "3h"
|
||||||
|
|
||||||
|
# Template directory for message templates.
|
||||||
|
#
|
||||||
|
# When "X-Template: <name>" (aliases: "Template: <name>", "Tpl: <name>") or "?template=<name>" is set, transform the message
|
||||||
|
# based on one of the built-in pre-defined templates, or on a template defined in the "template-dir" directory.
|
||||||
|
#
|
||||||
|
# Template files must have the ".yml" extension and must be formatted as YAML. They may contain "title" and "message" keys,
|
||||||
|
# which are interpreted as Go templates.
|
||||||
|
#
|
||||||
|
# Example template file (e.g. /etc/ntfy/templates/grafana.yml):
|
||||||
|
# title: |
|
||||||
|
# {{- if eq .status "firing" }}
|
||||||
|
# {{ .title | default "Alert firing" }}
|
||||||
|
# {{- else if eq .status "resolved" }}
|
||||||
|
# {{ .title | default "Alert resolved" }}
|
||||||
|
# {{- end }}
|
||||||
|
# message: |
|
||||||
|
# {{ .message | trunc 2000 }}
|
||||||
|
#
|
||||||
|
# template-dir: "/etc/ntfy/templates"
|
||||||
|
|
||||||
# If enabled, allow outgoing e-mail notifications via the 'X-Email' header. If this header is set,
|
# If enabled, allow outgoing e-mail notifications via the 'X-Email' header. If this header is set,
|
||||||
# messages will additionally be sent out as e-mail using an external SMTP server.
|
# messages will additionally be sent out as e-mail using an external SMTP server.
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -2918,7 +2918,7 @@ func TestServer_MessageTemplate_Range(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, 200, response.Code)
|
require.Equal(t, 200, response.Code)
|
||||||
m := toMessage(t, response.Body.String())
|
m := toMessage(t, response.Body.String())
|
||||||
require.Equal(t, "Severe URLs:\n- https://severe1.com\n- https://severe2.com\n", m.Message)
|
require.Equal(t, "Severe URLs:\n- https://severe1.com\n- https://severe2.com", m.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_MessageTemplate_ExceedMessageSize_TemplatedMessageOK(t *testing.T) {
|
func TestServer_MessageTemplate_ExceedMessageSize_TemplatedMessageOK(t *testing.T) {
|
||||||
@@ -2971,8 +2971,7 @@ Labels:
|
|||||||
Annotations:
|
Annotations:
|
||||||
- summary = 15m load average too high
|
- summary = 15m load average too high
|
||||||
Source: localhost:3000/alerting/grafana/NW9oDw-4z/view
|
Source: localhost:3000/alerting/grafana/NW9oDw-4z/view
|
||||||
Silence: localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DLoad+avg+15m+too+high&matcher=grafana_folder%3DNode+alerts&matcher=instance%3D10.108.0.2%3A9100&matcher=job%3Dnode-exporter
|
Silence: localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DLoad+avg+15m+too+high&matcher=grafana_folder%3DNode+alerts&matcher=instance%3D10.108.0.2%3A9100&matcher=job%3Dnode-exporter`, m.Message)
|
||||||
`, m.Message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_MessageTemplate_GitHub(t *testing.T) {
|
func TestServer_MessageTemplate_GitHub(t *testing.T) {
|
||||||
@@ -3073,18 +3072,75 @@ func TestServer_MessageTemplate_UnsafeSprigFunctions(t *testing.T) {
|
|||||||
var (
|
var (
|
||||||
//go:embed testdata/webhook_github_comment_created.json
|
//go:embed testdata/webhook_github_comment_created.json
|
||||||
githubCommentCreatedJSON string
|
githubCommentCreatedJSON string
|
||||||
|
|
||||||
|
//go:embed testdata/webhook_github_issue_opened.json
|
||||||
|
githubIssueOpenedJSON string
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServer_MessageTemplate_FromNamedTemplate(t *testing.T) {
|
func TestServer_MessageTemplate_FromNamedTemplate_GitHubCommentCreated(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
s := newTestServer(t, newTestConfig(t))
|
s := newTestServer(t, newTestConfig(t))
|
||||||
response := request(t, s, "POST", "/mytopic", githubCommentCreatedJSON, map[string]string{
|
response := request(t, s, "POST", "/mytopic?template=github", githubCommentCreatedJSON, nil)
|
||||||
"Template": "github",
|
|
||||||
})
|
|
||||||
require.Equal(t, 200, response.Code)
|
require.Equal(t, 200, response.Code)
|
||||||
m := toMessage(t, response.Body.String())
|
m := toMessage(t, response.Body.String())
|
||||||
require.Equal(t, "💬 New comment on issue #1389 — instant alerts without Pull to refresh", m.Title)
|
require.Equal(t, "💬 [ntfy] New comment on issue #1389 instant alerts without Pull to refresh", m.Title)
|
||||||
require.Equal(t, "💬 New comment on issue #1389 — instant alerts without Pull to refresh", m.Message)
|
require.Equal(t, `Commenter: https://github.com/wunter8
|
||||||
|
Repository: https://github.com/binwiederhier/ntfy
|
||||||
|
Comment link: https://github.com/binwiederhier/ntfy/issues/1389#issuecomment-3078214289
|
||||||
|
|
||||||
|
Comment:
|
||||||
|
These are the things you need to do to get iOS push notifications to work:
|
||||||
|
1. open a browser to the web app of your ntfy instance and copy the URL (including "http://" or "https://", your domain or IP address, and any ports, and excluding any trailing slashes)
|
||||||
|
2. put the URL you copied in the ntfy `+"`"+`base-url`+"`"+` config in server.yml or NTFY_BASE_URL in env variables
|
||||||
|
3. put the URL you copied in the default server URL setting in the iOS ntfy app
|
||||||
|
4. set `+"`"+`upstream-base-url`+"`"+` in server.yml or NTFY_UPSTREAM_BASE_URL in env variables to "https://ntfy.sh" (without a trailing slash)`, m.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_MessageTemplate_FromNamedTemplate_GitHubIssueOpened(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
s := newTestServer(t, newTestConfig(t))
|
||||||
|
response := request(t, s, "POST", "/mytopic?template=github", githubIssueOpenedJSON, nil)
|
||||||
|
require.Equal(t, 200, response.Code)
|
||||||
|
m := toMessage(t, response.Body.String())
|
||||||
|
require.Equal(t, "🐛 [ntfy] Issue opened: #1391 http 500 error (ntfy error 50001)", m.Title)
|
||||||
|
require.Equal(t, `Opened by: https://github.com/TheUser-dev
|
||||||
|
Repository: https://github.com/binwiederhier/ntfy
|
||||||
|
Issue link: https://github.com/binwiederhier/ntfy/issues/1391
|
||||||
|
Labels: 🪲 bug
|
||||||
|
|
||||||
|
Description:
|
||||||
|
:lady_beetle: **Describe the bug**
|
||||||
|
When sending a notification (especially when it happens with multiple requests) this error occurs
|
||||||
|
|
||||||
|
:computer: **Components impacted**
|
||||||
|
ntfy server 2.13.0 in docker, debian 12 arm64
|
||||||
|
|
||||||
|
:bulb: **Screenshots and/or logs**
|
||||||
|
`+"```"+`
|
||||||
|
closed with HTTP 500 (ntfy error 50001) (error=database table is locked, http_method=POST, http_path=/_matrix/push/v1/notify, tag=http, visitor_auth_limiter_limit=0.016666666666666666, visitor_auth_limiter_tokens=30, visitor_id=ip:<edited>, visitor_ip=<edited>, visitor_messages=448, visitor_messages_limit=17280, visitor_messages_remaining=16832, visitor_request_limiter_limit=0.2, visitor_request_limiter_tokens=57.049697891799994, visitor_seen=2025-07-16T15:06:35.429Z)
|
||||||
|
`+"```"+`
|
||||||
|
|
||||||
|
:crystal_ball: **Additional context**
|
||||||
|
Looks like this has already been fixed by #498, regression?`, m.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_MessageTemplate_FromNamedTemplate_GitHubIssueOpened_OverrideConfigTemplate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
c := newTestConfig(t)
|
||||||
|
c.TemplateDir = t.TempDir()
|
||||||
|
require.NoError(t, os.WriteFile(filepath.Join(c.TemplateDir, "github.yml"), []byte(`
|
||||||
|
title: |
|
||||||
|
Custom title: action={{ .action }} trunctitle={{ .issue.title | trunc 10 }}
|
||||||
|
message: |
|
||||||
|
Custom message {{ .issue.number }}
|
||||||
|
`), 0644))
|
||||||
|
s := newTestServer(t, c)
|
||||||
|
response := request(t, s, "POST", "/mytopic?template=github", githubIssueOpenedJSON, nil)
|
||||||
|
fmt.Println(response.Body.String())
|
||||||
|
require.Equal(t, 200, response.Code)
|
||||||
|
m := toMessage(t, response.Body.String())
|
||||||
|
require.Equal(t, "Custom title: action=opened trunctitle=http 500 e", m.Title)
|
||||||
|
require.Equal(t, "Custom message 1391", m.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestConfig(t *testing.T) *Config {
|
func newTestConfig(t *testing.T) *Config {
|
||||||
@@ -3093,6 +3149,7 @@ func newTestConfig(t *testing.T) *Config {
|
|||||||
conf.CacheFile = filepath.Join(t.TempDir(), "cache.db")
|
conf.CacheFile = filepath.Join(t.TempDir(), "cache.db")
|
||||||
conf.CacheStartupQueries = "pragma journal_mode = WAL; pragma synchronous = normal; pragma temp_store = memory;"
|
conf.CacheStartupQueries = "pragma journal_mode = WAL; pragma synchronous = normal; pragma temp_store = memory;"
|
||||||
conf.AttachmentCacheDir = t.TempDir()
|
conf.AttachmentCacheDir = t.TempDir()
|
||||||
|
conf.TemplateDir = t.TempDir()
|
||||||
return conf
|
return conf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
29
server/templates/alertmanager.yml
Normal file
29
server/templates/alertmanager.yml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
title: |
|
||||||
|
{{- if eq .status "firing" }}
|
||||||
|
🚨 Alert: {{ (first .alerts).labels.alertname }}
|
||||||
|
{{- else if eq .status "resolved" }}
|
||||||
|
✅ Resolved: {{ (first .alerts).labels.alertname }}
|
||||||
|
{{- else }}
|
||||||
|
{{ fail "Unsupported Alertmanager status." }}
|
||||||
|
{{- end }}
|
||||||
|
message: |
|
||||||
|
Status: {{ .status | title }}
|
||||||
|
Receiver: {{ .receiver }}
|
||||||
|
|
||||||
|
{{- range .alerts }}
|
||||||
|
Alert: {{ .labels.alertname }}
|
||||||
|
Instance: {{ .labels.instance }}
|
||||||
|
Severity: {{ .labels.severity }}
|
||||||
|
Starts at: {{ .startsAt }}
|
||||||
|
{{- if .endsAt }}Ends at: {{ .endsAt }}{{ end }}
|
||||||
|
{{- if .annotations.summary }}
|
||||||
|
Summary: {{ .annotations.summary }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .annotations.description }}
|
||||||
|
Description: {{ .annotations.description }}
|
||||||
|
{{- end }}
|
||||||
|
Source: {{ .generatorURL }}
|
||||||
|
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@ title: |
|
|||||||
👀 {{ .sender.login }} started watching {{ .repository.name }}
|
👀 {{ .sender.login }} started watching {{ .repository.name }}
|
||||||
|
|
||||||
{{- else if and .comment (eq .action "created") }}
|
{{- else if and .comment (eq .action "created") }}
|
||||||
💬 New comment on #{{ .issue.number }}: {{ .issue.title }}
|
💬 New comment on issue #{{ .issue.number }} {{ .issue.title }}
|
||||||
|
|
||||||
{{- else if .pull_request }}
|
{{- else if .pull_request }}
|
||||||
🔀 Pull request {{ .action }}: #{{ .pull_request.number }} {{ .pull_request.title }}
|
🔀 Pull request {{ .action }}: #{{ .pull_request.number }} {{ .pull_request.title }}
|
||||||
@@ -47,6 +47,7 @@ message: |
|
|||||||
{{ .action | title }} by: {{ .issue.user.html_url }}
|
{{ .action | title }} by: {{ .issue.user.html_url }}
|
||||||
Repository: {{ .repository.html_url }}
|
Repository: {{ .repository.html_url }}
|
||||||
Issue link: {{ .issue.html_url }}
|
Issue link: {{ .issue.html_url }}
|
||||||
|
{{ if .issue.labels }}Labels: {{ range .issue.labels }}{{ .name }} {{ end }}{{ end }}
|
||||||
{{ if .issue.body }}
|
{{ if .issue.body }}
|
||||||
Description:
|
Description:
|
||||||
{{ .issue.body | trunc 2000 }}{{ end }}
|
{{ .issue.body | trunc 2000 }}{{ end }}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
message: |
|
|
||||||
{{if .alerts}}
|
|
||||||
{{.alerts | len}} alert(s) triggered
|
|
||||||
{{else}}
|
|
||||||
No alerts triggered.
|
|
||||||
{{end}}
|
|
||||||
title: |
|
title: |
|
||||||
⚠️ Grafana alert: {{.title}}
|
{{- if eq .status "firing" }}
|
||||||
|
🚨 {{ .title | default "Alert firing" }}
|
||||||
|
{{- else if eq .status "resolved" }}
|
||||||
|
✅ {{ .title | default "Alert resolved" }}
|
||||||
|
{{- else }}
|
||||||
|
⚠️ Unknown alert: {{ .title | default "Alert" }}
|
||||||
|
{{- end }}
|
||||||
|
message: |
|
||||||
|
{{ .message | trunc 2000 }}
|
||||||
|
|
||||||
|
|||||||
33
server/testdata/webhook_alertmanager_firing.json
vendored
Normal file
33
server/testdata/webhook_alertmanager_firing.json
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"version": "4",
|
||||||
|
"groupKey": "...",
|
||||||
|
"status": "firing",
|
||||||
|
"receiver": "webhook-receiver",
|
||||||
|
"groupLabels": {
|
||||||
|
"alertname": "HighCPUUsage"
|
||||||
|
},
|
||||||
|
"commonLabels": {
|
||||||
|
"alertname": "HighCPUUsage",
|
||||||
|
"instance": "server01",
|
||||||
|
"severity": "critical"
|
||||||
|
},
|
||||||
|
"commonAnnotations": {
|
||||||
|
"summary": "High CPU usage detected"
|
||||||
|
},
|
||||||
|
"alerts": [
|
||||||
|
{
|
||||||
|
"status": "firing",
|
||||||
|
"labels": {
|
||||||
|
"alertname": "HighCPUUsage",
|
||||||
|
"instance": "server01",
|
||||||
|
"severity": "critical"
|
||||||
|
},
|
||||||
|
"annotations": {
|
||||||
|
"summary": "High CPU usage detected"
|
||||||
|
},
|
||||||
|
"startsAt": "2025-07-17T07:00:00Z",
|
||||||
|
"endsAt": "0001-01-01T00:00:00Z",
|
||||||
|
"generatorURL": "http://prometheus.local/graph?g0.expr=..."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
51
server/testdata/webhook_grafana_resolved.json
vendored
Normal file
51
server/testdata/webhook_grafana_resolved.json
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"receiver": "ntfy\\.example\\.com/alerts",
|
||||||
|
"status": "resolved",
|
||||||
|
"alerts": [
|
||||||
|
{
|
||||||
|
"status": "resolved",
|
||||||
|
"labels": {
|
||||||
|
"alertname": "Load avg 15m too high",
|
||||||
|
"grafana_folder": "Node alerts",
|
||||||
|
"instance": "10.108.0.2:9100",
|
||||||
|
"job": "node-exporter"
|
||||||
|
},
|
||||||
|
"annotations": {
|
||||||
|
"summary": "15m load average too high"
|
||||||
|
},
|
||||||
|
"startsAt": "2024-03-15T02:28:00Z",
|
||||||
|
"endsAt": "2024-03-15T02:42:00Z",
|
||||||
|
"generatorURL": "localhost:3000/alerting/grafana/NW9oDw-4z/view",
|
||||||
|
"fingerprint": "becbfb94bd81ef48",
|
||||||
|
"silenceURL": "localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DLoad+avg+15m+too+high&matcher=grafana_folder%3DNode+alerts&matcher=instance%3D10.108.0.2%3A9100&matcher=job%3Dnode-exporter",
|
||||||
|
"dashboardURL": "",
|
||||||
|
"panelURL": "",
|
||||||
|
"values": {
|
||||||
|
"B": 18.98211314475876,
|
||||||
|
"C": 0
|
||||||
|
},
|
||||||
|
"valueString": "[ var='B' labels={__name__=node_load15, instance=10.108.0.2:9100, job=node-exporter} value=18.98211314475876 ], [ var='C' labels={__name__=node_load15, instance=10.108.0.2:9100, job=node-exporter} value=0 ]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"groupLabels": {
|
||||||
|
"alertname": "Load avg 15m too high",
|
||||||
|
"grafana_folder": "Node alerts"
|
||||||
|
},
|
||||||
|
"commonLabels": {
|
||||||
|
"alertname": "Load avg 15m too high",
|
||||||
|
"grafana_folder": "Node alerts",
|
||||||
|
"instance": "10.108.0.2:9100",
|
||||||
|
"job": "node-exporter"
|
||||||
|
},
|
||||||
|
"commonAnnotations": {
|
||||||
|
"summary": "15m load average too high"
|
||||||
|
},
|
||||||
|
"externalURL": "localhost:3000/",
|
||||||
|
"version": "1",
|
||||||
|
"groupKey": "{}:{alertname=\"Load avg 15m too high\", grafana_folder=\"Node alerts\"}",
|
||||||
|
"truncatedAlerts": 0,
|
||||||
|
"orgId": 1,
|
||||||
|
"title": "[RESOLVED] Load avg 15m too high Node alerts (10.108.0.2:9100 node-exporter)",
|
||||||
|
"state": "ok",
|
||||||
|
"message": "**Resolved**\n\nValue: B=18.98211314475876, C=0\nLabels:\n - alertname = Load avg 15m too high\n - grafana_folder = Node alerts\n - instance = 10.108.0.2:9100\n - job = node-exporter\n"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user