Skip to main content
Version: 0.3 · v0.3.0

Impact of a change

mirops scan -f reads what you're about to apply and judges it against the mirror report — never the cluster. With --enforce it blocks (exit 1) only on what the change breaks; everything else is context.

export MIROPS_SOURCE=s3://my-reports/prod/default.mirops
helm template payments ./chart | mirops scan -f - -n payments --enforce

What it reads​

InputExample
A manifest file — YAML (one or many documents) or JSON (an object or a List)mirops scan -f k8s/deployment.yaml
A directory — every .yaml, .yml and .json under it, subfolders includedmirops scan -f k8s/
Standard inputkustomize build overlays/prod | mirops scan -f -
A Terraform plan in JSON — kubernetes_* resources, kubernetes_manifest, kubectl_manifestterraform show -json plan.out | mirops scan -f -

From a Terraform plan it reads the kubernetes_* resources it models — deployment, stateful_set, daemon_set, job, cron_job, pod, replication_controller, service, ingress, persistent_volume_claim, config_map, secret, namespace, service_account, with or without the _v1 / _v2 suffix — plus any manifest in kubernetes_manifest and kubectl_manifest. A plan action maps to create, update, delete or replace (delete and recreate). It reads only Kubernetes changes: other providers are counted as not Kubernetes, no-op resources are skipped, and a helm_release is flagged not evaluable — it carries no manifests, so render the chart instead. In a directory it lists the files it ignored — other extensions, empty files, hidden directories (.git, .terraform):

IMPACT — demo-shop
read YAML: 2 Kubernetes change(s) → 2 evaluated
ignored 5 file(s):
.terraform — hidden directory
README.md — not .yaml, .yml or .json
empty.yaml — empty
main.tf — Terraform source (only a plan says what will change)
notes.txt — not .yaml, .yml or .json

It reads identity, action and references only — never Secret data, and it doesn't validate syntax or schema.

This static illustration provides a quick visual summary of the same flow:

A static overview of how mirops scan compares a proposed change with the mirror report and returns a pass or block.

What it blocks​

With --enforce, exit 1:

The change…Example
deletes a PVC, Service, ConfigMap or Secret that something still uses✗ BLOCK deleting service/checkout — ingress/shop routes to it
replaces one of those (a Terraform destroy-and-recreate) while something uses it✗ BLOCK service/checkout is replaced (deleted and recreated), and ingress/shop uses it
mounts a PVC that is Pending, Lost, or doesn't exist✗ BLOCK deployment/reports-worker mounts pvc/payments-db-data, which is Pending
routes an Ingress to a Service that doesn't exist✗ BLOCK ingress/shop routes to service/cart, which isn't in the cluster or this change
depends on something the same change deletesa Deployment that mounts a PVC the plan deletes

A dependency the change itself brings — a new app with its own PVC, Service and Ingress — passes.

What it shows but never blocks​

  • ℹ Context — the current state of what the change touches. A deploy that touches a component that is Down passes, because the deploy may be the fix: ℹ deployment/payments-api is Down right now — this change may be the fix.
  • ℹ Blast radius — what depends on what the change touches: ℹ deployment/checkout — ingress/shop, service/checkout depend on it.
  • ? Not verified — what the mirror report can't vouch for yet: whether a ConfigMap or Secret exists, whether a new Service's selector matches pods, and the references of an object in a namespace the mirror doesn't have (new, empty, or outside the mirror's scope).

Each line starts with its mark: ✗ BLOCK fails the pipeline under --enforce; ℹ and ? never do.

Filtering by namespace​

Set MIROPS_NAMESPACE (or -n) to the namespaces a repo deploys to — one, a comma-separated list, or all. A repo is then judged only on its own namespaces, never on what another team broke.

The change…Without -nWith -n payments
touches payments and ordersboth are judgedonly payments; orders is counted in other namespaces and never blocks
has objects without a namespace (helm template output)not judged — "pass the one namespace it deploys to with -n"judged in payments (with a single -n)
creates a namespacejudgedjudged if it's one of -n

A -n namespace that is neither in the mirror nor in the change exits 2 — most likely a typo, and judging nothing would pass the gate unchecked. -n also prints those namespaces' current state, which never blocks.

What it refuses​

These aren't what will be applied. They exit 2 with the command that renders them:

InputMessage
values.yaml, Chart.yamlHelm chart source — render it first: helm template <release> <chart> | mirops scan -f -
A template with {{ … }}render it first, e.g. helm template
kustomization.yamlKustomize source — render it first: kustomize build <dir> | mirops scan -f -
A .tf fileTerraform source — terraform show -json plan.out | mirops scan -f -
A binary plan (plan.out)convert it with terraform show -json

A fresh mirror​

The verdict is only as good as the mirror. --max-mirror-age (default 1h, MIROPS_MAX_MIRROR_AGE) refuses a report older than that with exit 2 — the operator may have stopped rebuilding it, and judging against it could pass something that is broken now.

Next: use it in CI/CD.