Skip to content

feat(diff): add dash0 diff command - #256

Draft
mmanciop wants to merge 3 commits into
refactor/shared-document-modelfrom
feat/diff-command
Draft

feat(diff): add dash0 diff command#256
mmanciop wants to merge 3 commits into
refactor/shared-document-modelfrom
feat/diff-command

Conversation

@mmanciop

@mmanciop mmanciop commented Aug 17, 2026

Copy link
Copy Markdown
Member

Summary

  • New experimental dash0 diff -f <file|directory> [--since <ref>] command previews what apply would do (creates, updates, and — with --since — deletions), without ever mutating anything.
  • Fetches each document's current state from Dash0 first (per-kind, non-mutating) to accurately distinguish create from update — unlike apply --dry-run, which is local-only and cannot tell the two apart.
  • All-or-nothing fetch gate: any document fetch failure (other than a plain "not found") aborts the whole plan before anything is printed.
  • --since <ref> reuses apply --since's identifier-diffing to preview deletions alongside creates/updates, never deleting anything.
  • Three-way exit code (0 clean, 1 differences pending, 2 error), modeled on kubectl diff — a deliberate, narrow exception to this CLI's uniform 0/1 convention.
  • Human-mode unified-diff report and agent-mode JSON output matching apply --dry-run's {path, changes} shape, extended with a "create" op.
  • apply --dry-run is now deprecated in favor of dash0 diff, with a runtime stderr warning and updated help text.

Semantic comparison engine

The comparison behind diff (and apply/dashboards update's diff rendering, since they share internal/asset.PrintDiff) is semantic, not textual. It's backed by a new shared engine in dash0hq/dash0-api-client-go#32 — ported from terraform-provider-dash0's existing internal/converter (used across all 8 of its resource types today), rather than reimplementing the same logic separately.

This closes real gaps a naive text diff has: key/slice reordering, duration-string formatting ("2m" vs "2m0s"), int-vs-float representation differences between YAML and JSON, API-populated defaults absent from the local document, and (for check rules specifically) the default annotation values the Dash0 JSON → Prometheus YAML conversion omits by default. None of these are reported as changes anymore, and the printed diff itself is now built from normalized YAML so it isn't cluttered by them either.

Note: this PR currently depends on dash0-api-client-go#32, which is open but not yet merged/released — developed against this repo's existing local replace directive in go.mod (an established pattern here for in-flight client-library changes). The replace directive will be removed and go.mod bumped to a real released version before this is ready to merge.

Supporting changes

  • Moved apply --since's git-diffing plan (deletionPlan) into internal/git as the exported SincePlan/ComputeSincePlan, so diff can reuse it without depending on internal/apply.
  • Moved apply's document validation (validateDocuments) and PrometheusRule CRD parsing into internal/asset as ValidateDocuments/ParsePrometheusRuleCRD, for the same reason.
  • Found and fixed a real cobra Traverse bug in cmd/dash0/main.go: a boolean persistent flag preceding the subcommand (e.g. dash0 --experimental diff ..., the form used throughout this CLI's own docs) was misresolved to the root command, because Traverse's flag lookup doesn't see PersistentFlags() until cobra's own lazy merge runs during Execute. This silently broke diff's exit code (always falling back to 1 instead of 2 on a genuine error) for that invocation order. Fixed by merging persistent flags into the root command's own flag set before the pre-flight Traverse call; regression test included (TestTraverseTargetCommand/TestTraverseTargetCommand_ReproducesBugWithoutFix).

Why this is stacked

This PR targets refactor/shared-document-model (#255), which extracted the shared document model both apply and diff need.

Test plan

  • go build ./...
  • go vet ./...
  • go test ./...
  • go test -tags=integration ./...
  • make lint
  • make skill-validate
  • make chlog-validate
  • Manual smoke test: dash0 --experimental diff -f <fixture> for create/update/clean/error cases, exit codes 0/1/2 confirmed with both -X-before and -X-after subcommand ordering.
  • New tests covering the semantic engine's real-world behavior on dash0-cli's actual asset types: reordered slices, duration-format equivalence, check-rule default annotation values, the bare sharing annotation key check rules use, custom check-rule labels, and notification channels' spec.routing.assets.

@mmanciop
mmanciop requested a review from a team as a code owner August 17, 2026 15:12
mmanciop added a commit that referenced this pull request Aug 17, 2026
New experimental `dash0 diff -f <file|directory> [--since <ref>]` command
previews what `apply` would do without ever creating, updating, or deleting
anything:

- Fetches each document's current state from Dash0 (per-kind, non-mutating)
  to accurately distinguish create from update, unlike `apply --dry-run`,
  which is local-only and can't tell the two apart.
- All-or-nothing fetch gate: any document fetch failure (other than a plain
  "not found") aborts the whole plan before anything is printed.
- `--since <ref>` reuses apply's identifier-diffing to preview deletions
  alongside creates/updates, never deleting anything regardless of
  confirmation.
- Three-way exit code (0 clean, 1 differences pending, 2 error), modeled on
  `kubectl diff` — a deliberate, narrow exception to this CLI's uniform 0/1
  convention.
- Human-mode unified-diff report and agent-mode JSON output matching
  `apply --dry-run`'s {path, changes} shape, extended with a "create" op.

Supporting changes:
- Move apply --since's git-diffing plan (deletionPlan) into
  internal/git as the exported SincePlan/ComputeSincePlan, so diff can
  reuse it without depending on internal/apply.
- Move apply's document validation (validateDocuments) and PrometheusRule
  CRD parsing into internal/asset as ValidateDocuments/ParsePrometheusRuleCRD,
  for the same reason.
- Fix a real cobra Traverse bug in cmd/dash0/main.go: a boolean persistent
  flag preceding the subcommand (e.g. `dash0 --experimental diff ...`, the
  form used throughout this CLI's docs) was misresolved to the root command
  because Traverse's flag lookup doesn't see PersistentFlags() until cobra's
  own lazy merge runs during Execute. This silently broke diff's exit code
  (always falling back to 1 instead of 2 on a genuine error) for that
  invocation order.
- Deprecate `apply --dry-run` in favor of `dash0 diff`, with a runtime
  stderr warning and updated help text.

References #256.
@mmanciop
mmanciop marked this pull request as draft August 17, 2026 15:34
Replace the naive textual YAML comparison in internal/asset/diff.go
(HasDifference/PrintDiff) with dash0-api-client-go/yaml's new
Equivalent/Normalize functions, so dash0 diff (and apply/dashboards
update, which share PrintDiff) stop misreporting drift from purely
cosmetic differences: key/slice ordering, duration-string formatting
("2m" vs "2m0s"), int-vs-float representation, and API-populated
defaults absent from the local document.

Adds a per-kind semanticCompareConfig table (preserved annotation keys,
extra ignored fields, and -- for CheckRule specifically -- the
AnnotationsRoot/AnnotationsUnfiltered options the flat, non-CRD-shaped
PrometheusAlertRule type needs) and wires it into both the equivalence
check and the rendered diff, which is now built from normalized YAML
instead of the raw stripped YAML.

Depends on dash0hq/dash0-api-client-go#32 (not yet merged/released;
developed against the existing local `replace` directive in go.mod,
matching this repo's established pattern for in-flight client-library
changes).
dash0hq/dash0-api-client-go#32 regenerates generated.go against the
current upstream OpenAPI spec, which drops SamplingMode's bare
Adaptive/Disabled aliases in favor of the type-prefixed
SamplingModeAdaptive/SamplingModeDisabled (already generated
side-by-side since v1.17.0, per that repo's api_compatibility_exceptions.txt).
Needed to keep this repo building against the local `replace` directive
while that PR is in flight.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant