Catalog
A Catalog is a namespace’s shared catalog: one Corrosion cluster that
every Library in the namespace writes into. Declare one Catalog in a
namespace. It runs the catalog pod, the one durable member of that
cluster, which holds the namespace’s catalog on a durable claim and
reports what it holds over the bus. It sizes that claim, the claim
every Library’s Jobs take, and the claim every screen’s agent runs
on, and it owns the pod, its claim, and the namespace’s catalog
Service and EndpointSlice.
A Catalog name is at most 32 characters, because library-operator builds the names
of the objects it creates from it, and Kubernetes limits some of those
names to 63 characters.
Each catalog agent holds the whole namespace’s catalog, because the
cluster gossips every row to every peer. So one size covers the whole
namespace, on the Catalog, in place of a size on each Library.
apiVersion: library.liken.sh/v1alpha1
kind: Catalog
metadata:
name: media
namespace: media
spec:
storage:
size: 1Gi
A namespace has exactly one Catalog. A Library in a namespace with no
Catalog waits until one exists, and more than one Catalog marks every
Catalog in the namespace Blocked and runs no cluster. An empty
storageClassName binds each catalog volume to the cluster’s default
StorageClass. A claimName names an existing claim for the catalog
pod to mount in place of the one the operator provisions. A SQLite
file on a claim served over NFS can corrupt when its node is lost, so
prefer a StorageClass that binds node-local storage. A
spec.screens.storageClassName classes the screens’ claims apart from
the durable one, and omitted, the cluster’s default binds them too.
The namespace’s shared catalog. Declare one Catalog in a namespace, and every Library in it writes into that catalog.
spec
Where the catalog is stored and how large each agent’s copy is.
| Field | Type | Required | Description |
|---|---|---|---|
storage |
object | yes | The catalog of record every other agent copies, held on one claim that every durable copy mounts. The size is also the size of every working copy, and the class is the default class for the progress and libraries claims. |
progress |
object | no | The claim the progress store runs on: who watched what, and how far. Each field defaults to the field of the same name under storage, so a Catalog that names neither keeps the store on the catalog’s class at the catalog’s size. |
libraries |
object | no | The claim each Library’s Jobs run on, one Job at a time, named after the Library with the suffix -catalog. Each is a working copy of the whole catalog that a Job rebuilds from the catalog of record, so a namespace that keeps the catalog of record on a durable class keeps these on a node-local class such as local-path. On a per-node class the claim is ReadWriteOncePod, so Kubernetes admits one pod of it in the cluster at a time. |
screens |
object | no | The settings every screen pod in the namespace takes. |
jellyfin |
object | no | The Jellyfin server this namespace keeps playback progress with, in both directions. A Catalog that names one makes the operator run a pod and a Service named after the Catalog with the suffix -jellyfin, beside the progress store. The pod records what Jellyfin reports into the progress store, and writes what a screen played back to Jellyfin. Omitted, the operator runs neither and deletes the previous pair. |
spec.storage
The catalog of record every other agent copies, held on one claim that every durable copy mounts. The size is also the size of every working copy, and the class is the default class for the progress and libraries claims.
| Field | Type | Required | Description |
|---|---|---|---|
size |
string | no | The size of each agent’s catalog volume. Small by default. Default: 1Gi. |
storageClassName |
string | no | The StorageClass each agent’s catalog volume binds to. Omitted, the cluster’s default binds it. A class the per-node driver serves, whose provisioner is per-node.liken.sh, lets the operator run more than one copy of the catalog, because every copy then holds a directory of its own on the node where it runs. The operator reads the provisioner of the class and never matches its name. |
claimName |
string | no | An existing PersistentVolumeClaim in this namespace for every copy of the catalog to mount, in place of the one the operator provisions. The operator creates no claim and no volume when it is set. |
replicas |
integer | no | How many durable copies of the catalog the operator runs for the namespace. The copies are peers that Corrosion syncs from one another, and every copy mounts one claim of storage.size, named after the Catalog with the suffix -catalog. More than one copy needs a per-node class. On any other class the operator runs one copy, and the Ready condition is False with the reason ClassNotPerNode. No two copies share a node, so a copy the scheduler cannot place stays Pending and the Catalog is not Ready. A copy on a node that stays NotReady for ten minutes is deleted and started again elsewhere. It takes the claim with it only on a class that binds the claim to the node. A copy taken away by a lower count loses its pod alone, because the claim serves the copies that remain. Default: 1. |
spec.progress
The claim the progress store runs on: who watched what, and how far. Each field defaults to the field of the same name under storage, so a Catalog that names neither keeps the store on the catalog’s class at the catalog’s size.
| Field | Type | Required | Description |
|---|---|---|---|
size |
string | no | The size of the progress claim, in a binary unit such as 256Mi. The progress rows are small next to the catalog, so a namespace that keeps both central stores on a durable class names a smaller size here. Omitted, the claim takes storage.size. A size change applies to a new claim, not an existing one, because a bound claim’s spec is immutable. Delete the existing claim, and the next pass creates it at the new size. |
storageClassName |
string | no | The StorageClass the progress claim binds to. Omitted, the claim takes storage.storageClassName, and when that is also omitted the cluster’s default binds it. A per-node class lets the operator run more than one copy of the progress store, on the same terms as storage.storageClassName. |
replicas |
integer | no | How many durable copies of the progress store the operator runs for the namespace, on the same terms as storage.replicas, against the class this block names. Every copy mounts one claim, named after the Catalog with the suffix -progress. More than one copy needs a per-node class here as well, and on any other class the Ready condition is False with the reason ClassNotPerNode. The first copy records what crosses the bus, and every copy after it holds the rows. Default: 1. |
spec.libraries
The claim each Library’s Jobs run on, one Job at a time, named after the Library with the suffix -catalog. Each is a working copy of the whole catalog that a Job rebuilds from the catalog of record, so a namespace that keeps the catalog of record on a durable class keeps these on a node-local class such as local-path. On a per-node class the claim is ReadWriteOncePod, so Kubernetes admits one pod of it in the cluster at a time.
| Field | Type | Required | Description |
|---|---|---|---|
storageClassName |
string | no | The StorageClass a Library’s catalog claim binds to. Omitted, it takes storage.storageClassName, and when that is also omitted the cluster’s default binds it. There is no size here: every agent holds the whole catalog, so the claim takes storage.size. |
spec.screens
The settings every screen pod in the namespace takes.
| Field | Type | Required | Description |
|---|---|---|---|
storageClassName |
string | no | The StorageClass both of a screen’s claims bind to. Omitted, the cluster’s default binds them. A node-local class such as local-path is the right one, because a screen pod is already pinned to the machine that holds its display. On such a class, a screen the scheduler refuses for five minutes loses its pod and both claims, and the next pass creates them again where the display is. On a per-node class the claims pin the pod to no node, and the operator never deletes them. |
artCache |
object | no | The volume each screen’s browser keeps its scaled art on: posters, backdrops, episode stills, logos, and headshots. A screen that restarts draws the wall from art it already scaled. |
spec.screens.artCache
The volume each screen’s browser keeps its scaled art on: posters, backdrops, episode stills, logos, and headshots. A screen that restarts draws the wall from art it already scaled.
| Field | Type | Required | Description |
|---|---|---|---|
size |
string | no | The size of each screen’s art claim, in a binary unit such as 2Gi. The browser is told to keep 128 MiB under it. A size change applies to new screens, not existing ones, because a bound claim’s spec is immutable. Delete an existing screen’s claim, and the next pass creates it at the new size. Default: 2Gi. |
spec.jellyfin
The Jellyfin server this namespace keeps playback progress with, in both directions. A Catalog that names one makes the operator run a pod and a Service named after the Catalog with the suffix -jellyfin, beside the progress store. The pod records what Jellyfin reports into the progress store, and writes what a screen played back to Jellyfin. Omitted, the operator runs neither and deletes the previous pair.
| Field | Type | Required | Description |
|---|---|---|---|
url |
string | yes | The Jellyfin server’s address on the cluster network, such as http://jellyfin.jellyfin.svc:8096 . |
secretRef |
object | yes | The Secret in this namespace that holds a Jellyfin API key, and the key inside it. The API key is one an administrator issues on the server, because the pod writes the playback position of any user. |
spec.jellyfin.secretRef
The Secret in this namespace that holds a Jellyfin API key, and the key inside it. The API key is one an administrator issues on the server, because the pod writes the playback position of any user.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | The Secret’s name, in this Catalog’s own namespace. |
key |
string | no | The key inside that Secret. When omitted, token. Default: token. |
status
The catalog cluster that this Catalog runs. Only the library operator writes it.
| Field | Type | Required | Description |
|---|---|---|---|
members |
[]string | no | The pods that are members of the namespace’s catalog cluster: the durable copies of the catalog, the pods of the Jobs that are running, and the screen pods. |
storageSize |
string | no | The storage size the agents were given. |
replicas |
object | no | The durable copies of the namespace’s two stores: for each, the count that is up beside the count the Catalog asks for. |
screens |
[]object | no | One entry per screen pod in the namespace, in Player order: the Player it draws for, the claim its catalog agent runs on, the claim its art cache is on, the node it runs on, and its phase. A screen whose namespace has no single Catalog runs on emptyDirs and names neither claim. |
jellyfin |
object | no | The one-time backfill of the progress the namespace’s Jellyfin server already held before this operator recorded it: the server it ran against, its current phase, and when it finished. Present only while spec.jellyfin names a server. |
conditions |
[]object | no | The typed observations the operator keeps on this Catalog, in the standard Kubernetes form. Ready is True when every durable copy of the catalog runs with every container ready. It is False with the reason ClassNotPerNode when the Catalog asks for copies of a store on a class that cannot hold more than one, and otherwise False with the reason PodPending, PodFailed, or ManyCatalogs, naming the first copy that is not up. |
status.replicas
The durable copies of the namespace’s two stores: for each, the count that is up beside the count the Catalog asks for.
| Field | Type | Required | Description |
|---|---|---|---|
catalog |
object | no | The copies of the catalog. |
progress |
object | no | The copies of the progress store. |
status.replicas.catalog
The copies of the catalog.
| Field | Type | Required | Description |
|---|---|---|---|
ready |
integer | no | How many copies run with every container ready. |
wanted |
integer | no | How many copies the Catalog asks for, from storage.replicas. The count is the one the Catalog states, so a Catalog whose class cannot hold more than one copy reports the copies it asked for beside the one that is up. |
status.replicas.progress
The copies of the progress store.
| Field | Type | Required | Description |
|---|---|---|---|
ready |
integer | no | How many copies run with every container ready. |
wanted |
integer | no | How many copies the Catalog asks for, from progress.replicas. |
status.screens[]
One entry per screen pod in the namespace, in Player order: the Player it draws for, the claim its catalog agent runs on, the claim its art cache is on, the node it runs on, and its phase. A screen whose namespace has no single Catalog runs on emptyDirs and names neither claim.
| Field | Type | Required | Description |
|---|---|---|---|
player |
string | no | The Player the screen draws for. |
claim |
string | no | The claim the screen’s catalog agent runs on, or empty for a screen on an emptyDir. |
artClaim |
string | no | The claim the screen’s art cache is on, or empty for a screen on an emptyDir. |
node |
string | no | The node the screen pod runs on. On a node-local class it is the node both of its claims are bound to. |
phase |
string | no | The screen pod’s phase, as the kubelet reports it. |
status.jellyfin
The one-time backfill of the progress the namespace’s Jellyfin server already held before this operator recorded it: the server it ran against, its current phase, and when it finished. Present only while spec.jellyfin names a server.
| Field | Type | Required | Description |
|---|---|---|---|
server |
string | no | The server address the backfill ran against, from spec.jellyfin.url. A Catalog that changes the address runs the backfill again against the new one. |
backfill |
string | no | The backfill’s current phase. Pending waits for every durable copy of the progress store to be up. Running means the Job is running. Failed means the Job failed past its backoff limit, and the operator deletes it and creates it again after a wait that doubles each time. Finished means the Job exited zero. To run the backfill again against the same server, clear status.jellyfin. |
backfilled |
string | no | When the backfill finished, in UTC. Absent until it did. |
status.conditions[]
The typed observations the operator keeps on this Catalog, in the standard Kubernetes form. Ready is True when every durable copy of the catalog runs with every container ready. It is False with the reason ClassNotPerNode when the Catalog asks for copies of a store on a class that cannot hold more than one, and otherwise False with the reason PodPending, PodFailed, or ManyCatalogs, naming the first copy that is not up.
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | yes | The check this entry reports, in CamelCase. It is the key of this list. Pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$. |
status |
string | yes | The condition’s status. True is the healthy status, and Unknown means the operator cannot tell yet. One of: True, False, Unknown. |
observedGeneration |
integer | no | The metadata.generation that this condition reflects. |
reason |
string | no | One CamelCase word for why the condition has this status, for a program to match on. Pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$. |
message |
string | no | The same reason, as a sentence for a person to read. |
lastTransitionTime |
string | yes | When the status last changed. A change of the reason or the message alone does not move it. |