Skip to main content
Version: 0.3 · v0.3.0

CLI reference

The CLI has one command: mirops scan. The inputs you give pick the checks. Every flag has an environment-variable equivalent, MIROPS_<FLAG> (dashes become underscores); flags take precedence over env vars, and a variable that doesn't parse stops the CLI with exit 2.

mirops scan --source <mirror report> [-f <change>] [-n <namespaces>] [--upgrade --upgrade-source <analysis report>] [--enforce] [--output table|json]

Flags​

FlagEnv varDefaultPurpose
--sourceMIROPS_SOURCE—The ClusterMirror report (<name>.mirops): path, file://, s3://, azure://, http(s)://. An UpgradeAnalysis report here is gated on directly, as in 0.1.0.
-f, --fileMIROPS_FILE—What you're about to apply: a manifest file, a directory, - for stdin, or a Terraform plan in JSON. See Impact of a change.
-n, --namespaceMIROPS_NAMESPACE—Namespaces to show the state of — and, with -f, the only ones the change is judged in: one, a comma-separated list, or all. Never blocks by itself.
--upgradeMIROPS_UPGRADEfalseRun the upgrade check.
--upgrade-sourceMIROPS_UPGRADE_SOURCE—The UpgradeAnalysis report (<name>.mirops) to gate on. Required with --upgrade.
--enforceMIROPS_ENFORCEfalseExit 1 when a check blocks — the change breaks something, or the upgrade verdict blocks
--max-mirror-ageMIROPS_MAX_MIRROR_AGE1hOldest mirror report -f accepts; older exits 2
--enforce-levelMIROPS_ENFORCE_LEVELcriticalMinimum level that blocks: critical | warning
--outputMIROPS_OUTPUTtableOutput format: table | json
--version——Print the CLI version
-h, --help——Help

--api-url, --api-token and --cluster exist for a hosted mode that isn't implemented yet.

The impact check​

With -f, the CLI reads the change, keeps the objects in the -n namespaces (all of them without -n), and looks each one up in the mirror's graph. With --enforce it exits 1 when the change deletes something still in use, mounts a Pending, Lost or missing volume, routes an Ingress to a missing Service, or depends on something it deletes. The full rules, the namespace filter and what it refuses are in Impact of a change.

The upgrade check​

With --upgrade, the CLI:

  1. Reads the mirror report. If it says upgrade analysis is off in the cluster, it exits 2 with the command to turn it on.
  2. Reads the analysis report at --upgrade-source. The target version comes from that report.
  3. If the cluster (per the mirror) already runs the target version, reports skipped and passes.
  4. Otherwise prints the analysis and its verdict; with --enforce, a blocked verdict exits 1.

The CLI never looks the analysis up by itself — you give its URL.

Exit codes​

CodeMeaning
0Passed, or nothing to check on purpose (no upgrade pending)
1A check blocked — the change breaks something, or the upgrade verdict blocked — and --enforce is set
2Couldn't evaluate: missing or unreadable source, unknown report kind, bad flag, variable or argument, nothing to scan, a namespace in neither the mirror nor the change, a source that must be rendered first (values.yaml, kustomization.yaml, {{ }}, .tf, binary plan), a mirror report older than --max-mirror-age, --upgrade without --upgrade-source, upgrade analysis off. Never a pass.

Examples​

export MIROPS_SOURCE=s3://bucket/prod/default.mirops

# impact of a change — fail when it breaks something in payments
helm template payments ./chart | mirops scan -f - -n payments --enforce
mirops scan -f k8s/ -n payments --enforce
terraform show -json plan.out | mirops scan -f - --enforce

# a namespace's state — never fails
mirops scan -n payments
mirops scan -n payments,checkout
mirops scan -n all # only namespaces with something at risk

# CI gate — fail on CRITICAL
mirops scan --upgrade --upgrade-source s3://bucket/prod/to-1-35.mirops --enforce

# stricter — also fail on WARNING
mirops scan --upgrade --upgrade-source s3://bucket/prod/to-1-35.mirops --enforce --enforce-level warning

# JSON for another tool
mirops scan -n all --output json | jq '.checks'

Output​

table (the default) prints headed sections — the mirror, then IMPACT, NAMESPACES and the upgrade verdict, for the checks that ran. Color is used only on a real terminal: it's off when the output is piped or redirected, and when NO_COLOR is set.

JSON output​

--output json prints one document:

FieldMeaning
schemaVersion1. Bumped only for a breaking change; new optional fields don't bump it.
sourceThe --source that was read.
mirrorThe mirror's name, generatedAt, clusterVersion, and summary.
checks.impactsummary (format, changes, evaluated, namespaces, what was skipped — otherNamespaces, noNamespace, clusterScoped, nonKubernetes, noOp, notEvaluable — and ignored files), blocking, blockers[], info[], notVerified[]. Each finding has object, namespace, message, and dependents.
checks.namespaces[]Each requested namespace: namespace, risk, components, atRisk, atRiskComponents.
checks.otherHealthyNamespacesWith all: how many namespaces were left out because nothing in them is at risk.
checks.upgradestatus (evaluated | skipped), source, blocking, allow, level, blockers, reason, targetVersion, …

checks.upgrade.blocking says whether this verdict fails the pipeline under --enforce at the current --enforce-level.

Sources​

SchemeAuth
local / file://—
http://, https://— (the operator's report endpoint)
s3://bucket/keyIRSA / instance profile / env vars / static keys
azure://account/container/blobDefaultAzureCredential (Workload/Managed Identity, az login, service principal)

Credentials​

The CLI reads reports from the cloud (it doesn't write — that's the operator's job) and has no credential flags: each source resolves credentials for its own scheme, so the mirror and the analysis reports can live in different places. It only needs read access — s3:GetObject on the report object, or read on the blob.

In GitHub Actions, the cleanest option is a role assumed with OIDC — no stored keys:

permissions: { contents: read, id-token: write }
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::<account>:role/<read-only role>
aws-region: us-east-1
- run: mirops scan -f k8s/ --enforce

The role's trust policy must accept the repository's OIDC subject. Repositories using GitHub's immutable subject send repo:<org>@<org-id>/<repo>@<repo-id>:… instead of repo:<org>/<repo>:… — allow the one your repository uses.

Or the provider's standard environment variables:

SourceEnvironment variables
S3AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION (and AWS_SESSION_TOKEN for temporary creds)
Azure BlobAZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET

Inside the cloud you can set nothing and rely on IRSA (S3) or Workload / Managed Identity (Azure); from a laptop, az login or an AWS profile works too.

export MIROPS_SOURCE="azure://myacct/reports/default.mirops"
export AZURE_CLIENT_ID=... AZURE_TENANT_ID=... AZURE_CLIENT_SECRET=...
mirops scan -n all

See Use it in CI/CD for a full pipeline example.