Skip to main content
Version: 3.0.0-rc1

resources-presenter

Read-only HTTP API for querying current Kubernetes resource state stored in PostgreSQL.

Overview

resources-presenter serves the current state of a set of Kubernetes resources (one row per resource, identified by global_uid). It does not ingest data, it reads from a krateo_resources table populated by an another component: resources-ingester.

Key features:

  • GET and POST query support with filter capabilities to get lists of resources matching criteria
  • Single-resource detail endpoint via global_uid
  • Configurable sorting via sort_by parameter (resource, created_at, updated_at, global_uid, composition_id) with optional sort_order (asc, desc)
  • Keyset pagination for stable, efficient paging (cursor is sort-order-aware)
  • Dynamic resource resolution via group (required) plus optional version and resource filters: discovery-based, no static registry needed
  • JSONB label filtering via PostgreSQL containment (@>)
  • Batch RBAC enforcement via discovery → RBAC batch check → filtered query
  • Structured latency logging for every request (parse, discovery, rbac, query, serialize phases)

API

Endpoints

MethodPathDescription
GET/resourcesList resources matching filters (query parameters)
POST/resourcesList resources matching filters (JSON body)
GET/resources/{global_uid}Get a single resource by global_uid

List Endpoint — GET /resources and POST /resources

Returns resources matching the provided filters. The resource type is identified by the required group parameter. version and resource are optional filters that narrow the discovery query. These map directly to the DB columns resource_group, resource_version, and resource_plural in the krateo_resources table.

Filters

All filters are optional (except group) and combined with AND.

ParameterTypeBehavior
groupstringRequired. API group (e.g. apps, widgets.templates.krateo.io)
versionstringOptional. API version (e.g. v1, v1beta1). Narrows discovery.
resourcestringOptional. Resource plural name (e.g. deployments, panels). Lowercase. Narrows discovery.
clusterstringExact match on cluster_name
namespacestringExact match on namespace. Default: "default". Use "*" for all namespaces. See Namespace Handling.
composition_idUUIDExact match on composition_id
namestringExact match on resource_name. Mutually exclusive with name_contains.
name_containsstringCase-insensitive partial match (ILIKE %name_contains%). Mutually exclusive with name.
labelsJSON objectJSONB containment on metadata.labels (@>)
sinceRFC3339Resources with updated_at >= since
rawbooleanInclude full Kubernetes object (default: false)
status_rawbooleanInclude Kubernetes status subtree (default: false)
limitintegerPage size (default: 100). Minimum: 1, Maximum: 5000.
cursorbase64Opaque keyset cursor from previous response. Cursor is sort-aware: a cursor from one sort_by/sort_order combination cannot be reused with a different one.

GET uses query parameters. POST uses the same fields as a JSON body (with labels as a JSON object, not a string). Note: kind is not a query parameter: the resource (plural) field is used for filtering. Only group is required; all other fields are optional.

List response

{
"count": 2,
"items": [
{
"name": "my-panel",
"uid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"global_uid": "prod-eu:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"namespace": "krateo-system",
"group": "widgets.templates.krateo.io",
"version": "v1beta1",
"kind": "Panel",
"resource": "panels",
"cluster_name": "prod-eu",
"created_at": "2026-03-01T10:00:00Z",
"updated_at": "2026-03-06T14:30:00Z"
},
{
"name": "my-panel-2",
"uid": "b2c3d4e5-f6a7-8901-bcde-f2345678901a",
"global_uid": "prod-eu:b2c3d4e5-f6a7-8901-bcde-f2345678901a",
"namespace": "krateo-system",
"group": "widgets.templates.krateo.io",
"version": "v1beta1",
"kind": "Panel",
"resource": "panels",
"cluster_name": "prod-eu",
"created_at": "2026-03-02T11:00:00Z",
"updated_at": "2026-03-07T15:30:00Z"
}
],
"cursor": "<base64-opaque-token>"
}
  • global_uid is always present: composite key in cluster_name:uid format, uniquely identifies a resource across clusters
  • composition_id appears only when set (non-null)
  • raw appears only when raw=true is requested
  • status_raw appears only when status_raw=true is requested (list)
  • cursor is absent on the last page or when results fit in a single page

Namespace Handling

The namespace parameter follows Kubernetes API semantics:

ValueBehavior
(absent/empty)Defaults to "default": only resources in the default namespace are returned
*All namespaces: no namespace filter is applied. RBAC is still enforced, so only resources the user has access to will be returned.
prod, krateo-system, etc.Exact match on the specified namespace

This mirrors how kubectl works: commands target the default namespace unless -n or --all-namespaces is specified.

Sorting

The sort_by parameter controls which column(s) results are sorted by. The default is resource (group, version, resource (plural), namespace, name). Other options are created_at, updated_at, global_uid, and composition_id.

The sort_order parameter controls the direction: asc (ascending) or desc (descending). Each sort_by value has a sensible default direction: created_at and updated_at default to desc (newest first), all others default to asc. You can override this by explicitly passing sort_order.


Detail Endpoint — GET /resources/{global_uid}

Fetches a single resource by its global_uid (format: cluster_name:uid, e.g. prod-eu:a1b2c3d4-e5f6-7890-abcd-ef1234567890).

The global_uid is returned in every list response item, so clients can use it directly to fetch the full resource detail.

This endpoint does not support any of the list filters (group, version, resource, cluster, namespace, name, name_contains, labels, since, limit, cursor). It takes only the global_uid path parameter and the optional raw and status_raw query parameters to control the exclusion or inclusion of the full Kubernetes object and status subtree.

Query parameters

ParameterTypeDefaultBehavior
rawbooleantrueInclude the full Kubernetes object. Set ?raw=false to exclude.
status_rawbooleantrueInclude the Kubernetes status subtree. Set ?status_raw=false to exclude. Omitted when NULL in the database.

Note: raw and status_raw default to true on the detail endpoint (opposite of the list endpoint where they default to false).

Detail response

Same structure as the list endpoint, with count: 1 and a single item in the items array:

{
"count": 1,
"items": [
{
"name": "my-panel",
"uid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"global_uid": "prod-eu:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"namespace": "krateo-system",
"group": "widgets.templates.krateo.io",
"version": "v1beta1",
"kind": "Panel",
"resource": "panels",
"cluster_name": "prod-eu",
"created_at": "2026-03-01T10:00:00Z",
"updated_at": "2026-03-06T14:30:00Z",
"raw": { "..." },
"status_raw": { "..." }
}
]
}

No cursor field: there is no pagination on the detail endpoint.


Error Responses

Kubernetes-style Status objects:

CodeEndpointCondition
400ListInvalid/missing parameters (missing group, bad UUID, JSON, timestamp, cursor)
400DetailMissing global_uid path parameter
403ListForbidden: RBAC denied access to all discovered resources. If even one is allowed, the request will succeed with just those allowed.
403DetailForbidden: RBAC denied access to the requested resource
404DetailResource not found: no active resource matches the given global_uid
405ListMethod not allowed (only GET and POST)
405DetailMethod not allowed (only GET)
500BothInternal server error

See SEARCH.md for full examples including pagination loops.

Configuration

Environment VariableDefaultDescription
PORT8080HTTP server port
DEBUGfalseEnable debug-level logging
DB_USERPostgreSQL username
DB_PASSPostgreSQL password
DB_HOSTlocalhostPostgreSQL host
DB_PORT5432PostgreSQL port
DB_NAMEPostgreSQL database name
DB_PARAMSExtra connection params (e.g. sslmode=disable)
DB_READY_TIMEOUT5mMax wait for PostgreSQL to become ready

Testing

# All tests (requires Docker for testcontainers)
go test ./... -cover

# Unit tests only (fast, uses pgxmock, no Docker)
go test ./internal/sql/ -cover -v

# Integration tests only (real PostgreSQL via testcontainers)
go test ./internal/handlers/ -cover -v

See TESTING.md for detailed testing instructions.

Notes on RBAC

RBAC is enforced at resource level and not on the single object. For instance, if a user has access to widgets.templates.krateo.io/panels in namespace krateo-system then they can see all the panels in that namespace. However, if they don't have access to widgets.templates.krateo.io/panels in krateo-system then they can't see any panel in that namespace, even if they have access to a specific panel object like widgets.templates.krateo.io/panels/my-panel in krateo-system. This practically means that RBAC is enforced on the resource type (group/version/resource) + namespace and not on the single object and so for a user to have access to a specific object they need to have access to the whole resource type in that namespace.

Health Probes

GET /livez   → 200 OK (checks if service is running)
GET /readyz → 200 OK (checks PostgreSQL connectivity)

Latency information

Information about latency is logged for each request, broken down into phases. You can find more information about latency in LATENCY.md.

License

See LICENSE.