gitops-preview-toolkit

module
v0.2.0-rc.1 Latest Latest
Warning

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

Go to latest
Published: Oct 7, 2026 License: GPL-3.0

README

gitops-preview-toolkit

Build Status GitHub release (including prereleases) Go Report Card License

Preview rendered Kubernetes resource changes before a GitOps change reaches the cluster.

gitops-preview renders supported GitOps inputs through persistent local plugins, compares two revisions, and turns the result into CLI diffs, structured JSON, PR comments, policy checks, and an interactive HTML report. fmp remains a compatibility executable with the same commands, flags, exit codes, FMP_* environment variables and Action report artifacts. Existing fmp examples below work with either executable.

The core host owns comparison and reports. gitops-preview-flux owns Flux, Git, Helm and path rendering; the optional gitops-preview-crossplane plugin expands compositions through an external real Crossplane engine. The core binary does not embed Helm or Flux rendering libraries. The canonical repository and Go module are github.com/tobiash/gitops-preview-toolkit.

It is built for Kubernetes platform teams reviewing GitOps pull requests where the source YAML is not the whole story: Flux Kustomization dependencies, HelmRelease rendering, external GitRepository sources, Crossplane composition outputs, generated metadata, and multi-cluster layouts all affect the final manifests.

[!NOTE] v0.2.0-rc.1 is a release candidate, not the latest stable release. Pin this version for reproducible evaluation; see the release notes and Go API migration. Workflows and report details may continue to evolve.


What it shows you

Instead of asking “what YAML changed?”, gitops-preview answers “what desired Kubernetes resources change after the supported GitOps inputs are rendered?”

flowchart TD
    A[Git branch, PR, or local worktree] --> B[Flux Kustomizations]
    B --> C[HelmReleases]
    B --> D[External GitRepository sources]
    C --> E[Rendered Kubernetes manifests]
    D --> E
    E --> F[CLI diffs and summaries]
    E --> G[Policy checks and PR labels]
    E --> I[Interactive HTML report]

Use it to catch risky changes before merge:

  • image updates and replica changes hidden inside Helm values
  • Ingress/Gateway/HTTPRoute or Service exposure changes
  • Secret, CRD, PVC, StatefulSet, DaemonSet, and Namespace changes
  • noisy generated fields that would otherwise create permanent diffs
  • cluster-specific impact in multi-cluster Flux repositories

HTML report preview

Enable html-report in the GitHub Action to generate a browsable report artifact, or deploy it to GitHub Pages for direct links from pull requests.

Impact overview Unified resource diff Side-by-side diff
HTML report overview Unified diff view Side-by-side diff view

View the sample HTML report in a browser, or download the checked-in single-file HTML report to open it locally. The archived sample retains its original title and payload; the nested Crossplane/Flux example demonstrates the combined workflow.

The report includes:

  • long-form impact summary
  • added / modified / deleted resource counts
  • multi-cluster breakdowns
  • resource browser with search and filters for kind, namespace, cluster, producer, and action
  • unified and side-by-side per-resource diffs
  • policy classifications and violations when configured

Key features

  • Flux-aware rendering — discovers Kustomization.spec.path, follows Flux dependency patterns, and can resolve external GitRepository sources.
  • HelmRelease support — renders HelmRelease resources through the Helm SDK, including post-renderers and commonMetadata.
  • Crossplane composition preview — opt-in expansion through a pinned external controller engine, with logical identities for unnamed outputs and separate evaluation evidence.
  • Git-aware diffs — compare HEAD, branches, commits, local paths, or your dirty worktree.
  • Multi-cluster reports — configure several cluster roots and review each cluster independently.
  • SOPS support — decrypt encrypted resources locally when requested.
  • Noise reduction — normalize generated fields such as timestamps, random hashes, or certificate data.
  • Policy checks — classify changes, block risky PRs, and suggest/apply labels using built-in or custom Rego policies.
  • AI assessment — optionally generate a concise review summary and provenance-aware classifications with OpenAI, Anthropic, Z.AI, MiniMax, or OpenRouter.
  • Agent-friendly output — render, diff, test, and discovery commands support structured JSON, with failure diagnostics kept in a single document.

Install

Install the host and its sibling plugins, or extract a complete release archive into one directory on PATH. Installing only go install ./cmd/fmp (or only cmd/gitops-preview) installs a host with no render plugin; render operations fail unless gitops-preview-flux is already installed beside it or on PATH. The Crossplane plugin is additionally needed for --crossplane.

From source
go install ./cmd/gitops-preview ./cmd/gitops-preview-flux ./cmd/gitops-preview-crossplane
# Optional compatibility command:
go install ./cmd/fmp

To install the complete toolkit including the compatibility executable in one command:

go install ./cmd/fmp ./cmd/gitops-preview ./cmd/gitops-preview-flux ./cmd/gitops-preview-crossplane

The Go module and GitHub source repository are github.com/tobiash/gitops-preview-toolkit. Test fixture builds disable VCS stamping. Release builds retain normal Go VCS behavior; the Docker action defaults to BUILDVCS=false for source-only build contexts and accepts --build-arg BUILDVCS=true when complete Git metadata is available.

Release candidate via Go

After the RC tag is published, install all commands at the same explicit version:

go install github.com/tobiash/gitops-preview-toolkit/cmd/gitops-preview@v0.2.0-rc.1
go install github.com/tobiash/gitops-preview-toolkit/cmd/gitops-preview-flux@v0.2.0-rc.1
go install github.com/tobiash/gitops-preview-toolkit/cmd/gitops-preview-crossplane@v0.2.0-rc.1
# Optional compatibility command:
go install github.com/tobiash/gitops-preview-toolkit/cmd/fmp@v0.2.0-rc.1

@latest prefers a stable Go module version when one exists; it does not select this RC over an existing stable release. GitHub's /releases/latest likewise excludes prereleases. Use the explicit RC tag until stable promotion.

Requirements
  • git for git-aware diffing and external repository resolution
  • Install the three executables together. Defaults resolve plugins beside the host executable, then on PATH. --flux-plugin and --crossplane-plugin select trusted executable overrides; repository configuration cannot select commands.
  • Helm rendering uses the SDK in the Flux plugin; a standalone helm executable is not required. Unset Helm settings use the plugin's environment defaults.
  • Release archives gitops-preview-toolkit_<tag>_<os>_<arch>.tar.gz contain the core, both plugins, and fmp. Compatibility fmp_<tag>_<os>_<arch>.tar.gz archives include fmp and both plugins. The GitHub Action downloads and caches the complete toolkit.
Configuration and Crossplane

The host discovers .gitops-preview.yaml (or .gitops-preview.yml) first, then legacy .fmp.yaml, .fmp.yml, or .github/fmp.yaml. --config and the existing Action config input select an explicit file. A minimal configuration is:

paths: [clusters/production]
helm: true
sort: true
crossplane:
  enabled: true
  timeout: 1m
  max-functions: 32

crossplane.enabled records repository intent; it never grants execution. Enable Crossplane explicitly from a trusted invocation with --crossplane (or Action input crossplane: true). Neither executable paths nor development endpoints are read from repository configuration or resource annotations.

gitops-preview render . -k clusters/production --crossplane \
  --crossplane-engine /opt/crossplane-v2.4.2/crossplane

# Repeatable trusted overrides for individual Function names:
gitops-preview diff main --crossplane \
  --crossplane-function function-go-templating=127.0.0.1:9443 \
  --crossplane-function function-auto-ready=127.0.0.1:9444

Crossplane rendering defaults to the Docker function runtime and requires access to Docker. Install the external Crossplane controller binary v2.4.2, not the user-facing Crossplane CLI; the default executable name is crossplane-core on PATH, or select its path with --crossplane-engine. The plugin checks its version before execution. For Linux/amd64, the controller artifact is crossplane v2.4.2. The engine is not included in toolkit archives or Dockerfile.action. That image installs the host and both sibling plugins; Crossplane use additionally requires the engine and Docker runtime access.

Install the pinned engine locally, checking its published SHA-256 before executing it (Linux/amd64):

(
set -euo pipefail
engine_dir=$(mktemp -d)
trap 'rm -rf "$engine_dir"' EXIT
url=https://releases.crossplane.io/stable/v2.4.2/bin/linux_amd64/crossplane
curl -fL "$url" -o "$engine_dir/crossplane"
curl -fL "$url.sha256" -o "$engine_dir/crossplane.sha256"
expected=$(tr -d '[:space:]' < "$engine_dir/crossplane.sha256")
printf '%s  %s\n' "$expected" "$engine_dir/crossplane" | sha256sum --check --status
chmod +x "$engine_dir/crossplane"
test "$("$engine_dir/crossplane" --version)" = v2.4.2
mkdir -p "$HOME/.local/bin"
install -m 0755 "$engine_dir/crossplane" "$HOME/.local/bin/crossplane-core"
)

Put $HOME/.local/bin on PATH, or pass --crossplane-engine "$HOME/.local/bin/crossplane-core". The toolkit never downloads an engine implicitly. Engine and plugin executable selection and development endpoints are trusted invocation settings, not fields that a checked-out repository can grant.

Function definitions are automatically derived from rendered Function resources and composition references. Their names select trusted per-function development overrides. Unnamed composed resources use Logical Resource Identity: a stable parent/composition output key for matching and reports, not a generated live Kubernetes name. Composite status and readiness are Evaluation Evidence, separate from desired resources.

Preview performs bounded desired-state discovery, not arbitrary cyclic controller reconciliation. Offline rendering needs locally available sources/charts, cached function images or explicitly reachable development targets, and the external engine. Local-only agent mode restricts remote acquisition; it is not an OS sandbox and does not grant Crossplane execution.

Agent and MCP commands accept the same startup --flux-plugin, --crossplane, --crossplane-plugin, --crossplane-engine and repeatable --crossplane-function flags. Crossplane additionally requires --trusted because composition functions cannot run within local-only policy. Agent/MCP runtime configuration comes exclusively from startup flags: repository Crossplane settings and JSON operation requests cannot choose executables or grant development endpoints.

gitops-preview agent --root . --trusted --crossplane \
  --crossplane-engine "$HOME/.local/bin/crossplane-core" \
  --crossplane-function function-go-templating=127.0.0.1:9443 <<'JSON'
{"operation":"render","paths":["clusters/production"]}
JSON

gitops-preview mcp --root . --trusted --crossplane \
  --crossplane-engine "$HOME/.local/bin/crossplane-core"

CI runs transport/plugin-host integration tests with race detection, plus tests/plugins/run-live.sh on Linux with Go 1.26. The live chain downloads and checksum-checks the pinned controller, starts the real upstream function in Development mode, and exercises both plugins without Docker.


Quick start

Run this inside a Flux repository:

fmp diff

By default, this compares rendered manifests from HEAD with your current worktree, so you can review the cluster impact of uncommitted local changes.

Configure paths or cluster roots in .gitops-preview.yaml (legacy .fmp.yaml also works), or supply -k/--path (for example, gitops-preview diff -k clusters/production). A repository argument selects the source directory; it does not implicitly select . as a render root. Missing render roots are an error.

Common examples:

fmp diff                      # HEAD vs current worktree
fmp diff HEAD~1               # one commit back vs current worktree
fmp diff main feature-branch  # two git refs
fmp diff ./before ./after     # two local directories
fmp diff git:HEAD path:/tmp   # mix git refs and local paths

Render manifests directly:

fmp render <path>
fmp render --output json <path>

Discover and validate Flux resources:

fmp test <path>       # validate renderability
fmp get ks <path>     # list discovered Kustomizations
fmp get hr <path>     # list discovered HelmReleases

Automation helpers:

fmp ci                        # CI-optimized diff mode
fmp detect-permadiffs <path>  # suggest filters for noisy fields

Configuration

Both entry points auto-discover .gitops-preview.yaml or .gitops-preview.yml, then .fmp.yaml, .fmp.yml, or .github/fmp.yaml for compatibility.

Minimal config:

paths:
  - clusters/kube
recursive: true
helm: true
resolve-git: true
sort: true
exclude-crds: true

Multi-cluster repositories can use cluster:path entries:

paths:
  - staging:clusters/staging/flux-system
  - production:clusters/production/flux-system

Or the clusters map:

clusters:
  staging:
    - clusters/staging/flux-system
  production:
    - clusters/production/flux-system

Add field normalizers and policy rules when you want deterministic diffs and automated review gates:

paths:
  - staging:clusters/staging/flux-system
  - production:clusters/production/flux-system
recursive: true
helm: true
resolve-git: true
sort: true

filters:
  - kind: FieldNormalizer
    match:
      kind: Secret
    fieldPaths:
      - path: [data, tls.crt]
        action: replace
        placeholder: "<<auto-generated>>"

policies:
  builtin:
    - image_update
    - ingress_change
    - secret_change
  fail-on:
    - forbid_latest
  labels:
    image_update: image-update
    ingress_change:
      - needs-network-review
      - risky-change
  inline:
    - |
      package fmp
      import rego.v1

      violations contains {
        "id": "forbid_latest",
        "message": sprintf("%s/%s uses :latest", [change.namespace, change.name]),
        "severity": "error"
      } if {
        some change in input.changes
        spec := object.get(change.new, "spec", {})
        template := object.get(spec, "template", {})
        podspec := object.get(template, "spec", {})
        some c in object.get(podspec, "containers", [])
        endswith(object.get(c, "image", ""), ":latest")
      }

In diff mode, configuration is loaded from the current worktree so local config changes take effect immediately.

Built-in policy IDs

Built-in policies currently include:

  • image_update
  • secret_change
  • ingress_change
  • crd_change
  • namespace_delete
  • stateful_workload_change
  • pvc_change
  • service_type_change
  • replicas_change

fail-on matches policy IDs. If any listed rule matches, fmp diff exits non-zero and the GitHub Action fails. labels maps policy IDs to one or more pull request labels.

AI assessment

AI assessment is an optional generated review aid for fmp diff and the GitHub Action. It adds a concise generated summary and may add classifications with provenance: ai; policy classifications use provenance: policy.

Enable it in config:

ai:
  enabled: true
  provider: openai # openai, anthropic, zai, minimax, or openrouter; optional if exactly one token is set
  model: gpt-4o-mini # optional provider-specific override
  fail-on-error: false
  allowed-classifications:
    - network_exposure
    - data_risk
  max-input-bytes: 100000
  max-diff-lines-per-resource: 80
  timeout: 30s
  instructions: |
    Focus on production-impacting Kubernetes changes.

Or enable per invocation:

OPENAI_API_KEY=... fmp diff --ai-assessment --summary

Supported credential environment variables are OPENAI_API_KEY, ANTHROPIC_API_KEY, ZAI_API_KEY, MINIMAX_API_KEY, and OPENROUTER_API_KEY. If no provider is configured, fmp auto-selects only when exactly one supported token is present; missing or ambiguous credentials warn and skip AI assessment by default.

AI classifications participate in the same policies.labels and policies.fail-on mappings as policy classifications. Use ai.allowed-classifications when you want to restrict which generated classification IDs may enter the unified classification set.


GitHub Action

Use the action to review GitOps pull requests automatically. The release-tagged Action downloads the matching binary bundle:

- uses: actions/checkout@v6
  with:
    fetch-depth: 0 # required for git-aware diffing

- uses: tobiash/gitops-preview-toolkit@v0.2.0-rc.1
  with:
    repo: .
    base-ref: origin/main
    resolve-git: true
    comment: true
    html-report: true
    ai-assessment: true
  env:
    OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
Rich HTML report artifact
- uses: tobiash/gitops-preview-toolkit@v0.2.0-rc.1
  with:
    repo: .
    base-ref: origin/main
    html-report: true

The action uploads the report as an artifact and exposes:

  • html-report-file — generated index.html
  • html-report-artifact — uploaded artifact name
  • html-report-url — direct report URL when available
Deploy report to GitHub Pages
- uses: tobiash/gitops-preview-toolkit@v0.2.0-rc.1
  with:
    repo: .
    base-ref: origin/main
    html-report: true
    html-report-pages: true

This publishes reports to the gh-pages branch. When Pages is not enabled, the report is still available as a downloadable single-file HTML artifact that GitHub can preview directly.

Export rendered manifests

The action's export-dir input exports the retained target manifests, including unnamed Crossplane outputs. Set export-changed-only: true to export only added or modified target resources. The destination must be absent or empty; filenames are deterministic and cluster-scoped. Use gitops-preview render for direct manifest output.

Important inputs
Input Description Default
repo Path to the repository checkout .
base-ref Git ref to diff against origin/main
base-sha Exact SHA to diff against; overrides base-ref
paths Directories to render, newline-separated
recursive Recursively discover paths false
helm Enable Helm rendering true
resolve-git Clone external GitRepository sources false
sort Sort output for deterministic diffs false
exclude-crds Strip CRDs from output false
config Explicit config path auto-discovered
comment Post/update a sticky PR comment false
comment-mode When to comment: changes, always, or failure changes
html-report Generate and upload the HTML report false
html-report-pages Deploy the HTML report to GitHub Pages false
ai-assessment Enable AI assessment; empty inherits config
ai-provider AI provider override
ai-model AI model override
ai-fail-on-error Fail if AI assessment cannot be generated
export-dir Reserved; manifest export is not implemented
export-changed-only Reserved; manifest export is not implemented false
fail-on-warning Fail the step on warnings false
fail-on-error Fail the step on errors true
Important outputs
Output Description
status Overall status: clean, changed, warning, or error
changed Whether manifest changes were detected
resources-added / resources-modified / resources-deleted Change counts
resources-total Total changed resources
diff-file Unified diff preview file; may be truncated
summary-file Markdown summary file
report-file Structured JSON report
html-report-url Direct URL to the interactive HTML report when available
export-dir Requested export directory; does not confirm an export
classifications-json Matched classifications with provenance
violations-json Matched policy violations
labels-json Suggested or applied PR labels
policy-failed Whether a configured fail-on policy matched
ai-assessment-json AI assessment object when generated
ai-summary AI-generated summary when generated

[!WARNING] sops-decrypt is intentionally unsupported in the GitHub Action to avoid leaking decrypted content into logs, summaries, comments, or artifacts.


Structured output and exit codes

Agent CLI and MCP

The versioned agent interface adds local stdio MCP tools and a JSON CLI over the same operation service:

fmp agent schema
fmp agent discover --root /absolute/path/to/gitops
fmp agent --root /absolute/path/to/gitops < request.json
fmp mcp --root /absolute/path/to/gitops

It supports discovery, rendering, preview, bounded queries, redacted inspection, snapshot comparison, deterministic checks, and explicit handle release. MCP retains results between calls; CLI batches reuse results within one invocation. Existing CLI output formats are unchanged.

Local-only rendering is the default. Trusted access is a startup option, never a tool argument. Both agent profiles reject known unsupported rendering inputs; there are no apply, publish, decryption, or nested-AI tools. Artifact limits and cooperative timeouts are not an OS sandbox or a process memory quota.

See the agent workflow and contract and the portable OpenCode/Claude Code skill.

Legacy command output

render, diff, test, and get ks/hr support --output json:

fmp diff --output json
fmp render --output json <path>
fmp test --output json <path>
fmp get ks --output json <path>
fmp get hr --output json <path>

JSON output preserves the existing command-specific shapes:

  • rendered resources are wrapped in a v1/List envelope; discovery commands use an items collection
  • resource references use ObjectRef fields: apiVersion, kind, name, namespace
  • errors include {"status":"failure","error":{"reason":"...","message":"..."}} in the same document as any available result, never as a second JSON document
  • diff results include complete, warnings, and a unified changes array carrying action, cluster, structured provenance, before/after origins, and resource snapshots
  • legacy added, deleted, and modified fields remain available; use changes to retain cluster and provenance context for every action
  • empty change collections are [], not null

Ordinary complete diffs continue to exit 0 even when resources change. Use fmp diff --exit-code to exit 1 for differences. A failed policy or execution still returns nonzero without this flag. With JSON output, a failed check retains the available diff and its complete: true field alongside failure information.

Code Meaning
0 Success; may include differences unless --exit-code is set
1 Differences with --exit-code, or an otherwise unclassified error
2 Explicitly classified user input error
3 Expansion failure or explicitly classified dependency failure
5 Policy violation

Not every validation or dependency error is classified separately yet. Treat any nonzero status as a failed command/check and inspect the JSON error or stderr for details.

Incomplete previews

If either side fails to render, fmp suppresses the entire comparison rather than reporting missing resources as deletions or an empty diff as clean. Policy and AI assessments are skipped, and the CLI/action fails. JSON failures include complete: false; action reports carry error status and diagnostics even when advisory fail-on-error behavior is disabled.

Malformed manifests, duplicate output identities, failed source acquisition, unresolved sources at the discovery fixed point, and missing discovered paths are failures. Known fmp configuration files are not treated as raw manifests. Distinct Flux rendering contexts remain separate even when they use the same directory, while equivalent bootstrap paths are deduplicated.

Completeness covers the configured render scope and detected failures. It does not guarantee full Flux feature parity, live-cluster validity, or runtime behavior. Rendering may access remote sources, credentials, or external programs according to your configuration; it is not sandboxed.

The hidden describe command emits command and flag metadata for agents and other automation:

fmp describe

Local pre-commit policy check

Example lefthook hook that checks only staged changes:

pre-commit:
  commands:
    fmp-policy:
      run: |
        if [ ! -f .fmp.yaml ] || ! grep -q "policies:" .fmp.yaml; then
          exit 0
        fi
        if ! command -v fmp >/dev/null 2>&1; then
          echo "warning: fmp not found in PATH, skipping policy check" >&2
          exit 0
        fi
        head_tree=$(git rev-parse HEAD^{tree})
        staged_tree=$(git write-tree)
        fmp diff "git:${head_tree}" "git:${staged_tree}" --summary-only

Development

go test ./...
go vet ./...

For local action development or branch testing, build gitops-preview (or fmp) and both sibling plugins into one directory, then pass the host path to the action with the binary input.

Directories

Path Synopsis
cmd
fmp command
gitops-preview command
internal
cli
pkg
agent
Package agent provides bounded, in-memory manifest analysis sessions.
Package agent provides bounded, in-memory manifest analysis sessions.
agentmcp
Package agentmcp exposes the bounded agent service through MCP tools.
Package agentmcp exposes the bounded agent service through MCP tools.
ai
build
Package build loads manifest paths and executes Kustomize for render plugins.
Package build loads manifest paths and executes Kustomize for render plugins.
crossplanerender
Package crossplanerender implements the persistent Crossplane render plugin.
Package crossplanerender implements the persistent Crossplane render plugin.
fluxrender
Package fluxrender implements the persistent Flux rendering plugin.
Package fluxrender implements the persistent Flux rendering plugin.
plugin
Package plugin defines the engine-independent contract for local render plugins.
Package plugin defines the engine-independent contract for local render plugins.
pluginhost
Package pluginhost evaluates persistent render plugins using bounded, synchronous desired-inventory sweeps.
Package pluginhost evaluates persistent render plugins using bounded, synchronous desired-inventory sweeps.
sourcealiases
Package sourcealiases preserves source-repository URLs in materialized trees without importing a rendering engine or a Git SDK.
Package sourcealiases preserves source-repository URLs in materialized trees without importing a rendering engine or a Git SDK.

Jump to

Keyboard shortcuts

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