mirror of
https://github.com/hanzoai/o11y.git
synced 2026-10-15 12:58:33 +00:00
* fork: own SigNoz platform deps under hanzoai, repath imports off upstream brand
Phase 1 of the full-fork mandate: eliminate upstream-branded SigNoz modules,
own them as public hanzoai forks, and repath all imports to hanzoai paths.
Owned deps (new public hanzoai forks, module renamed to hanzoai path):
github.com/SigNoz/signoz-otel-collector v0.144.3
-> github.com/hanzoai/signoz-otel-collector v0.144.6 (module renamed)
github.com/SigNoz/clickhouse-go-mock v0.14.0
-> github.com/hanzoai/clickhouse-go-mock v0.14.1 (module renamed)
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd
-> github.com/hanzoai/govaluate v0.1.0 (module renamed)
github.com/SigNoz/expr (replace target for github.com/expr-lang/expr)
-> github.com/hanzoai/expr v1.17.8 (upstream module path preserved,
consumed via replace exactly like the prior SigNoz/expr fork)
In-repo repath: the vendored SigNoz source tree in pkg/, ee/, cmd/ is now
self-referenced as github.com/hanzoai/o11y/... instead of
github.com/SigNoz/signoz/... (1300+ import lines across 377 files), matching
the 750 files already on the hanzoai path. Zero SigNoz-branded module
references remain in go.mod or any .go import.
Generic ecosystem libraries (golang.org/x, google.golang.org, prometheus,
opentelemetry, gin, cobra, luxfi/*, etc.) are left untouched — only the
SigNoz platform fork is owned.
Pre-existing (NOT introduced here): origin/main does not `go build ./...`
because 22 packages are imported but absent from the vendored tree; 14 of
them (roletypes, grammar, cloudintegrations, resourcefilter, integrationtypes,
metricsexplorer, metrics_explorer, times, agg_rewrite, bucket_cache,
sqlitesqlstore, sqlschema/postgressqlschema, sqlstore/postgressqlstore,
cloudintegrations/services) exist in no upstream SigNoz version and must be
vendored into hanzoai/o11y from hanzoai/signoz@770f464f to finish. This
repath is orthogonal to that gap.
* docs(LLM.md): fork boundary map + pre-existing compile-gap inventory
---------
Co-authored-by: Hanzo Dev <[email protected]>
156 lines
5.6 KiB
Go
156 lines
5.6 KiB
Go
package alertmanagertemplate
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
"text/template/parse"
|
|
|
|
"github.com/hanzoai/o11y/pkg/errors"
|
|
)
|
|
|
|
// bareVariableRegex matches $-references including dotted paths (e.g. $alert.is_firing).
|
|
var bareVariableRegex = regexp.MustCompile(`\$(\w+(?:\.\w+)*)`)
|
|
|
|
// bareVariableFirstSegRegex captures only the first segment of a $-reference.
|
|
// $labels.severity yields "$labels"; $alert.is_firing yields "$alert".
|
|
var bareVariableFirstSegRegex = regexp.MustCompile(`\$\w+`)
|
|
|
|
// wrapDollarVariables rewrites bare $-references in a template's plain-text
|
|
// regions into Go text/template syntax. References inside `{{ ... }}` action
|
|
// blocks are left untouched — they're already template syntax. structRoots
|
|
// is the set of top-level mapstructure names whose values are nested structs
|
|
// (e.g. "alert", "rule"): $-refs whose first segment is in this set are
|
|
// walked segment-by-segment; everything else dotted is treated as a flat key
|
|
// on the root map so flattened OTel-style label keys resolve naturally.
|
|
//
|
|
// Examples (structRoots = {alert, rule}):
|
|
//
|
|
// "$name is $status" -> "{{ .name }} is {{ .status }}"
|
|
// "$labels.severity" -> `{{ index .labels "severity" }}`
|
|
// "$labels.http.method" -> `{{ index .labels "http.method" }}`
|
|
// "$annotations.summary" -> `{{ index .annotations "summary" }}`
|
|
// "$alert.is_firing" -> `{{ index . "alert" "is_firing" }}`
|
|
// "$rule.threshold.value" -> `{{ index . "rule" "threshold" "value" }}`
|
|
// "$service.name" -> `{{ index . "service.name" }}`
|
|
// "$name is {{ .Status }}" -> "{{ .name }} is {{ .Status }}"
|
|
func wrapDollarVariables(src string, structRoots map[string]bool) (string, error) {
|
|
if src == "" {
|
|
return src, nil
|
|
}
|
|
|
|
tree := parse.New("template")
|
|
tree.Mode = parse.SkipFuncCheck
|
|
|
|
if _, err := tree.Parse(src, "{{", "}}", make(map[string]*parse.Tree), nil); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
walkAndWrapTextNodes(tree.Root, structRoots)
|
|
return tree.Root.String(), nil
|
|
}
|
|
|
|
// walkAndWrapTextNodes descends the parse tree and rewrites $-references
|
|
// found in TextNodes. If/Range bodies are recursed into. ActionNodes and
|
|
// other `{{...}}` constructs are left alone because they're already template
|
|
// syntax. WithNode is not yet supported — add when the editor grows a "with"
|
|
// block.
|
|
func walkAndWrapTextNodes(node parse.Node, structRoots map[string]bool) {
|
|
if reflect.ValueOf(node).IsNil() {
|
|
return
|
|
}
|
|
|
|
switch n := node.(type) {
|
|
case *parse.ListNode:
|
|
if n.Nodes != nil {
|
|
for _, child := range n.Nodes {
|
|
walkAndWrapTextNodes(child, structRoots)
|
|
}
|
|
}
|
|
|
|
case *parse.TextNode:
|
|
n.Text = bareVariableRegex.ReplaceAllFunc(n.Text, func(match []byte) []byte {
|
|
return rewriteDollarRef(match, structRoots)
|
|
})
|
|
|
|
case *parse.IfNode:
|
|
walkAndWrapTextNodes(n.List, structRoots)
|
|
walkAndWrapTextNodes(n.ElseList, structRoots)
|
|
|
|
case *parse.RangeNode:
|
|
walkAndWrapTextNodes(n.List, structRoots)
|
|
walkAndWrapTextNodes(n.ElseList, structRoots)
|
|
}
|
|
}
|
|
|
|
// rewriteDollarRef converts one $-reference (with the leading $) into the
|
|
// corresponding Go template expression.
|
|
//
|
|
// - labels./annotations. prefixes: treat the remainder as a single map key
|
|
// (OTel-style keys like "http.request.method" are flat keys, not paths).
|
|
// - Dotted path with a struct-root first segment: walk via chained index
|
|
// arguments. `index x a b c` is equivalent to x[a][b][c].
|
|
// - Other dotted path: treat as a single flat key on the root map, so a
|
|
// flattened OTel-style label key like "service.name" resolves.
|
|
// - Simple names: plain dot access on the root map.
|
|
func rewriteDollarRef(match []byte, structRoots map[string]bool) []byte {
|
|
name := string(match[1:])
|
|
|
|
if !strings.Contains(name, ".") {
|
|
return fmt.Appendf(nil, `{{ .%s }}`, name)
|
|
}
|
|
|
|
if key, ok := strings.CutPrefix(name, "labels."); ok {
|
|
return fmt.Appendf(nil, `{{ index .labels %q }}`, key)
|
|
}
|
|
if key, ok := strings.CutPrefix(name, "annotations."); ok {
|
|
return fmt.Appendf(nil, `{{ index .annotations %q }}`, key)
|
|
}
|
|
|
|
// If the first segment is a known struct root, walk segments.
|
|
if dot := strings.IndexByte(name, '.'); dot >= 0 && structRoots[name[:dot]] {
|
|
parts := strings.Split(name, ".")
|
|
args := make([]string, len(parts))
|
|
for i, p := range parts {
|
|
args[i] = fmt.Sprintf("%q", p)
|
|
}
|
|
return fmt.Appendf(nil, `{{ index . %s }}`, strings.Join(args, " "))
|
|
}
|
|
|
|
// Otherwise treat the full dotted path as a single flat root key.
|
|
return fmt.Appendf(nil, `{{ index . %q }}`, name)
|
|
}
|
|
|
|
// extractUsedVariables returns the set of every base $-name referenced in the
|
|
// template — text nodes, action blocks, branch conditions, loop declarations.
|
|
// Names are first-segment only: $labels.severity contributes "labels".
|
|
//
|
|
// The template is validated during extraction (a synthesised preamble
|
|
// pre-declares each name so the parser doesn't trip on "undefined variable"
|
|
// for names that genuinely exist in the data), so a parse error here
|
|
// indicates a genuine syntax problem rather than a missing binding.
|
|
func extractUsedVariables(src string) (map[string]bool, error) {
|
|
if src == "" {
|
|
return map[string]bool{}, nil
|
|
}
|
|
|
|
used := make(map[string]bool)
|
|
for _, m := range bareVariableFirstSegRegex.FindAll([]byte(src), -1) {
|
|
used[string(m[1:])] = true
|
|
}
|
|
|
|
var preamble strings.Builder
|
|
for name := range used {
|
|
fmt.Fprintf(&preamble, `{{$%s := ""}}`, name)
|
|
}
|
|
|
|
tree := parse.New("template")
|
|
tree.Mode = parse.SkipFuncCheck
|
|
if _, err := tree.Parse(preamble.String()+src, "{{", "}}", make(map[string]*parse.Tree), nil); err != nil {
|
|
return nil, errors.WrapInvalidInputf(err, errors.CodeInternal, "failed to extract used variables")
|
|
}
|
|
|
|
return used, nil
|
|
}
|