Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove function in KV is hard to use from templates #3393

Open
grobinson-grafana opened this issue Jun 14, 2023 · 3 comments
Open

Remove function in KV is hard to use from templates #3393

grobinson-grafana opened this issue Jun 14, 2023 · 3 comments

Comments

@grobinson-grafana
Copy link
Contributor

grobinson-grafana commented Jun 14, 2023

Problem

This week I was attempting to write an Alertmanager template that printed the Summary, Description and Runbook URL annotations, followed by any additional annotations indented under the title Annotations:. For example:

Labels: alertname=Test
Summary: This is a summary
Description: This is a description
Runbook URL: https://example.com/runbooks/test
Annotations: 
    something_else = This is something else

Such templates should be straightforward to write, however I struggled for about an hour or so on how use the Remove method on KV in a Go template, which is necessary to remove the Summary, Description and Runbook URL annotations from the list of remaining annotations that will be printed at the end.

The following doesn't work:

{{ .Annotations.Remove "summary" }}

because the problem here is that Remove expects an []string, rather than a variadic argument of ...string:

// Remove returns a copy of the key/value set without the given keys.
func (kv KV) Remove(keys []string) KV {
	...
}

How does one create a []string in a Go template? You can't - at least not without a helper function that turns ...string into []string.

It turns out that Alertmanager has a stringSlice function, but it's right at the bottom of the page. That being said, it works:

{{ .Annotations.Remove (stringSlice "summary") }}

and I was able to create the template I wanted:

{{ define "summarize_alerts" }}
{{ range .Alerts }}
{{ template "summarize_alert" . }}
{{ end }}
{{ end }}

{{- define "summarize_alert" -}}
{{- $annotations := .Annotations -}}
{{- if index $annotations "summary" -}}
Summary: {{ index .Annotations "summary" }}
{{- $annotations = $annotations.Remove (stringSlice "summary") }}
{{ end -}}
{{- if index $annotations "description" -}}
Description: {{ index $annotations "description" }}
{{- $annotations = $annotations.Remove (stringSlice "description") }}
{{ end -}}
{{- if index $annotations "runbook_url" -}}
Runbook URL: {{ index $annotations "runbook_url" }}
{{- $annotations = $annotations.Remove (stringSlice "runbook_url") }}
{{ end -}}
{{- if $annotations -}}
Annotations: {{ template "print_annotations" $annotations }}
{{ end -}}
{{- end -}}

{{- define "print_annotations" -}}
{{ range .SortedPairs }}
    {{ .Name }} = {{ .Value }}
{{- end }}
{{- end -}}

Questions

  1. Does it make sense for Remove to require []string instead of ...string when so much of Go template is designed around variadic arguments? For example:
and
	Returns the boolean AND of its arguments by returning the
	first empty argument or the last argument. That is,
	"and x y" behaves as "if x then y else x."
	Evaluation proceeds through the arguments left to right
	and returns when the result is determined.
call
	Returns the result of calling the first argument, which
	must be a function, with the remaining arguments as parameters.
	Thus "call .X.Y 1 2" is, in Go notation, dot.X.Y(1, 2) where
	Y is a func-valued field, map entry, or the like.
	The first argument must be the result of an evaluation
	that yields a value of function type (as distinct from
	a predefined function such as print). The function must
	return either one or two result values, the second of which
	is of type error. If the arguments don't match the function
	or the returned error value is non-nil, execution stops.
index
	Returns the result of indexing its first argument by the
	following arguments. Thus "index x 1 2 3" is, in Go syntax,
	x[1][2][3]. Each indexed item must be a map, slice, or array.
slice
	slice returns the result of slicing its first argument by the
	remaining arguments. Thus "slice x 1 2" is, in Go syntax, x[1:2],
	while "slice x" is x[:], "slice x 1" is x[1:], and "slice x 1 2 3"
	is x[1:2:3]. The first argument must be a string, slice, or array.
or
	Returns the boolean OR of its arguments by returning the
	first non-empty argument or the last argument, that is,
	"or x y" behaves as "if x then x else y".
	Evaluation proceeds through the arguments left to right
	and returns when the result is determined.
  1. Should we add examples to the documentation on how to use Remove?
@simonpasquier
Copy link
Member

Looking at 66a0ed2 , stringSlice was added exactly for this use case.

Does it make sense for Remove to require []string instead of ...string

IIUC Remove is expected to take a KV.Names (or KV.Values) as input.

I agree that a note in the documentation of Remove mentioning stringSlice would help.

@grobinson-grafana
Copy link
Contributor Author

grobinson-grafana commented Aug 16, 2023

IIUC Remove is expected to take a KV.Names (or KV.Values) as input.

If that's the expected use case then I think what's missing is documentation showing situations where using KV.Names or KV.Values as an input to Remove is useful. The problem is I can't find any situations where this is useful, so I'm thinking if we should change it.

For example, as far as I can see the only way you can use KV.Names with Remove is to remove all elements from a KV:

.Annotations.Remove (.Annotations.Names)

But how useful is that?

I couldn't find any functions that operate on KV.Names that would let you do something like this:

.Annotations.Remove (.Annotations.Names.Select "summary")

Instead, if I just want to remove the summary annotation I need to do something like:

.Annotations.Remove (stringSlice "summary")

when instead I could do:

.Annotations.Remove "summary"

That is to say I don't see what use cases are made possible by having KV.Names or KV.Values as input to Remove. Instead, it looks like this makes templating in Alertmanager more difficult than it could otherwise be, unless I have missed something?

@allyrr
Copy link

allyrr commented Mar 14, 2024

with the next alertmanager configuration:

text: >-
  {{ range .Alerts }}
    *Alert:* {{ .Annotations.summary }}
    *Description:* {{ .Annotations.description }}
    *Details:*
    {{ range .Labels.SortedPairs }}
      {{ if ne .Name "endpoint" }} • *{{ .Name }}:* `{{ .Value }}`{{ end }}
    {{ end }}
  {{ end }}

I got the message in slack which has no endpoint label in the list of labels but somehow the blank multilines were added between the labels (look at the screenshot):
image

May be someone has already resolved that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants