upgradescope server API¶
Rendered from the OpenAPI 3.1.0 document api/openapi.yaml, which tests check against every route and handler response.
The REST API of upgradescope serve: snapshot ingest from agents, the
read API behind the dashboard, the CI gate, auditor exports and cluster
administration.
Authentication. Every credential is a bearer token
(Authorization: Bearer <token>). Reads, the gate and /metrics take
a read credential: serve --read-token, the admin token, or a read
token minted with upgradescope tokens create --read; or, in the
opt-in trusted-proxy mode (--trust-team-header with
--trusted-proxy-cidr), a team header from a proxy in those ranges.
With none configured, reads are open.
Read scopes. --read-token, the admin token and a read token
minted for * read the whole fleet. A read token minted for teams, and
the trusted proxy's header, read only those teams: the clusters one of
them owns a namespace in (by the namespace attribution findings carry,
after --team-map), and of those only their findings, suppressed
findings (those the objects' upgradescope.basit.engineer/ignore
annotations accepted, which the server's evaluations apply; never the
agent's spec.ignore) and team scores. A finding that spans teams is cut to theirs:
only their teams, namespaces and objects, its title counting only
theirs and its detail replaced by one that names only what is kept,
without the folded apiserver caller rows (callers).
A scoped report has no unrecognizedImages, and the helm
capability's reason and skipped list (which name releases as
namespace/name) are withheld. Any other cluster answers the same 404 as an
unknown one, on every per-cluster endpoint and for /api/v1/gate's
?cluster=, and is absent from the cluster list and fleet rollups.
/metrics answers a team-scoped read 403. A scoped answer carries
X-Upgradescope-Teams: <team,...> (each team percent-encoded, as a
URL path segment is: Platform%20Team,payments) and Cache-Control: private,
no-store; a fleet-wide one carries neither. In the trusted-proxy mode
every read answer carries Vary: <header>, Authorization. A finding
whose namespace list was capped is always cut for a scoped read. See
https://abd-ulbasit.github.io/upgradescope/operations/auth/
Pushes take the
shared ingest token (--ingest-token) or a per-cluster token from
upgradescope tokens create. Deleting and renaming clusters take the
admin token (--admin-token); without one the server refuses both.
/healthz and /readyz are never authenticated.
Errors. Every error a handler writes is JSON, {"error": "..."},
with a 4xx or 5xx status. An unknown path under /api/, or below
/healthz, /readyz, /livez or /metrics, is a JSON 404 too. A
known path called with the wrong method gets ServeMux's 405 with an
Allow header and a plain-text body. 500 bodies never carry internal
detail: they read {"error": "internal error"} and the server logs the
cause.
Targets are Kubernetes minors written MAJOR.MINOR (1.38).
Where a target is optional, the default is the next minor above the
version the cluster's latest snapshot reports.
Stability. Paths under /api/v1 keep their meaning within v1;
fields may be added to responses, so the response schemas here do not
forbid fields they do not list: ignore a field you do not know. See the
compatibility policy:
https://abd-ulbasit.github.io/upgradescope/compatibility-policy/
Authentication¶
| Scheme | Type | Description |
|---|---|---|
readToken |
bearer | serve --read-token, the admin token, or a read token from upgradescope tokens create --read (fleet-wide, or scoped to teams). Not required when the server runs with no read credential configured. |
ingestToken |
bearer | serve --ingest-token, or a per-cluster token from upgradescope tokens create. |
adminToken |
bearer | serve --admin-token. |
probes¶
Liveness, readiness and Prometheus metrics.
GET /healthz¶
Liveness. 200 while the process serves; never touches the database.
Auth: none.
| Status | Content type | Schema | Description |
|---|---|---|---|
| 200 | application/json |
Status | The process is up. |
GET /readyz¶
Readiness. 200 when the database answers a ping within 2s, else 503.
Auth: none.
| Status | Content type | Schema | Description |
|---|---|---|---|
| 200 | application/json |
Status | The database answered. |
| 503 | application/json |
Error | An error. |
GET /metrics¶
Prometheus metrics. Prometheus text exposition: HTTP traffic, ingest results, and per cluster the score, verdict, blockers, push age and staleness of its current evaluations. Behind the read token when one is set. Its series name every cluster, so it takes a fleet-wide read credential: a team-scoped one is 403. The metric names are listed in the observability reference.
Auth: readToken (bearer).
| Status | Content type | Schema | Description |
|---|---|---|---|
| 200 | text/plain |
string | Prometheus text format. |
| 401 | application/json |
Error | An error. |
| 403 | application/json |
Error | A team-scoped read credential. |
| 503 | application/json |
Error | Reads of the whole fleet (/api/v1/clusters, /api/v1/fleet, /metrics) run two at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the per-cluster reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next fleet read waits, and cut off if the client has not taken it within 20s. |
ingest¶
Snapshot pushes from in-cluster agents.
POST /api/v1/snapshots¶
Push an inventory snapshot. What the agent sends every time its cluster's inventory changes and
at least every --force-sync-every (1h). The body is JSON,
optionally gzipped (Content-Encoding: gzip), at most
serve --max-snapshot-bytes (20 MiB) on the wire and after
decompression.
The server stores the inventory as sent and evaluates it against
the cluster's next minor plus serve --targets, in one
transaction. A push whose inventory equals the latest stored one
(ignoring collectedAt and fields this server does not know) is a
duplicate: 200, nothing new stored. A cluster name is bound to the
first non-empty clusterId (kube-system namespace UID) pushed under
it; from then on a push under that name from another UID, or with
none, is a 409. A name registered without one is bound by the first
clusterId pushed afterwards, from whichever cluster sends it, and
that push continues the name's history: the server logs the adoption
and sends no became-ready for it. Two pushers that both send no
clusterId always share the name.
A per-cluster token may push only as its own cluster (403 before
anything is written). clusterName is an RFC 1123 subdomain of at
most 253 bytes, and the inventory's identifiers are what the
apiserver accepts for what they name: namespaces RFC 1123 labels,
object names at most 253 bytes without / or %, node and Helm
release names RFC 1123 subdomains, team labels label values. Its
other values are within the limits collectors keep to: strings at
most 16 KiB (a capability's reason 64 KiB, an object's field
manager 128 printable bytes), at most 100 objects per API usage
entry, no count, objectsOmitted or namespace count below zero in
an API usage entry (apiUsage, apiAuthorshipUnknown, a CRD's
usage, a Helm release's manifestApis) or a volume plugin entry,
each group/version/kind once per list, at most 200
unrecognized images and 32 capabilities. Anything else is 422,
naming the field and the rule, before anything is stored. The free
text a collector copies whole from the cluster is cut to the
limits, not refused: a capability's reason and skipped entries,
and an object's ignore (to its whole tokens) and ignoreReason
(the upgradescope.basit.engineer/ignore annotations). The snapshot keeps the
push as sent.
Each target's report is at most --max-snapshot-bytes: a push
whose report for one would be larger is 413, and nothing is
stored. No genuine inventory's report comes near it.
Memory is bounded by the body's structure as well as its size:
while the body is read, its JSON values are counted (each value 1,
each object 8) against a budget of 1,000,000 units, and buffered
bodies share a budget of twice the cap across requests. One push
is decoded at a time; a push that waits more than 10s for its
turn, or finds the body budget full, gets 503 with Retry-After.
So does a push whose notification baselines other writers (another
replica's push, the background re-evaluation) replaced three times
while it was evaluated; nothing is stored. The agent retries 408
and 503.
Auth: ingestToken (bearer).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Content-Encoding |
header | gzip | identity |
no | — |
Request body (application/json): PushRequest
| Status | Content type | Schema | Description |
|---|---|---|---|
| 200 | application/json |
PushDuplicate | A duplicate of the cluster's latest snapshot. |
| 202 | application/json |
PushAccepted | Stored and evaluated. |
| 401 | application/json |
Error | An error. |
| 403 | application/json |
Error | An error. |
| 400 | application/json |
Error | The body ended early or could not be read. |
| 408 | application/json |
Error | The body did not arrive within the server's read timeout (60s for serve). |
| 409 | application/json |
Error | An error. |
| 413 | application/json |
Error | Over the byte cap (on the wire or after decompression) or over the node budget, before anything is decoded; or, once decoded, a report for one of the targets would be over --max-snapshot-bytes, and nothing is stored. |
| 415 | application/json |
Error | An error. |
| 422 | application/json |
Error | Not judgeable, refused before anything is written: invalid JSON or gzip, a body that is not valid UTF-8, an envelope schemaVersion other than 1, no clusterName or one that is not an RFC 1123 subdomain of at most 253 bytes, a missing or null inventory, an inventory schemaVersion other than 1, a source other than cluster, a collectorSchema other than 1 (or none), a serverVersion that is not a Kubernetes 1.x version, an identifier that is not valid for what it names, or a value beyond the limits collectors keep to (see above). The message names the field and the rule. |
| 500 | application/json |
Error | An error. |
| 503 | application/json |
Error | The shared body budget is full, the push waited too long for its turn, or other writers kept replacing its notification baselines (nothing is stored); retry after Retry-After. |
clusters¶
Per-cluster state, reports, findings and history.
GET /api/v1/clusters¶
List clusters. Every cluster, with whether it is stale and the summary of its current evaluation at its default target (absent until one exists).
Auth: readToken (bearer).
| Status | Content type | Schema | Description |
|---|---|---|---|
| 200 | application/json |
array of ClusterSummary | The clusters, by id. |
| 401 | application/json |
Error | An error. |
| 500 | application/json |
Error | An error. |
| 503 | application/json |
Error | Reads of the whole fleet (/api/v1/clusters, /api/v1/fleet, /metrics) run two at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the per-cluster reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next fleet read waits, and cut off if the client has not taken it within 20s. |
GET /api/v1/clusters/{id}¶
Get one cluster. The cluster, the version and capabilities of its latest snapshot,
and the summaries of its current evaluations: the default target
and every applicable serve --targets minor.
Auth: readToken (bearer).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | integer (int64) | yes | The cluster's numeric id (id in the cluster list). A non-integer is a 400. |
| Status | Content type | Schema | Description |
|---|---|---|---|
| 200 | application/json |
ClusterDetail | The cluster. |
| 400 | application/json |
Error | An error. |
| 401 | application/json |
Error | An error. |
| 404 | application/json |
Error | An error. |
| 503 | application/json |
Error | Reads that load a cluster's snapshot run one at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the fleet reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next read waits, and cut off if the client has not taken it within 20s. |
GET /api/v1/clusters/{id}/report¶
Readiness report. The full report for one target: the stored evaluation of the latest
snapshot when there is one (source: stored), otherwise a what-if
evaluated now from the latest snapshot and not stored
(source: what-if), 413 when its report would be over
--max-snapshot-bytes. teams holds per-team scores.
Auth: readToken (bearer).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | integer (int64) | yes | The cluster's numeric id (id in the cluster list). A non-integer is a 400. |
target |
query | Target | no | Target minor. Default, the next minor above the cluster's version. |
| Status | Content type | Schema | Description |
|---|---|---|---|
| 200 | application/json |
ReportResponse | The report. |
| 400 | application/json |
Error | An error. |
| 401 | application/json |
Error | An error. |
| 404 | application/json |
Error | An error. |
| 413 | application/json |
Error | An error. |
| 422 | application/json |
Error | An error. |
| 503 | application/json |
Error | Reads that load a cluster's snapshot run one at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the fleet reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next read waits, and cut off if the client has not taken it within 20s. |
GET /api/v1/clusters/{id}/findings¶
Findings, filtered. The report's findings (same source as the report), exact-match filtered.
Auth: readToken (bearer).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | integer (int64) | yes | The cluster's numeric id (id in the cluster list). A non-integer is a 400. |
target |
query | Target | no | Target minor. Default, the next minor above the cluster's version. |
severity |
query | Severity | no | Only findings of this severity. An unknown value matches nothing. |
category |
query | Category | no | Only findings of this category. An unknown value matches nothing. |
| Status | Content type | Schema | Description |
|---|---|---|---|
| 200 | application/json |
FindingsResponse | The findings. |
| 400 | application/json |
Error | An error. |
| 401 | application/json |
Error | An error. |
| 404 | application/json |
Error | An error. |
| 413 | application/json |
Error | An error. |
| 422 | application/json |
Error | An error. |
| 503 | application/json |
Error | Reads that load a cluster's snapshot run one at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the fleet reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next read waits, and cut off if the client has not taken it within 20s. |
GET /api/v1/clusters/{id}/history¶
Score history. Stored evaluations of the target, oldest first, within the retention window.
Auth: readToken (bearer).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | integer (int64) | yes | The cluster's numeric id (id in the cluster list). A non-integer is a 400. |
target |
query | Target | no | Target minor. Default, the next minor above the cluster's version. |
limit |
query | integer | no | Most recent points to return, at most 1000. |
| Status | Content type | Schema | Description |
|---|---|---|---|
| 200 | application/json |
array of ScorePoint | The score history. |
| 400 | application/json |
Error | An error. |
| 401 | application/json |
Error | An error. |
| 404 | application/json |
Error | An error. |
| 422 | application/json |
Error | An error. |
| 503 | application/json |
Error | Reads that load a cluster's snapshot run one at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the fleet reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next read waits, and cut off if the client has not taken it within 20s. |
GET /api/v1/clusters/{id}/teams¶
Per-team scores. The score formula applied to each team's findings, from the same
report the report endpoint serves. Findings with no team are under
(unattributed), a name no label value can take and --team-map refuses, so a real team called unattributed is a separate row.
Auth: readToken (bearer).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | integer (int64) | yes | The cluster's numeric id (id in the cluster list). A non-integer is a 400. |
target |
query | Target | no | Target minor. Default, the next minor above the cluster's version. |
| Status | Content type | Schema | Description |
|---|---|---|---|
| 200 | application/json |
TeamsResponse | Team scores. |
| 400 | application/json |
Error | An error. |
| 401 | application/json |
Error | An error. |
| 404 | application/json |
Error | An error. |
| 413 | application/json |
Error | An error. |
| 422 | application/json |
Error | An error. |
| 503 | application/json |
Error | Reads that load a cluster's snapshot run one at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the fleet reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next read waits, and cut off if the client has not taken it within 20s. |
fleet¶
Rollups across every cluster.
GET /api/v1/fleet¶
Fleet score matrix. One row per cluster, one cell per target, from current stored
evaluations only (nothing is recomputed). A cell is null when the
cluster has no current evaluation for that target, or already runs
it (then the target is also in the row's notApplicable).
Auth: readToken (bearer).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
targets |
query | string | no | Comma-separated minors, at most 16 distinct (more is 422). Default: every cluster's next minor plus the serve --targets minors some cluster does not run yet, at most 16 of them: those with the most clusters to fill them (the older minor on a tie), the rest counted in targetsOmitted. |
| Status | Content type | Schema | Description |
|---|---|---|---|
| 200 | application/json |
FleetResponse | The matrix. |
| 401 | application/json |
Error | An error. |
| 422 | application/json |
Error | An error. |
| 500 | application/json |
Error | An error. |
| 503 | application/json |
Error | Reads of the whole fleet (/api/v1/clusters, /api/v1/fleet, /metrics) run two at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the per-cluster reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next fleet read waits, and cut off if the client has not taken it within 20s. |
GET /api/v1/fleet/teams¶
Per-team rollup across the fleet. For one target, each team's worst score, total blockers and the
clusters it has findings in. Each cluster contributes its stored
evaluation, else a what-if from its latest snapshot (evaluated
says which). Clusters left out are named in missing and, with the
reason, in excluded: no-snapshot (the agent has not pushed
one), too-large (the what-if report would be over
--max-snapshot-bytes) or unreadable (the stored inventory does
not decode). Clusters that already run the target are
notApplicable.
A what-if's evaluatedAt is when it was computed: each cluster's
contribution is kept for its snapshot (or its stored evaluation),
the target, the server's knowledge base and team map and the UTC
day, so a later request for the target computes only what changed.
Each contribution is computed in the per-cluster read slot, one
cluster at a time, so other per-cluster reads wait at most for one
cluster's. A rollup still computing after 20s answers 503 with
Retry-After; what it computed is kept, so the retry goes on from
there.
A team's verdict is the worst of its verdicts in the clusters
evaluated: blocked over unknown over ready. Its score and
blockers count only its own findings, so a team with a clean score
can still be blocked by a blocker no team owns, or unknown because
a required check did not run.
Auth: readToken (bearer).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
target |
query | Target | yes | — |
| Status | Content type | Schema | Description |
|---|---|---|---|
| 200 | application/json |
FleetTeamsResponse | The rollup. |
| 401 | application/json |
Error | An error. |
| 422 | application/json |
Error | An error. |
| 500 | application/json |
Error | An error. |
| 503 | application/json |
Error | Reads that load a cluster's snapshot run one at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the fleet reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next read waits, and cut off if the client has not taken it within 20s. |
gate¶
The CI gate for rendered manifests.
POST /api/v1/gate¶
Gate rendered manifests. Evaluates a YAML (or JSON) manifest stream against a target and
stores nothing. Without cluster, the manifests are judged alone,
like scan --files. With cluster, they are judged inside that
cluster's latest stored inventory (its version, nodes, add-ons,
CRDs and team labels), with the manifests' objects, add-ons and
CRDs merged into it, and the verdict counts only what the
manifests introduce (findings tagged source: manifest); findings
the cluster already has are tagged source: cluster. A removed or
deprecated API a posted object uses, an add-on the manifests
deploy, an in-tree volume plugin a posted pod template, Pod or
PersistentVolume names (also when the cluster already has pods or
volumes on it), and a custom resource they write at a version the CRDs
do not serve are always the manifests'.
With a team-scoped credential, cluster is judged as the scope's
share of it only (its teams' namespaces, and what describes the
cluster as a whole): the findings, verdict, status, score and
clusterVerdict are the share's, so a PR that breaks only
another team's workloads passes. Repositories that can break
shared or other teams' resources gate with a fleet-wide
credential. See
https://abd-ulbasit.github.io/upgradescope/operations/auth/#the-gate-with-a-team-scoped-token
fail-on works like scan --fail-on (default blocker): when an
introduced finding reaches it, or the verdict is unknown, the
status is 422 with the full body, so curl --fail-with-body fails
a CI step and keeps the report. X-Upgradescope-Verdict always
carries the verdict.
allow-incomplete=true is scan --allow-incomplete: the gate
decides on findings alone. An unknown verdict (a required check
did not run, such as a target past the knowledge base's
horizon) with no finding at the fail-on threshold then answers
200, and a blocker still answers 422. The verdict header, the
body's verdict and its required notAssessed gaps still say
unknown. A target that is not an upgrade of the cluster (the
target gap) is a mistake, not a coverage limit, and still fails.
Only true and false are values, given once.
format picks the body: the JSON gate response (default), SARIF,
JUnit XML whose test outcomes follow fail-on, or a GitLab Code
Quality report. SARIF, JUnit and Code Quality hold only the
findings the manifests introduce; the status and verdict header
are the same in every format.
Suppression works as in scan: the upgradescope.basit.engineer/ignore and
upgradescope.basit.engineer/ignore-reason annotations of posted (and, with
cluster, stored) objects, and the ignore rules of a
.upgradescope.yaml sent in config. Suppressed findings count
toward neither the verdict nor fail-on; the JSON answer lists
them in suppressed with suppressedCount, SARIF with no result
for them (GitHub code scanning ignores a result's suppressions and
would open an alert; one run-level note counts them instead), JUnit
as skipped test cases, and Code Quality leaves them out. An expired rule
no longer applies; it and an annotation without a reason are named in
warnings. There is
no baseline input: with cluster, the cluster's own findings are
the baseline (source: cluster), and manifest-level debt is
accepted with scan --baseline or ignore rules.
Limits: serve --max-gate-bytes (10 MiB) per body, and for the
stream with its YAML aliases expanded; 4 MiB per document with its
aliases expanded, 20,000 documents, and a node budget of 400,000
units (each YAML node 1, each sequence entry 4, each alias what it
names), counted from the raw bytes before anything is decoded;
over any of them is 413. Buffered bodies share a budget of three
times the cap. Measuring aliases, decoding and evaluating run one
request at a time; a request that waits more than 30s for its
turn, or finds the shared body budget full, gets 503 with
Retry-After. A stream with a UTF-16 byte order mark is 422: send
UTF-8.
The answer may take at most --max-gate-bytes too: an upper bound
on it, in the asked format (every string at its dearest escape),
is computed after the evaluation and before anything is encoded,
and over the cap is 413, saying to split the stream. With
cluster, the cluster's own findings and the objects they list
count toward it. The answer is built before it is sent, with its
Content-Length. Answers waiting for their clients share a budget
of twice --max-snapshot-bytes with the per-cluster and fleet
reads; an answer that does not fit what is left of it is 503 with
Retry-After. (One larger than that whole budget, possible only
when --max-gate-bytes is over twice --max-snapshot-bytes, is
sent while the next request waits, and cut off if the client has
not taken it within 20s.)
Auth: readToken (bearer).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
target |
query | Target | yes | — |
cluster |
query | string | no | A cluster name or id whose stored context the manifests are judged in. |
format |
query | json | sarif | junit | gitlab-codequality |
no | — |
fail-on |
query | blocker | warning | never |
no | — |
allow-incomplete |
query | true | false |
no | true gates on findings alone: an unknown verdict with no finding at the fail-on threshold is 200, not 422. See the operation description. |
path |
query | string | no | The repository-relative file the stream was rendered to (deploy/rendered.yaml): clean, relative, valid UTF-8 without control characters, at most 512 bytes (every listed object carries it); otherwise 422. Introduced findings are then located in it in SARIF and Code Quality, so code scanning or the merge request widget shows them on the change. |
config |
query | string | no | A .upgradescope.yaml (its text, URL-encoded): ignore rules applied as scan --config applies them, file globs matched against path. An invalid config, one over 32 KiB, or config given more than once is a 422. The request line, and so this parameter, also counts toward the server's 64 KiB request-header limit: URL-encoding expands YAML, so a config under 32 KiB can exceed it, and the server then answers 431 with a plain-text body. A reverse proxy in front of the server may refuse a long request line sooner (ingress-nginx's default answers 414 above 8 KiB). |
Request body (application/x-yaml): string
Request body (application/json): string
| Status | Content type | Schema | Description |
|---|---|---|---|
| 200 | application/json |
GateResponse or CodeQuality | The gate passed. |
| 200 | application/sarif+json |
SARIF | The gate passed. |
| 200 | application/xml |
JUnit | The gate passed. |
| 422 | application/json |
GateResponse or CodeQuality or Error | The gate failed (same body as 200), or the request is invalid (no target, bad format, fail-on, allow-incomplete, path or config, an invalid document separator, a UTF-16 stream, or an undecodable manifest stream: an Error body). |
| 422 | application/sarif+json |
SARIF | The gate failed (same body as 200), or the request is invalid (no target, bad format, fail-on, allow-incomplete, path or config, an invalid document separator, a UTF-16 stream, or an undecodable manifest stream: an Error body). |
| 422 | application/xml |
JUnit | The gate failed (same body as 200), or the request is invalid (no target, bad format, fail-on, allow-incomplete, path or config, an invalid document separator, a UTF-16 stream, or an undecodable manifest stream: an Error body). |
| 400 | application/json |
Error | The target is not a Kubernetes 1.x minor at or above the oldest the knowledge base covers (1.16): 1.3, which is what YAML makes of an unquoted target: 1.30, would judge nothing and read ready, so it is refused, saying to quote the version. Not a 422, which is the gate's own verdict. Also: the body ended early or could not be read. |
| 401 | application/json |
Error | An error. |
| 404 | application/json |
Error | An error. |
| 408 | application/json |
Error | The body did not arrive within the server's read timeout (60s for serve). |
| 413 | application/json |
Error | Over the byte cap (for the body, or for the stream with its aliases expanded), the per-document size (aliases expanded), the document count, the node budget, or the answer's bound, or, with ?cluster=, a report over --max-snapshot-bytes; the message says which, and to split the stream. |
| 415 | application/json |
Error | An error. |
| 503 | application/json |
Error | The shared body budget is full, the request waited more than 30s for its turn, or the answers waiting for their clients leave no room for this one; retry after Retry-After. |
exports¶
Auditor exports of a stored evaluation.
GET /api/v1/clusters/{id}/export¶
Auditor export. The stored evaluation of the latest snapshot as CSV (one summary
row, one row per finding, one suppressed row per finding or
objects of one that the objects' ignore annotations accepted, with
its severity, source, reason and objects in the detail, and one per
capability not assessed) or a self-contained HTML report with the
score history and a suppressed section. Never a what-if:
404 when no stored evaluation exists for the target, or when the
cluster already runs it. An export is at most
--max-snapshot-bytes, as the report it renders is (HTML writes
' " & as five bytes and < > as four): 413 above, saying to
read the JSON report.
Auth: readToken (bearer).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | integer (int64) | yes | The cluster's numeric id (id in the cluster list). A non-integer is a 400. |
target |
query | Target | no | Target minor. Default, the next minor above the cluster's version. |
format |
query | csv | html |
yes | — |
| Status | Content type | Schema | Description |
|---|---|---|---|
| 200 | text/csv |
string | The export. |
| 200 | text/html |
string | The export. |
| 400 | application/json |
Error | An error. |
| 401 | application/json |
Error | An error. |
| 404 | application/json |
Error | An error. |
| 413 | application/json |
Error | An error. |
| 422 | application/json |
Error | An error. |
| 503 | application/json |
Error | Reads that load a cluster's snapshot run one at a time, and this one waited more than 30s for its turn, or the responses waiting for their clients leave no room for this one (they share a budget of twice --max-snapshot-bytes with the fleet reads and /gate); retry after Retry-After. A response is sent with its Content-Length; one larger than that whole budget is sent while the next read waits, and cut off if the client has not taken it within 20s. |
registry¶
The add-on registry this binary judges against.
GET /api/v1/registry¶
The add-on registry. The add-on EOL and compatibility dataset compiled into this binary, sorted by id.
Auth: readToken (bearer).
| Status | Content type | Schema | Description |
|---|---|---|---|
| 200 | application/json |
Registry | The registry. |
| 401 | application/json |
Error | An error. |
admin¶
Cluster administration (admin token).
DELETE /api/v1/clusters/{id}¶
Delete a cluster. Removes the cluster with its snapshots, evaluations, queued notifications and the per-cluster ingest tokens minted for its name. An agent still pushing under the name registers it again.
Auth: adminToken (bearer).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | integer (int64) | yes | The cluster's numeric id (id in the cluster list). A non-integer is a 400. |
| Status | Content type | Schema | Description |
|---|---|---|---|
| 204 | — | — | Deleted. |
| 400 | application/json |
Error | An error. |
| 401 | application/json |
Error | An error. |
| 403 | application/json |
Error | No admin token configured, or another token presented. |
| 404 | application/json |
Error | An error. |
PATCH /api/v1/clusters/{id}¶
Rename a cluster. Moves the cluster's history and per-cluster ingest tokens to the new
name. Change the agent's --cluster-name too: until then its pushes
under the old name are refused (per-cluster token) or register the
old name again (shared token).
Auth: adminToken (bearer).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | integer (int64) | yes | The cluster's numeric id (id in the cluster list). A non-integer is a 400. |
Request body (application/json): RenameRequest
| Status | Content type | Schema | Description |
|---|---|---|---|
| 200 | application/json |
Cluster | The renamed cluster. |
| 400 | application/json |
Error | An error. |
| 401 | application/json |
Error | An error. |
| 403 | application/json |
Error | An error. |
| 404 | application/json |
Error | An error. |
| 409 | application/json |
Error | An error. |
| 413 | application/json |
Error | An error. |
| 422 | application/json |
Error | An error. |
Schemas¶
Error¶
Every error a handler writes.
| Field | Type | Required | Description |
|---|---|---|---|
error |
string | yes | — |
Status¶
| Field | Type | Required | Description |
|---|---|---|---|
status |
ok |
yes | — |
Target¶
A Kubernetes minor, MAJOR.MINOR, from 1.16 (the oldest the
knowledge base covers; an older one is refused, with the gate's
400 and the other endpoints' 422). Quote it in YAML: an unquoted
1.30 is the number 1.3.
Type: string, pattern ^[0-9]+\.[0-9]+$.
Verdict¶
blocked: at least one blocker. unknown: no blocker found, but a
required check was not assessed, so one may have been missed.
ready: neither.
Type: ready, blocked, unknown.
Severity¶
Type: blocker, warning, info.
Category¶
One of removed-api, deprecated-api, deprecated-api-in-use,
eol-addon, eol-approaching, version-skew, chart-incompat,
kb-stale, addon-no-data, unknown-api, crd-version,
support-lifecycle, volume-plugin. A release may add
categories under /api/v1, so this is not an enum: treat one you do
not know by its severity.
Type: string.
Cluster¶
| Field | Type | Required | Description |
|---|---|---|---|
id |
integer (int64) | yes | — |
name |
string | yes | The agent's --cluster-name. |
clusterUid |
string | yes | The kube-system namespace UID the name is bound to. |
firstSeen |
string (date-time) | yes | — |
lastSeen |
string (date-time) | yes | The agent's last push, duplicates included. |
ClusterFields¶
| Field | Type | Required | Description |
|---|---|---|---|
id |
integer (int64) | yes | — |
name |
string | yes | The agent's --cluster-name. |
clusterUid |
string | yes | The kube-system namespace UID the name is bound to. |
firstSeen |
string (date-time) | yes | — |
lastSeen |
string (date-time) | yes | The agent's last push, duplicates included. |
CapabilityGap¶
Something the evaluation could not assess.
| Field | Type | Required | Description |
|---|---|---|---|
capability |
string | yes | A collector capability (api-usage, deprecated-calls, helm, addons, versions, crds, volumes), kb-coverage, or target (the target is not an upgrade of the cluster). |
reason |
string | yes | — |
partial |
boolean | no | The capability ran but did not read everything. |
skipped |
array of string | no | What a partial capability did not read. |
required |
boolean | no | The gap makes the verdict unknown. |
SummaryGap¶
A report's gap as an evaluation summary carries it, bounded: its
capability cut to 64 bytes, its reason to 256 and at most 3 of what
it skipped, each cut to 128 bytes (a cut one ends in "…"), with
skippedOmitted counting the rest. The report lists every gap
whole.
| Field | Type | Required | Description |
|---|---|---|---|
capability |
string | yes | A collector capability (api-usage, deprecated-calls, helm, addons, versions, crds, volumes), kb-coverage, or target (the target is not an upgrade of the cluster). |
reason |
string | yes | — |
partial |
boolean | no | The capability ran but did not read everything. |
skipped |
array of string | no | What a partial capability did not read. |
required |
boolean | no | The gap makes the verdict unknown. |
skippedOmitted |
integer | no | Skipped entries not listed. |
CapabilityStatus¶
| Field | Type | Required | Description |
|---|---|---|---|
available |
boolean | yes | — |
reason |
string | no | — |
partial |
boolean | no | — |
skipped |
array of string | no | — |
EvalSummary¶
| Field | Type | Required | Description |
|---|---|---|---|
target |
Target | yes | — |
score |
integer | yes | — |
ready |
boolean | yes | verdict == ready (kept for v0.1 clients). |
verdict |
Verdict | yes | — |
blockers |
integer | yes | — |
warnings |
integer | yes | — |
kbVersion |
string | yes | — |
evaluatedAt |
string (date-time) | yes | When the result was last confirmed. |
snapshotId |
integer (int64) | yes | — |
outdated |
boolean | no | Evaluated before today (UTC), or under another knowledge base or team map; the next pass replaces it. |
notAssessed |
array of SummaryGap | no | The required gaps first, then the rest. A cluster's detail lists every gap; /clusters lists at most 1 KiB of them (encoded) and counts the rest in notAssessedOmitted. |
notAssessedOmitted |
integer | no | Gaps not listed (only in /clusters). |
ClusterSummary¶
| Field | Type | Required | Description |
|---|---|---|---|
id |
integer (int64) | yes | — |
name |
string | yes | The agent's --cluster-name. |
clusterUid |
string | yes | The kube-system namespace UID the name is bound to. |
firstSeen |
string (date-time) | yes | — |
lastSeen |
string (date-time) | yes | The agent's last push, duplicates included. |
stale |
boolean | yes | No push within serve --stale-after. |
latest |
EvalSummary | no | — |
ClusterDetail¶
| Field | Type | Required | Description |
|---|---|---|---|
id |
integer (int64) | yes | — |
name |
string | yes | The agent's --cluster-name. |
clusterUid |
string | yes | The kube-system namespace UID the name is bound to. |
firstSeen |
string (date-time) | yes | — |
lastSeen |
string (date-time) | yes | The agent's last push, duplicates included. |
stale |
boolean | yes | — |
serverVersion |
string | no | The version the latest snapshot is judged at. |
capabilities |
map of CapabilityStatus | no | — |
evaluations |
array of EvalSummary | yes | — |
ObjectRef¶
| Field | Type | Required | Description |
|---|---|---|---|
namespace |
string | no | — |
name |
string | no | — |
file |
string | no | Manifest path, relative to the scanned root (manifests only). |
line |
integer | no | 1-based line of the object's apiVersion key (manifests only). |
renderedFrom |
string | no | The Helm template the object was rendered from. |
manager |
string | no | The field manager that wrote a live object through the flagged API, or kubectl last-applied. |
ignore |
string | no | — |
ignoreReason |
string | no | — |
Finding¶
| Field | Type | Required | Description |
|---|---|---|---|
category |
Category | yes | — |
severity |
Severity | yes | — |
key |
string | no | Stable, count-free identity: category/discriminator. |
title |
string | yes | — |
detail |
string | yes | — |
teams |
array of string | no | — |
namespaces |
array of string | no | Affected namespaces, sorted, at most 100. |
namespacesOmitted |
integer | no | Affected namespaces not listed. |
unnamespaced |
boolean | no | An add-on finding that also covers an install in no named namespace: a manifest object without metadata.namespace, or a cluster-scoped IngressClass. A namespace-scoped ignore rule cannot be shown to match it, so it does not take the finding. |
remediation |
string | no | — |
citations |
array of string (uri) | no | — |
objects |
array of ObjectRef | no | — |
objectsOmitted |
integer | no | Affected objects not listed. |
baselineState |
new | unchanged |
no | — |
callers |
array of Caller | no | API usage findings: the apiserver_requested_deprecated_apis rows folded into this finding, in row order, each the deprecated-api-in-use finding it is on its own. Suppressing every object of the finding by annotation or an object-scoped rule does not suppress them: they then stand as findings of their own. A team-scoped read omits them from a finding it cuts to its teams. |
FindingFields¶
| Field | Type | Required | Description |
|---|---|---|---|
category |
Category | yes | — |
severity |
Severity | yes | — |
key |
string | no | Stable, count-free identity: category/discriminator. |
title |
string | yes | — |
detail |
string | yes | — |
teams |
array of string | no | — |
namespaces |
array of string | no | Affected namespaces, sorted, at most 100. |
namespacesOmitted |
integer | no | Affected namespaces not listed. |
unnamespaced |
boolean | no | An add-on finding that also covers an install in no named namespace: a manifest object without metadata.namespace, or a cluster-scoped IngressClass. A namespace-scoped ignore rule cannot be shown to match it, so it does not take the finding. |
remediation |
string | no | — |
citations |
array of string (uri) | no | — |
objects |
array of ObjectRef | no | — |
objectsOmitted |
integer | no | Affected objects not listed. |
baselineState |
new | unchanged |
no | — |
callers |
array of Caller | no | API usage findings: the apiserver_requested_deprecated_apis rows folded into this finding, in row order, each the deprecated-api-in-use finding it is on its own. Suppressing every object of the finding by annotation or an object-scoped rule does not suppress them: they then stand as findings of their own. A team-scoped read omits them from a finding it cuts to its teams. |
Caller¶
| Field | Type | Required | Description |
|---|---|---|---|
group |
string | no | Absent for the core group. |
version |
string | yes | — |
resource |
string | yes | — |
subresource |
string | no | — |
key |
string | yes | The key of the caller's own deprecated-api-in-use finding. |
severity |
Severity | yes | — |
title |
string | yes | — |
detail |
string | yes | — |
SuppressedFinding¶
| Field | Type | Required | Description |
|---|---|---|---|
category |
Category | yes | — |
severity |
Severity | yes | — |
key |
string | no | Stable, count-free identity: category/discriminator. |
title |
string | yes | — |
detail |
string | yes | — |
teams |
array of string | no | — |
namespaces |
array of string | no | Affected namespaces, sorted, at most 100. |
namespacesOmitted |
integer | no | Affected namespaces not listed. |
unnamespaced |
boolean | no | An add-on finding that also covers an install in no named namespace: a manifest object without metadata.namespace, or a cluster-scoped IngressClass. A namespace-scoped ignore rule cannot be shown to match it, so it does not take the finding. |
remediation |
string | no | — |
citations |
array of string (uri) | no | — |
objects |
array of ObjectRef | no | — |
objectsOmitted |
integer | no | Affected objects not listed. |
baselineState |
new | unchanged |
no | — |
callers |
array of Caller | no | API usage findings: the apiserver_requested_deprecated_apis rows folded into this finding, in row order, each the deprecated-api-in-use finding it is on its own. Suppressing every object of the finding by annotation or an object-scoped rule does not suppress them: they then stand as findings of their own. A team-scoped read omits them from a finding it cuts to its teams. |
reason |
string | yes | Why the finding is accepted. |
source |
string | yes | What suppressed it: the config file, annotation, or spec.ignore. |
expires |
string | no | YYYY-MM-DD, as the rule gave it. |
TeamScore¶
| Field | Type | Required | Description |
|---|---|---|---|
score |
integer | yes | — |
ready |
boolean | yes | verdict == ready. |
verdict |
ready | blocked | unknown |
no | The team's readiness, by the report's rules over what can concern it: blocked by a blocker of its own or by an unattributed one (which cannot be ruled out as the team's); otherwise unknown when the report has a required not-assessed gap, which may hide any team's blocker; otherwise ready. Another team's blocker does not lower it; score, blockers and warnings count only the team's own findings. |
blockers |
integer | yes | — |
warnings |
integer | yes | — |
TeamScores¶
Team name to score; findings with no team are under (unattributed), a name no label value can take and --team-map refuses, so a real team called unattributed is a separate row.
Type: map of TeamScore.
ReportEnvelope¶
The first two fields of every report-shaped response (a cluster's report and the gate's JSON answer), as of scan --output json.
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion |
1 |
yes | The version of the report's shape. Within it fields are only added, never renamed, removed, retyped or given a new meaning; a breaking change bumps it (see the compatibility policy). |
toolVersion |
string | yes | The server build that wrote the response, also for a stored evaluation. Informational, no compatibility meaning. |
ReportFields¶
The engine's report, as scan --output json writes it.
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion |
1 |
yes | The version of the report's shape. Within it fields are only added, never renamed, removed, retyped or given a new meaning; a breaking change bumps it (see the compatibility policy). |
toolVersion |
string | yes | The server build that wrote the response, also for a stored evaluation. Informational, no compatibility meaning. |
clusterId |
string | yes | — |
target |
Target | yes | — |
serverVersion |
string | no | The kube-apiserver gitVersion the target was judged against; absent for manifests. |
kubeContext |
string | no | scan of a live cluster only: the kubeconfig context it read. The server never sets it. |
apiServer |
string | no | scan of a live cluster only: the API server it read, as scheme, host and port, without credentials, path or query. The server never sets it. |
kbVersion |
string | yes | — |
score |
integer | yes | max(0, 100 - min(75, 25 x blockers) - min(20, 5 x warnings)) |
ready |
boolean | yes | verdict == ready. |
verdict |
Verdict | yes | — |
notAssessed |
array of CapabilityGap | no | — |
suppressed |
array of SuppressedFinding | no | What ignore rules or object annotations accepted, out of findings, the score and the verdict. A stored or read-time report (GET /report, the fleet, history, notifications, exports) applies the objects' upgradescope.basit.engineer/ignore annotations (source annotation), never the agent's spec.ignore; /gate applies them and its config rules. |
unrecognizedImages |
array of string | no | Image repositories (host/path) no add-on image matcher recognised: a detection gap, not a finding (neither score nor verdict counts them). Sorted, deduplicated, at most 200. |
unrecognizedImagesOmitted |
integer | no | Unrecognized image repositories beyond the 200 listed. |
addOnEvidenceAgeSeconds |
integer | no | How old, in seconds (at least 1), the pod evidence the add-ons were detected from was when the agent reused its last full pod pass instead of listing every pod again (--pod-pass-every, --pod-pass-max-age): an add-on installed or upgraded since is reported as it was then, and a pass of --pod-pass-max-age or more is not reused (the age is that of the collection, so a viewer can see evidence about one interval older). Absent when every pod was read for this report, and from collectors that predate the field. The verdict does not depend on it. |
support |
SupportStatus | no | — |
findings |
array of Finding | yes | — |
ReportBase¶
The report's fields other than its findings.
| Field | Type | Required | Description |
|---|---|---|---|
clusterId |
string | yes | — |
target |
Target | yes | — |
serverVersion |
string | no | The kube-apiserver gitVersion the target was judged against; absent for manifests. |
kubeContext |
string | no | scan of a live cluster only: the kubeconfig context it read. The server never sets it. |
apiServer |
string | no | scan of a live cluster only: the API server it read, as scheme, host and port, without credentials, path or query. The server never sets it. |
kbVersion |
string | yes | — |
score |
integer | yes | max(0, 100 - min(75, 25 x blockers) - min(20, 5 x warnings)) |
ready |
boolean | yes | verdict == ready. |
verdict |
Verdict | yes | — |
notAssessed |
array of CapabilityGap | no | — |
suppressed |
array of SuppressedFinding | no | What ignore rules or object annotations accepted, out of findings, the score and the verdict. A stored or read-time report (GET /report, the fleet, history, notifications, exports) applies the objects' upgradescope.basit.engineer/ignore annotations (source annotation), never the agent's spec.ignore; /gate applies them and its config rules. |
unrecognizedImages |
array of string | no | Image repositories (host/path) no add-on image matcher recognised: a detection gap, not a finding (neither score nor verdict counts them). Sorted, deduplicated, at most 200. |
unrecognizedImagesOmitted |
integer | no | Unrecognized image repositories beyond the 200 listed. |
addOnEvidenceAgeSeconds |
integer | no | How old, in seconds (at least 1), the pod evidence the add-ons were detected from was when the agent reused its last full pod pass instead of listing every pod again (--pod-pass-every, --pod-pass-max-age): an add-on installed or upgraded since is reported as it was then, and a pass of --pod-pass-max-age or more is not reused (the age is that of the collection, so a viewer can see evidence about one interval older). Absent when every pod was read for this report, and from collectors that predate the field. The verdict does not depend on it. |
support |
SupportStatus | no | — |
SupportStatus¶
A cluster on EKS, GKE or AKS whose Kubernetes minor the knowledge base
dates: where that minor stands in the provider's support calendar,
whether or not it is a finding (see the support-lifecycle category).
Absent for other clusters and when the provider's dates for the minor
are not known. The same for every target.
| Field | Type | Required | Description |
|---|---|---|---|
provider |
eks | gke | aks |
yes | The managed Kubernetes service the collector inferred from the server version suffix and the nodes' managed node pool labels. |
minor |
string | yes | The control plane's Kubernetes minor. |
phase |
standard | ending | extended | ended |
yes | standard: standard support, not ending within 90 days. ending: standard support ends within 90 days. extended: past standard support, before the provider's extended window ends (EKS bills it by default; on GKE and AKS it is opt-in, see extendedSupportCondition, and the cluster may not be enrolled). ended: past the end of extended support, or past standard support for a minor with no extended support. |
extendedSupportFrom |
string (date) | yes | The day (UTC) standard support ends and extended support begins. EKS bills extended support from the start of this day. |
extendedSupportEnds |
string (date) | no | The day extended support ends; absent when the provider offered none for the minor. |
annualCostDelta |
string | no | What extended support adds per cluster per year at the provider's published list price, (extended - standard) x 8760 cluster-hours, as a decimal string in currency. A list price as of priceAsOf, not the customer's bill: prices change and vary by contract. Present only when the knowledge base cites the provider's price and the minor can still be in extended support (not in phase ended); never inferred. |
currency |
USD |
no | The currency of annualCostDelta. |
priceAsOf |
string (date) | no | The day the list price behind annualCostDelta was read from the provider's pricing page. |
annualCostNote |
string | no | The provider's caveat on whom annualCostDelta is charged to (GKE: only clusters on the Extended release channel). Present with annualCostDelta when the knowledge base records one; show it beside the figure. |
extendedSupportCondition |
string | no | Where the provider's extended support is opt-in (GKE, AKS): the configuration under which it applies, as a clause completing "only if ..." ("Long Term Support is enabled"). The scanner cannot see the cluster's configuration, so phases ending and extended describe the provider's window, not a confirmed enrolment. Absent for EKS and when the provider offered no extended support for the minor. |
ReportMeta¶
| Field | Type | Required | Description |
|---|---|---|---|
evaluatedAt |
string (date-time) | yes | — |
snapshotId |
integer (int64) | yes | — |
source |
stored | what-if |
yes | — |
serverVersion |
string | no | — |
notApplicable |
boolean | no | The cluster already runs the target. |
outdated |
boolean | no | — |
ReportResponse¶
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion |
1 |
yes | The version of the report's shape. Within it fields are only added, never renamed, removed, retyped or given a new meaning; a breaking change bumps it (see the compatibility policy). |
toolVersion |
string | yes | The server build that wrote the response, also for a stored evaluation. Informational, no compatibility meaning. |
clusterId |
string | yes | — |
target |
Target | yes | — |
serverVersion |
string | no | — |
kubeContext |
string | no | scan of a live cluster only: the kubeconfig context it read. The server never sets it. |
apiServer |
string | no | scan of a live cluster only: the API server it read, as scheme, host and port, without credentials, path or query. The server never sets it. |
kbVersion |
string | yes | — |
score |
integer | yes | max(0, 100 - min(75, 25 x blockers) - min(20, 5 x warnings)) |
ready |
boolean | yes | verdict == ready. |
verdict |
Verdict | yes | — |
notAssessed |
array of CapabilityGap | no | — |
suppressed |
array of SuppressedFinding | no | What ignore rules or object annotations accepted, out of findings, the score and the verdict. A stored or read-time report (GET /report, the fleet, history, notifications, exports) applies the objects' upgradescope.basit.engineer/ignore annotations (source annotation), never the agent's spec.ignore; /gate applies them and its config rules. |
unrecognizedImages |
array of string | no | Image repositories (host/path) no add-on image matcher recognised: a detection gap, not a finding (neither score nor verdict counts them). Sorted, deduplicated, at most 200. |
unrecognizedImagesOmitted |
integer | no | Unrecognized image repositories beyond the 200 listed. |
addOnEvidenceAgeSeconds |
integer | no | How old, in seconds (at least 1), the pod evidence the add-ons were detected from was when the agent reused its last full pod pass instead of listing every pod again (--pod-pass-every, --pod-pass-max-age): an add-on installed or upgraded since is reported as it was then, and a pass of --pod-pass-max-age or more is not reused (the age is that of the collection, so a viewer can see evidence about one interval older). Absent when every pod was read for this report, and from collectors that predate the field. The verdict does not depend on it. |
support |
SupportStatus | no | — |
findings |
array of Finding | yes | — |
evaluatedAt |
string (date-time) | yes | — |
snapshotId |
integer (int64) | yes | — |
source |
stored | what-if |
yes | — |
notApplicable |
boolean | no | The cluster already runs the target. |
outdated |
boolean | no | — |
teams |
TeamScores | no | — |
FindingsResponse¶
| Field | Type | Required | Description |
|---|---|---|---|
target |
Target | yes | — |
findings |
array of Finding | yes | — |
evaluatedAt |
string (date-time) | yes | — |
snapshotId |
integer (int64) | yes | — |
source |
stored | what-if |
yes | — |
serverVersion |
string | no | — |
notApplicable |
boolean | no | The cluster already runs the target. |
outdated |
boolean | no | — |
TeamsResponse¶
| Field | Type | Required | Description |
|---|---|---|---|
target |
Target | yes | — |
teams |
TeamScores | yes | — |
evaluatedAt |
string (date-time) | yes | — |
snapshotId |
integer (int64) | yes | — |
source |
stored | what-if |
yes | — |
serverVersion |
string | no | — |
notApplicable |
boolean | no | The cluster already runs the target. |
outdated |
boolean | no | — |
ScorePoint¶
| Field | Type | Required | Description |
|---|---|---|---|
at |
string (date-time) | yes | — |
score |
integer | yes | — |
ready |
boolean | yes | — |
verdict |
Verdict | no | The evaluation's verdict. A score can't tell unknown from ready (an evaluation whose required check did not run can score 100), so draw the trend by this. Absent from servers that predate it. |
FleetCell¶
| Field | Type | Required | Description |
|---|---|---|---|
score |
integer | yes | — |
ready |
boolean | yes | — |
verdict |
Verdict | yes | — |
blockers |
integer | yes | — |
evaluatedAt |
string (date-time) | yes | — |
snapshotId |
integer (int64) | yes | — |
source |
stored |
yes | — |
outdated |
boolean | no | — |
notAssessed |
array of SummaryGap | no | The required gaps first, then the rest, at most 1 KiB of them (encoded). |
notAssessedOmitted |
integer | no | Gaps not listed. |
FleetRow¶
| Field | Type | Required | Description |
|---|---|---|---|
clusterId |
integer (int64) | yes | — |
name |
string | yes | — |
lastSeen |
string (date-time) | yes | — |
stale |
boolean | yes | — |
serverVersion |
string | no | — |
cells |
map of FleetCell or null | yes | Target to cell; null when there is no current evaluation or the target is not applicable. |
notApplicable |
array of Target | no | — |
FleetResponse¶
| Field | Type | Required | Description |
|---|---|---|---|
targets |
array of Target | yes | — |
targetsOmitted |
integer | no | Default columns left out past the 16 (absent with ?targets= and when none are); ask for them with ?targets=. |
clusters |
array of FleetRow | yes | — |
FleetTeam¶
| Field | Type | Required | Description |
|---|---|---|---|
worstScore |
integer | yes | — |
blockers |
integer | yes | — |
verdict |
Verdict | yes | The worst of the team's verdicts across the clusters, blocked over unknown over ready. Never read the score without it. |
clusters |
array of string | yes | — |
FleetExcluded¶
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | — |
clusterId |
integer (int64) | yes | — |
reason |
no-snapshot | too-large | unreadable |
yes | no-snapshot: the agent has not pushed one. too-large: the cluster has snapshots, but its report for the target would be over --max-snapshot-bytes. unreadable: its stored inventory does not decode (or the cluster went away mid-request). |
FleetTeamsSource¶
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | — |
clusterId |
integer (int64) | yes | — |
source |
stored | what-if |
yes | — |
evaluatedAt |
string (date-time) | yes | — |
snapshotId |
integer (int64) | yes | — |
FleetTeamsResponse¶
| Field | Type | Required | Description |
|---|---|---|---|
target |
Target | yes | — |
teams |
map of FleetTeam | yes | — |
evaluated |
array of FleetTeamsSource | yes | — |
missing |
array of string | yes | The names of every cluster left out of the rollup, whatever the reason; excluded says why. |
excluded |
array of FleetExcluded | yes | — |
notApplicable |
array of string | yes | — |
GateFinding¶
| Field | Type | Required | Description |
|---|---|---|---|
category |
Category | yes | — |
severity |
Severity | yes | — |
key |
string | no | Stable, count-free identity: category/discriminator. |
title |
string | yes | — |
detail |
string | yes | — |
teams |
array of string | no | — |
namespaces |
array of string | no | Affected namespaces, sorted, at most 100. |
namespacesOmitted |
integer | no | Affected namespaces not listed. |
unnamespaced |
boolean | no | An add-on finding that also covers an install in no named namespace: a manifest object without metadata.namespace, or a cluster-scoped IngressClass. A namespace-scoped ignore rule cannot be shown to match it, so it does not take the finding. |
remediation |
string | no | — |
citations |
array of string (uri) | no | — |
objects |
array of ObjectRef | no | — |
objectsOmitted |
integer | no | Affected objects not listed. |
baselineState |
new | unchanged |
no | — |
callers |
array of Caller | no | API usage findings: the apiserver_requested_deprecated_apis rows folded into this finding, in row order, each the deprecated-api-in-use finding it is on its own. Suppressing every object of the finding by annotation or an object-scoped rule does not suppress them: they then stand as findings of their own. A team-scoped read omits them from a finding it cuts to its teams. |
source |
manifest | cluster |
yes | manifest: introduced by the posted manifests; cluster: the cluster already has it. |
GateResponse¶
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion |
1 |
yes | The version of the report's shape. Within it fields are only added, never renamed, removed, retyped or given a new meaning; a breaking change bumps it (see the compatibility policy). |
toolVersion |
string | yes | The server build that wrote the response, also for a stored evaluation. Informational, no compatibility meaning. |
clusterId |
string | yes | — |
target |
Target | yes | — |
serverVersion |
string | no | The kube-apiserver gitVersion the target was judged against; absent for manifests. |
kubeContext |
string | no | scan of a live cluster only: the kubeconfig context it read. The server never sets it. |
apiServer |
string | no | scan of a live cluster only: the API server it read, as scheme, host and port, without credentials, path or query. The server never sets it. |
kbVersion |
string | yes | — |
score |
integer | yes | max(0, 100 - min(75, 25 x blockers) - min(20, 5 x warnings)) |
ready |
boolean | yes | verdict == ready. |
verdict |
Verdict | yes | — |
notAssessed |
array of CapabilityGap | no | — |
suppressed |
array of SuppressedFinding | no | What ignore rules or object annotations accepted, out of findings, the score and the verdict. A stored or read-time report (GET /report, the fleet, history, notifications, exports) applies the objects' upgradescope.basit.engineer/ignore annotations (source annotation), never the agent's spec.ignore; /gate applies them and its config rules. |
unrecognizedImages |
array of string | no | Image repositories (host/path) no add-on image matcher recognised: a detection gap, not a finding (neither score nor verdict counts them). Sorted, deduplicated, at most 200. |
unrecognizedImagesOmitted |
integer | no | Unrecognized image repositories beyond the 200 listed. |
addOnEvidenceAgeSeconds |
integer | no | How old, in seconds (at least 1), the pod evidence the add-ons were detected from was when the agent reused its last full pod pass instead of listing every pod again (--pod-pass-every, --pod-pass-max-age): an add-on installed or upgraded since is reported as it was then, and a pass of --pod-pass-max-age or more is not reused (the age is that of the collection, so a viewer can see evidence about one interval older). Absent when every pod was read for this report, and from collectors that predate the field. The verdict does not depend on it. |
support |
SupportStatus | no | — |
findings |
array of GateFinding | yes | — |
teams |
TeamScores | no | — |
clusterVerdict |
Verdict | no | With cluster, the verdict of the cluster plus the manifests, existing findings included; with a team-scoped credential, of the scope's share of the cluster plus the manifests. |
suppressedCount |
integer | yes | The number of suppressed entries. |
warnings |
array of string | no | Suppression warnings: rules in config that expired (they no longer apply), upgradescope.basit.engineer/ignore annotations without a reason (not applied), and one line for the posted objects annotated under the deprecated pre-v0.2.0 keys (applied, and read until v0.3.0), naming the first five. Stored objects (cluster) under the old keys are not named here: the cluster's agent reports them in its ClusterReadiness status.notAssessed. |
SARIF¶
A SARIF 2.1.0 log (https://json.schemastore.org/sarif-2.1.0.json) with one run, the report's findings as results.
Type: object.
JUnit¶
JUnit XML valid against the Jenkins JUnit schema (the xUnit
plugin's junit-10.xsd): one testsuite per finding category with
one testcase per finding, failing when the finding reaches
fail-on; a not-assessed suite with an error per required gap the
gate fails on. See scan --output junit.
Type: string.
CodeQuality¶
A GitLab Code Quality report
(https://docs.gitlab.com/ci/testing/code_quality/#code-quality-report-format):
one entry per finding and located object. Findings without a file
are on the virtual path upgradescope/<finding key>. See
scan --output gitlab-codequality.
Type: array of object.
PushRequest¶
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion |
1 |
yes | The push protocol version. |
clusterName |
string | yes | — |
agentVersion |
string | no | — |
kbVersion |
string | no | — |
inventory |
Inventory | yes | — |
Inventory¶
What the agent observed, never a verdict. The server stores it as sent, unknown fields included, so a newer server can judge them.
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion |
1 |
yes | — |
clusterId |
string | no | The kube-system namespace UID. |
collectorSchema |
integer | no | The generation of field meanings the collector filled this inventory with (1 from v0.2.0). Absent from inventories of collectors before it — v0.1.x and v0.2.0's release candidates — which are judged by the push's agentVersion: one that is not a release at or after 0.2.0-0 (including "dev" and empty) is a v0.1.x agent, whose api-usage and deprecated-calls are not assessed and whose chart-found add-on version is evidence only. A later generation, whose meanings this server would misread, is 422. |
source |
cluster |
no | Absent from v0.1.x agents' inventories, which are cluster ones too. A files inventory (scan --files) is judged without versions or add-ons, so a push claiming one is 422: only the agent pushes, and it collects from a cluster. A files snapshot stored before ingest refused them is judged as a cluster one. |
collectedAt |
string (date-time) | no | — |
serverVersion |
string | no | The apiserver's gitVersion, e.g. v1.34.2-gke.100. |
capabilities |
map of CapabilityStatus | no | Every capability the collector has, by name. One the verdict requires and the map does not report — api-usage, versions, and addons when the knowledge base has add-ons — is a required not-assessed gap, so an inventory without this map is never ready. |
apiUsage |
array of object | no | — |
apiAuthorshipUnknown |
array of object | no | Objects of a flagged API with no managedFields entry outside the status subresource and the control plane, and no usable last-applied annotation, so nothing says which version wrote them; shaped like apiUsage, once per kind. Reported as info, never as use of the API. |
deprecatedCalls |
array of object | no | — |
helmReleases |
array of object | no | — |
gitopsCharts |
array of object | no | Charts Argo CD (Application spec.source/spec.sources[] entries with chart set) and Flux (HelmRelease spec.chart.spec, or a chartRef to an OCIRepository) deploy to the scanned cluster, as those resources name them. Each has tool (argocd or flux), name and namespace (the Application's or HelmRelease's own), target (the namespace the chart deploys into; omitted when left to the manifests), chart, version (as the resource spells it, so possibly a constraint or a tag; omitted when none) and repo (an Application's repoURL or an OCIRepository's URL, recorded without userinfo, query or fragment, though a token embedded in the path is kept; for a HelmRelease's own source, Kind/namespace/name). Applications and HelmReleases that deploy to other clusters, or to a target that is not a namespace name, are not listed. A chart reference carries no app version and no stored manifest; a Helm release that Flux's helm-controller leaves is listed in helmReleases. |
addOns |
array of object | no | — |
nodes |
array of object | no | — |
controlPlane |
array of object | no | — |
namespaces |
array of object | no | — |
unrecognizedImages |
array of string | no | Image repositories no add-on image matcher claims; sorted, deduplicated, at most 200. |
unrecognizedImagesOmitted |
integer | no | Unrecognized image repositories the cap dropped. |
provider |
string | no | Known values: eks, gke, aks, other. The managed Kubernetes service the collector inferred, or other when the cluster shows none of the three; absent when it could not tell (the nodes were unreadable and the server version names no provider). A name this build does not know is accepted and has no support calendar (a newer agent may learn more providers); a value that is not a name of at most 63 bytes of letters, digits, -, _ or . is refused (422). |
crds |
array of object | no | CustomResourceDefinitions: versions, status.storedVersions, and the custom resources at a deprecated or unserved version. |
volumePlugins |
array of object | no | The in-tree volume plugins (the volumes capability) the pods, PersistentVolumes and StorageClasses name, one entry per plugin by its field name in a pod's volumes or a PersistentVolume's spec (glusterfs, awsElasticBlockStore), a StorageClass counting for the plugin of its in-tree provisioner (kubernetes.io/rbd): plugin, count (the pods, PersistentVolumes and StorageClasses, or in files mode the pod templates, Pods, PersistentVolumes and StorageClasses, naming it), namespaces (that count per namespace; a bound PersistentVolume under its claim's, an unbound one and a StorageClass under the empty key), and objects (live, the PersistentVolumes and StorageClasses by name, never a pod; in files mode file, line, namespace and name; at most 100) and objectsOmitted. An agent that predates PersistentVolume and StorageClass reads sends pods only, and is still valid. At most 64 entries, each plugin once; a plugin must be a field name (at most 64 ASCII letters and digits, starting with a letter), no count, objectsOmitted or namespace count negative, namespace keys and object refs namespace and object names (422 otherwise). An agent drops an entry that would be refused, marking volumes partial. An inventory without the volumes capability, as older agents push, is not assessed for them. |
addOnEvidenceAgeSeconds |
integer | no | How old, in seconds, the pod evidence the add-ons were detected from was when the agent reused its last full pod pass instead of listing every pod again (--pod-pass-every, --pod-pass-max-age). Absent when every pod was read. Not part of the snapshot's identity, like collectedAt: a push that differs only in it is a duplicate and is not stored, so the stored report keeps the age it was first pushed with, as it keeps that push's collectedAt (the evidence is as of collectedAt). |
apiServerStartTime |
string (date-time) | no | process_start_time_seconds of the kube-apiserver whose /metrics deprecatedCalls were read from, in whole seconds: apiserver_requested_deprecated_apis counts requests since then. Absent when the scrape did not report it. Not part of the snapshot's identity, like collectedAt: a push that differs only in it is a duplicate. The server ignores one before 2014 or more than 10 minutes after collectedAt. |
PushAccepted¶
| Field | Type | Required | Description |
|---|---|---|---|
snapshotId |
integer (int64) | yes | — |
PushDuplicate¶
| Field | Type | Required | Description |
|---|---|---|---|
snapshotId |
integer (int64) | yes | The latest snapshot |
duplicate |
true |
yes | — |
RenameRequest¶
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | — |
Registry¶
| Field | Type | Required | Description |
|---|---|---|---|
addons |
array of AddOn | yes | — |
Citations¶
Type: array of string (uri) or null.
AddOn¶
One add-on registry entry (registry/data/
| Field | Type | Required | Description |
|---|---|---|---|
schema_version |
integer | yes | — |
id |
string | yes | — |
display_name |
string | yes | — |
matchers |
object | yes | — |
support |
object | yes | — |
cycles |
array of object | no | — |
compat |
array of object | no | — |
recommendation |
string | no | — |
endoflife_product |
string | no | — |