Documentation
¶
Index ¶
- func AbsentFields(data []byte, fields []string) []string
- func ConditionallyIgnoredFields() []string
- func DetectKind(data []byte) (string, error)
- func Equivalent(a, b []byte, additionalIgnoredFields []string, ...) (bool, error)
- func MarshalPrometheusRule(rule *dash0.PrometheusAlertRule) ([]byte, error)
- func MergeAnnotations(metadataAnnotations, ruleAnnotations map[string]string) map[string]string
- func Normalize(data []byte, additionalIgnoredFields []string, ...) ([]byte, error)
- func ParseAsDashboard(data []byte) (*dash0.DashboardDefinition, error)
- func ParseAsPrometheusAlertRules(data []byte) ([]*dash0.PrometheusAlertRule, error)
- func UnmarshalPrometheusRule(data []byte) (*dash0.PrometheusAlertRule, error)
- type Option
- type Options
Examples ¶
- AbsentFields
- ConditionallyIgnoredFields
- DetectKind
- DetectKind (InferredCheckRule)
- Equivalent
- MarshalPrometheusRule
- Normalize
- ParseAsDashboard
- ParseAsDashboard (PersesDashboard)
- ParseAsPrometheusAlertRules
- ParseAsPrometheusAlertRules (NativeCheckRule)
- UnmarshalPrometheusRule
- WithAnnotationsRoot
- WithAnnotationsUnfiltered
- WithFlatDocument
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AbsentFields ¶ added in v1.21.0
AbsentFields returns which of the given dot-separated field paths are absent from the parsed document. Used to conditionally ignore API-managed fields the reference document didn't include (e.g. ConditionallyIgnoredFields).
Example ¶
package main
import (
"fmt"
dash0yaml "github.com/dash0hq/dash0-api-client-go/yaml"
)
func main() {
data := []byte("spec:\n enabled: true\n")
absent := dash0yaml.AbsentFields(data, []string{"spec.enabled", "spec.permissions"})
fmt.Println(absent)
}
Output: [spec.permissions]
func ConditionallyIgnoredFields ¶ added in v1.21.0
func ConditionallyIgnoredFields() []string
ConditionallyIgnoredFields returns a copy of the fields ignored during comparison only when absent from the reference document. See conditionallyIgnoredFields for the full description. Returns a fresh slice on every call so a caller mutating the result cannot shift drift semantics for every other caller in the process.
Example ¶
package main
import (
"fmt"
dash0yaml "github.com/dash0hq/dash0-api-client-go/yaml"
)
func main() {
fields := dash0yaml.ConditionallyIgnoredFields()
fmt.Println(fields)
}
Output: [metadata.name spec.permissions]
func DetectKind ¶
DetectKind extracts the "kind" field from raw YAML/JSON bytes. When the document has no explicit "kind" (e.g., a check rule exported via `check-rules get -o yaml`), the kind is inferred from the document structure: the "expression" field is required for check rules and absent in all other asset types. An empty kind is returned when the input is valid YAML but has no recognizable kind. An error is returned when the input cannot be parsed as YAML.
Example ¶
package main
import (
"fmt"
"log"
dash0yaml "github.com/dash0hq/dash0-api-client-go/yaml"
)
func main() {
kind, err := dash0yaml.DetectKind([]byte("kind: PrometheusRule\nspec: {}"))
if err != nil {
log.Fatal(err)
}
fmt.Println(kind)
}
Output: PrometheusRule
Example (InferredCheckRule) ¶
package main
import (
"fmt"
"log"
dash0yaml "github.com/dash0hq/dash0-api-client-go/yaml"
)
func main() {
// Documents without an explicit "kind" but with "name" and "expression"
// are inferred as check rules.
kind, err := dash0yaml.DetectKind([]byte("name: HighErrors\nexpression: up == 0"))
if err != nil {
log.Fatal(err)
}
fmt.Println(kind)
}
Output: CheckRule
func Equivalent ¶ added in v1.21.0
func Equivalent(a, b []byte, additionalIgnoredFields []string, preservedAnnotationKeys []string, opts ...Option) (bool, error)
Equivalent reports whether two documents are semantically equivalent for drift-detection purposes, ignoring fields that don't matter for that decision: server-managed metadata, empty containers, default values, non-preserved annotations, slice element order, and duration-string formatting differences (e.g. "2m" == "2m0s").
a is the reference document (typically the user's local definition) and b is the value to compare against (typically the current API state). additionalIgnoredFields are extra field paths to strip beyond the defaults (e.g. ConditionallyIgnoredFields filtered by AbsentFields, or a kind-specific API-managed field like "spec.routing.assets"). preservedAnnotationKeys lists annotation keys that participate in drift detection; every other annotation is stripped before comparison. WithAnnotationsUnfiltered (and WithFlatDocument, which implies it) disables this filtering entirely, making preservedAnnotationKeys inert -- every annotation key participates in the comparison instead.
Example ¶
package main
import (
"fmt"
"log"
dash0yaml "github.com/dash0hq/dash0-api-client-go/yaml"
)
func main() {
reference := []byte("kind: Dash0View\nmetadata:\n name: my-view\nspec:\n type: spans\n")
// The API response adds a server-managed timestamp Equivalent ignores.
apiResponse := []byte("kind: Dash0View\nmetadata:\n name: my-view\n createdAt: \"2024-01-01T00:00:00Z\"\nspec:\n type: spans\n")
equivalent, err := dash0yaml.Equivalent(reference, apiResponse, nil, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(equivalent)
}
Output: true
func MarshalPrometheusRule ¶
func MarshalPrometheusRule(rule *dash0.PrometheusAlertRule) ([]byte, error)
MarshalPrometheusRule converts a PrometheusAlertRule (Dash0 API format) back to a Prometheus rule YAML document.
Example ¶
package main
import (
"fmt"
"log"
dash0 "github.com/dash0hq/dash0-api-client-go"
dash0yaml "github.com/dash0hq/dash0-api-client-go/yaml"
)
func main() {
forDur := dash0.Duration("5m")
summary := "High error rate detected"
rule := &dash0.PrometheusAlertRule{
Name: "my-group - HighErrors",
Expression: "sum(rate(errors[5m])) > 0.1",
For: &forDur,
Annotations: &dash0.PrometheusAlertRule_Annotations{
Summary: &summary,
},
}
// Marshal to YAML and unmarshal back to verify the round-trip.
data, err := dash0yaml.MarshalPrometheusRule(rule)
if err != nil {
log.Fatal(err)
}
roundTripped, err := dash0yaml.UnmarshalPrometheusRule(data)
if err != nil {
log.Fatal(err)
}
fmt.Println(roundTripped.Name)
fmt.Println(roundTripped.Expression)
fmt.Println(*roundTripped.For)
fmt.Println(*roundTripped.Annotations.Summary)
}
Output: my-group - HighErrors sum(rate(errors[5m])) > 0.1 5m High error rate detected
func MergeAnnotations ¶ added in v1.20.0
MergeAnnotations merges a PrometheusRule document's top-level metadata.annotations into a rule's own annotations. Rule-level annotations win on key conflict, mirroring the Dash0 Operator's behavior (dash0-operator/internal/controller/prometheus_rules_controller.go, mergeAnnotations). Nil-safe: either or both inputs may be nil.
UnmarshalPrometheusRule and ParseAsPrometheusAlertRules already apply this on the write path, so callers converting a document for the API do not need to. It is exported for downstream IaC tools that must model the same merge elsewhere: the Terraform provider, for example, compares a user's config against an API response that already reflects the merge, and so has to apply it to its comparison copy. Sharing the precedence rule keeps those consumers from drifting from the client.
func Normalize ¶ added in v1.21.0
func Normalize(data []byte, additionalIgnoredFields []string, preservedAnnotationKeys []string, opts ...Option) ([]byte, error)
Normalize normalizes a YAML/JSON document by removing fields that don't participate in drift detection: server-managed metadata, empty containers, default values, and non-preserved annotations. additionalIgnoredFields are extra dot-separated field paths to strip (e.g. from ConditionallyIgnoredFields, or a kind-specific API-managed field). preservedAnnotationKeys lists annotation keys that should survive normalization (e.g. "dash0.com/sharing"); every other annotation is stripped. If empty, all annotations are stripped. See WithAnnotationsRoot for documents that don't nest annotations/labels under "metadata". WithAnnotationsUnfiltered (and WithFlatDocument, which implies it) disables this filtering entirely, making preservedAnnotationKeys inert -- every annotation key participates in normalization instead.
Example ¶
package main
import (
"fmt"
"log"
dash0yaml "github.com/dash0hq/dash0-api-client-go/yaml"
)
func main() {
input := []byte("kind: Dash0View\nmetadata:\n name: my-view\n createdAt: \"2024-01-01T00:00:00Z\"\nspec:\n type: spans\n")
normalized, err := dash0yaml.Normalize(input, nil, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(normalized))
}
Output: metadata: name: my-view spec: type: spans
func ParseAsDashboard ¶
func ParseAsDashboard(data []byte) (*dash0.DashboardDefinition, error)
ParseAsDashboard detects whether data is a Dashboard or PersesDashboard CRD, unmarshals it, and returns a normalized DashboardDefinition ready for the API. PersesDashboard CRDs are converted via dash0.ConvertPersesDashboardToDashboard.
Example ¶
package main
import (
"fmt"
"log"
dash0 "github.com/dash0hq/dash0-api-client-go"
dash0yaml "github.com/dash0hq/dash0-api-client-go/yaml"
)
func main() {
data := []byte(`kind: Dashboard
metadata:
name: My Dashboard
dash0Extensions:
id: dash-123
dataset: production
spec:
display:
name: My Dashboard
`)
dashboard, err := dash0yaml.ParseAsDashboard(data)
if err != nil {
log.Fatal(err)
}
fmt.Println(dashboard.Metadata.Name)
fmt.Println(dash0.GetDashboardID(dashboard))
fmt.Println(string(*dashboard.Metadata.Dash0Extensions.Dataset))
}
Output: My Dashboard dash-123 production
Example (PersesDashboard) ¶
package main
import (
"fmt"
"log"
dash0 "github.com/dash0hq/dash0-api-client-go"
dash0yaml "github.com/dash0hq/dash0-api-client-go/yaml"
)
func main() {
// PersesDashboard CRDs are automatically detected and converted to
// the Dash0 DashboardDefinition format.
// The dash0.com/id and dash0.com/dataset labels are extracted into
// dash0Extensions.
data := []byte(`apiVersion: perses.dev/v1alpha1
kind: PersesDashboard
metadata:
name: my-perses-dashboard
labels:
dash0.com/id: perses-123
dash0.com/dataset: production
annotations:
dash0.com/folder-path: /team/sre
spec:
display:
name: SRE Overview
panels: {}
`)
dashboard, err := dash0yaml.ParseAsDashboard(data)
if err != nil {
log.Fatal(err)
}
fmt.Println(dashboard.Metadata.Name)
fmt.Println(dash0.GetDashboardID(dashboard))
fmt.Println(string(*dashboard.Metadata.Dash0Extensions.Dataset))
fmt.Println(*dashboard.Metadata.Annotations.Dash0ComfolderPath)
}
Output: SRE Overview perses-123 production /team/sre
func ParseAsPrometheusAlertRules ¶
func ParseAsPrometheusAlertRules(data []byte) ([]*dash0.PrometheusAlertRule, error)
ParseAsPrometheusAlertRules detects whether data is a CheckRule or PrometheusRule CRD, unmarshals it, and returns one or more normalized check rules ready for the API. A plain CheckRule returns a slice of length 1. A PrometheusRule CRD returns one entry per alerting rule (recording rules are skipped). For a PrometheusRule CRD, the document's top-level metadata.annotations are merged into each rule's own annotations before conversion, with rule-level annotations winning on key conflict.
Example ¶
package main
import (
"fmt"
"log"
dash0 "github.com/dash0hq/dash0-api-client-go"
dash0yaml "github.com/dash0hq/dash0-api-client-go/yaml"
)
func main() {
// The dash0.com/id and dash0.com/dataset labels from CRD metadata are
// propagated to every returned rule.
data := []byte(`apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: my-rules
labels:
dash0.com/id: rule-42
dash0.com/dataset: production
spec:
groups:
- name: availability
rules:
- alert: HighErrorRate
expr: "sum(rate(errors[5m])) > 0.1"
for: 5m
- alert: ServiceDown
expr: up == 0
`)
rules, err := dash0yaml.ParseAsPrometheusAlertRules(data)
if err != nil {
log.Fatal(err)
}
for _, r := range rules {
fmt.Printf("%s (id=%s, dataset=%s)\n", r.Name, dash0.StringValue(r.Id), dash0.StringValue(r.Dataset))
}
}
Output: HighErrorRate (id=rule-42, dataset=production) ServiceDown (id=rule-42, dataset=production)
Example (NativeCheckRule) ¶
package main
import (
"fmt"
"log"
dash0 "github.com/dash0hq/dash0-api-client-go"
dash0yaml "github.com/dash0hq/dash0-api-client-go/yaml"
)
func main() {
// A native check rule (no "kind" field, detected by the presence of
// "name" and "expression") returns a single-element slice.
// The "dataset" field is preserved from the input.
data := []byte(`
name: HighErrorRate
expression: "sum(rate(errors[5m])) > 0.1"
dataset: production
`)
rules, err := dash0yaml.ParseAsPrometheusAlertRules(data)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(rules))
fmt.Println(rules[0].Name)
fmt.Println(rules[0].Expression)
fmt.Println(dash0.StringValue(rules[0].Dataset))
}
Output: 1 HighErrorRate sum(rate(errors[5m])) > 0.1 production
func UnmarshalPrometheusRule ¶
func UnmarshalPrometheusRule(data []byte) (*dash0.PrometheusAlertRule, error)
UnmarshalPrometheusRule converts a Prometheus rule YAML document to a PrometheusAlertRule (Dash0 API format). The YAML must contain exactly one group with one rule. The check rule name is composed as "groupName - alertName". The document's top-level metadata.annotations are merged into the rule's own annotations before conversion, with rule-level annotations winning on key conflict.
Example ¶
package main
import (
"fmt"
"log"
dash0 "github.com/dash0hq/dash0-api-client-go"
dash0yaml "github.com/dash0hq/dash0-api-client-go/yaml"
)
func main() {
// The dash0.com/id and dash0.com/dataset labels from CRD metadata are
// extracted and set on the returned PrometheusAlertRule.
data := []byte(`apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
labels:
dash0.com/id: rule-42
dash0.com/dataset: production
spec:
groups:
- name: my-group
interval: 1m
rules:
- alert: HighErrors
expr: "sum(rate(errors[5m])) > 0.1"
for: 5m
annotations:
summary: High error rate detected
`)
rule, err := dash0yaml.UnmarshalPrometheusRule(data)
if err != nil {
log.Fatal(err)
}
fmt.Println(rule.Name)
fmt.Println(rule.Expression)
fmt.Println(dash0.StringValue(rule.Id))
fmt.Println(dash0.StringValue(rule.Dataset))
fmt.Println(*rule.For)
fmt.Println(*rule.Interval)
fmt.Println(*rule.Annotations.Summary)
}
Output: my-group - HighErrors sum(rate(errors[5m])) > 0.1 rule-42 production 5m 1m High error rate detected
Types ¶
type Option ¶ added in v1.21.0
type Option func(*Options)
Option configures Options.
func WithAnnotationsRoot ¶ added in v1.21.0
WithAnnotationsRoot overrides the default "metadata" root under which "annotations" and "labels" are expected to live. Pass "" for a flat document where they live at the document root.
Example ¶
package main
import (
"fmt"
"log"
dash0yaml "github.com/dash0hq/dash0-api-client-go/yaml"
)
func main() {
// A CRD nested one level deeper than the usual top-level "metadata".
reference := []byte("spec:\n metadata:\n annotations:\n dash0.com/sharing: team:a\n")
apiResponse := []byte("spec:\n metadata:\n annotations:\n dash0.com/sharing: team:b\n")
equivalent, err := dash0yaml.Equivalent(reference, apiResponse, nil, []string{"dash0.com/sharing"}, dash0yaml.WithAnnotationsRoot("spec.metadata"))
if err != nil {
log.Fatal(err)
}
fmt.Println(equivalent)
}
Output: false
func WithAnnotationsUnfiltered ¶ added in v1.21.0
func WithAnnotationsUnfiltered() Option
WithAnnotationsUnfiltered disables preservedAnnotationKeys filtering entirely, so every key already present in the annotations map at AnnotationsRoot takes part in comparison (the unconditional stringify/default-value cleanup still applies). Without this option, an empty preservedAnnotationKeys means "strip every annotation" -- correct for a metadata.annotations convention that is provenance-only unless a key is explicitly opted back in (Dashboard, View, ...). Use this option for a kind whose annotations map holds genuine user content by convention instead -- dash0-cli's CheckRule kind is the one Dash0 asset shaped this way: its flat top-level "annotations" carries summary/description/sharing directly (the same PrometheusAlertRule type backs both a native CheckRule document and a PrometheusRule CRD's per-alert annotations, which TerraformProvider-dash0 already compares in full, only auto-removing the three known default values).
Example ¶
package main
import (
"fmt"
"log"
dash0yaml "github.com/dash0hq/dash0-api-client-go/yaml"
)
func main() {
// dash0-cli's CheckRule kind carries genuine content directly in its
// flat top-level "annotations" map, so every key -- not just an
// explicitly preserved allow-list -- must participate in comparison.
reference := []byte("id: rule-1\nannotations:\n summary: High error rate\n")
apiResponse := []byte("id: rule-1\nannotations:\n summary: Error rate too high\n")
equivalent, err := dash0yaml.Equivalent(reference, apiResponse, nil, nil, dash0yaml.WithAnnotationsRoot(""), dash0yaml.WithAnnotationsUnfiltered())
if err != nil {
log.Fatal(err)
}
fmt.Println(equivalent)
}
Output: false
func WithFlatDocument ¶ added in v1.21.0
func WithFlatDocument() Option
WithFlatDocument configures a flat document whose annotations carry genuine user content by convention, the dash0-cli CheckRule shape WithAnnotationsUnfiltered's doc comment describes. Equivalent to WithAnnotationsRoot("") combined with WithAnnotationsUnfiltered(); prefer this over combining the two by hand for that shape. WithAnnotationsRoot("") alone still means what it always has -- a flat document whose non-preserved annotations should still be filtered out, a legitimate and separately supported combination -- so this option does not change or replace it, only names the specific pairing CheckRule-shaped callers need.
Example ¶
package main
import (
"fmt"
"log"
dash0yaml "github.com/dash0hq/dash0-api-client-go/yaml"
)
func main() {
// dash0-cli's native CheckRule kind has no "metadata" nesting, and its
// top-level "annotations" carries genuine user content (summary here)
// rather than server-managed provenance -- WithFlatDocument() tells
// Equivalent both facts at once.
reference := []byte("id: rule-1\nname: test-rule\nannotations:\n summary: High error rate\n")
apiResponse := []byte("id: rule-1\nname: test-rule\nannotations:\n summary: Error rate too high\n")
// WithAnnotationsRoot("") alone is the footgun WithFlatDocument exists
// to prevent: with an empty preservedAnnotationKeys list, it strips
// every annotation -- including this genuine "summary" change -- so
// the drift goes undetected.
missedDrift, err := dash0yaml.Equivalent(reference, apiResponse, nil, nil, dash0yaml.WithAnnotationsRoot(""))
if err != nil {
log.Fatal(err)
}
fmt.Println(missedDrift)
// WithFlatDocument() pairs WithAnnotationsRoot("") with
// WithAnnotationsUnfiltered() so the same content change is detected.
detectedDrift, err := dash0yaml.Equivalent(reference, apiResponse, nil, nil, dash0yaml.WithFlatDocument())
if err != nil {
log.Fatal(err)
}
fmt.Println(detectedDrift)
}
Output: true false
type Options ¶ added in v1.21.0
type Options struct {
// AnnotationsRoot is the dot-separated path under which "annotations",
// "labels", and the rest of defaultIgnoredFields live. Defaults to
// "metadata", matching every Kubernetes-CRD-shaped Dash0 asset
// (Dashboard, View, SyntheticCheck, PrometheusRule, Dash0SpamFilter,
// Dash0NotificationChannel, Dash0Team). Set to "" via WithAnnotationsRoot
// for a flat document that carries "annotations"/"labels" at the root
// instead of nested under "metadata" -- dash0-cli's native (non-CRD)
// CheckRule kind is the one Dash0 asset shaped this way.
AnnotationsRoot string
// AnnotationsUnfiltered disables preservedAnnotationKeys filtering: every
// key in the annotations map at AnnotationsRoot participates in
// comparison (still subject to the unconditional stringify/default-value
// cleanup cleanupMap always does), instead of being stripped unless
// explicitly preserved. See WithAnnotationsUnfiltered.
AnnotationsUnfiltered bool
}
Options configure Equivalent and Normalize.