yaml

package
v1.21.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 24, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AbsentFields added in v1.21.0

func AbsentFields(data []byte, fields []string) []string

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

func DetectKind(data []byte) (string, error)

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

func MergeAnnotations(metadataAnnotations, ruleAnnotations map[string]string) map[string]string

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

func WithAnnotationsRoot(root string) Option

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL