Flux Manifest Preview (fmp)

Preview exactly what Flux would apply before a GitOps change reaches the cluster.
fmp renders Flux repositories the way Flux does, compares two revisions, and turns the result into CLI diffs, structured JSON, PR comments, policy checks, exported manifests, and an interactive HTML report.
It is built for Kubernetes platform teams reviewing Flux pull requests where the source YAML is not the whole story: Kustomization dependencies, HelmRelease rendering, external GitRepository sources, generated metadata, and multi-cluster layouts all affect the final manifests.
[!NOTE]
fmp is under active development. It is already useful for local review and CI validation, but workflows and report details may continue to evolve.
What it shows you
Instead of asking “what YAML changed?”, fmp answers “what Kubernetes resources will change after Flux renders this repo?”
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 --> H[Exported manifests]
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 |
 |
 |
 |
View the sample HTML report in a browser, or download the checked-in single-file HTML report to open it locally.
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.
- 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 — every major command supports structured JSON and stable semantic exit codes.
Install
From source
go install ./cmd/fmp
Latest release via Go
go install github.com/tobiash/flux-manifest-preview/cmd/fmp@latest
Requirements
git for git-aware diffing and external repository resolution
helm for Helm chart registry/cache behavior while rendering HelmRelease resources
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.
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
fmp auto-discovers .fmp.yaml, .fmp.yml, or .github/fmp.yaml.
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 Flux pull requests automatically.
- uses: actions/checkout@v6
with:
fetch-depth: 0 # required for git-aware diffing
- uses: tobiash/flux-manifest-preview@vX.Y.Z
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/flux-manifest-preview@vX.Y.Z
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/flux-manifest-preview@vX.Y.Z
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
- uses: tobiash/flux-manifest-preview@vX.Y.Z
with:
repo: .
base-ref: origin/main
export-dir: ./rendered
export-changed-only: true
- name: Validate with kubeconform
run: kubeconform ./rendered/*.yaml
| 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 |
Export rendered manifests directory |
|
export-changed-only |
Only export changed manifests |
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 |
Full unified diff file |
summary-file |
Markdown summary file |
report-file |
Structured JSON report |
html-report-url |
Direct URL to the interactive HTML report when available |
export-dir |
Directory where manifests were exported |
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
All commands that produce YAML or reports 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 follows Kubernetes API conventions:
- lists are wrapped in a
v1/List envelope with apiVersion, kind, and items
- resource references use
ObjectRef fields: apiVersion, kind, name, namespace
- errors use
{"status":"failure","error":{"reason":"...","message":"..."}}
| Code |
Meaning |
| 0 |
Success; for diff, no differences |
| 1 |
Differences found by diff, or generic error |
| 2 |
User input error |
| 3 |
Dependency failure such as git, Helm, or network errors |
| 5 |
Policy violation |
| 10 |
Unexpected internal error |
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 fmp ahead of time and pass it to the action with the binary input.