
<!-- Generated from deploy/metadataproviders-crd.yaml by crdref. Do not edit. -->

# `MetadataProvider`

A `MetadataProvider` is one account with one metadata provider: the
`Secret` that holds its key, and the facts it may serve. It lives
in the namespace of the libraries that name it, because a pod mounts
a `Secret` only from its own namespace. A `Library` names providers by
name in `spec.sources`, in the order they are asked, and for each
fact the first provider in that list that serves it is the one
asked. The `trailer` and `marks` facts take the union of their
providers, so they ask every provider in the list that serves them.

A `MetadataProvider` 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.

A `MetadataProvider` names exactly one provider block: `tmdb`,
`omdb`, `fanart`, `tvmaze`, `peertube`, `archive`, `theintrodb`,
`introdb`, or `imdb`. TMDb, OMDb, and Fanart.tv take a key from a
`Secret`; TVmaze, the Internet Archive, IntroDB, and IMDb take none,
so their blocks are empty. TheIntroDB takes a key where its block names a `Secret`
and asks with none where it names none. PeerTube takes no key either,
but it is software that many people run, so its block names the
address of one instance. The `PROVIDER` column shows the block.

The operator paces every provider: one request at a time per block,
with a fixed gap between requests that fits each provider's stated
limits, and a quarter second for the Internet Archive and IntroDB.

`spec.facts` is optional. A provider that names none serves every
fact the operator can request, and `status.facts`, shown
in the `FACTS` column, lists what it serves right now. That list is
empty while the provider is not `Ready`.

    apiVersion: library.liken.sh/v1alpha1
    kind: MetadataProvider
    metadata:
      name: tmdb
      namespace: media
    spec:
      tmdb:
        secretRef:
          name: tmdb-key
          key: token
      facts: [identity]   # optional; omit to serve the whole table
    ---
    apiVersion: library.liken.sh/v1alpha1
    kind: MetadataProvider
    metadata:
      name: omdb
      namespace: media
    spec:
      omdb:
        secretRef:
          name: omdb-key
    ---
    apiVersion: library.liken.sh/v1alpha1
    kind: MetadataProvider
    metadata:
      name: tvmaze
      namespace: media
    spec:
      tvmaze: {}
    ---
    apiVersion: library.liken.sh/v1alpha1
    kind: MetadataProvider
    metadata:
      name: imdb
      namespace: media
    spec:
      imdb: {}
    ---
    apiVersion: library.liken.sh/v1alpha1
    kind: Library
    metadata:
      name: movies
      namespace: media
    spec:
      sources: [tmdb, imdb, omdb, tvmaze]
      # the rest as before

The operator checks each provider with one call to the provider, and
reports the answer in the
`Ready` condition: `Reachable`, `NoSecret`, `Refused`, `LimitReached`,
`Unreachable`, or `Unavailable`. `LimitReached` is a key the provider
answered with its request limit. OMDb answers a key it does not accept
and a key that has spent its calls for the day with the same
`401`, and names which one in its answer, so the check reads the answer.
A `Refused` or `LimitReached` message carries the provider's own words.
`Unreachable` is a check that got no answer at all and
carries the error as its message. `Unavailable` is a check the provider
answered with a status that says nothing about the account, and its
message names that status code. The key reaches each phase container
of a `Library`'s `Job` through a `secretKeyRef` that the kubelet
resolves. It never passes through a status, a log, or the
catalog.

The check calls a provider when the operator starts and when the
provider's `metadata.generation` changes. Otherwise it calls a provider
whose last answer was `Reachable` or `LimitReached` once an hour, and
every other provider every five minutes. Every call before the limit
resets counts against it, and OMDb does not publish when it resets, so
a key past its limit is asked once an hour. The operator reads the `Secret` only for
a check call, because the `Job` takes the key through its
`secretKeyRef`. An edit of the `Secret` alone therefore shows at the
next call: within five minutes for a `Refused` or `NoSecret` provider,
and within the hour for a `Reachable` one. An account with a daily
allowance, such as OMDb's thousand calls, spends at most twenty-four of
them a day on the check while its key works.

The `imdb` block has no API to call. IMDb publishes its datasets as
files, so the check sends one `HEAD` request for each file that the
provider's served facts read: `title.ratings` and `title.episode` for
`rating.imdb`, and `title.principals` and `name.basics` for `credits`. Every file must answer `200` for `Reachable`. Any other
status is `Unavailable`, and the message names the file. The check
keeps the same schedule as every other provider, so while IMDb
answers it sends one `HEAD` request for each file an hour. A `HEAD`
request transfers no file.

`status.imdb.datasets` lists what IMDb returned for each file: its
`lastModified`, `etag`, and `size`. A failed check leaves the entries
of the last check that read the files. The `UPDATED` column shows the
oldest `lastModified`. IMDb replaces each file every day, so a file
older than three days writes the `Stale` condition with status `True`,
and its message names the file and its date. `Stale` does not change
`Ready`, because a file from last week still gives correct ratings and
credits.

    $ kubectl -n media get metadataprovider imdb
    NAME   PROVIDER   READY   REASON      UPDATED   AGE
    imdb   imdb       True    Reachable   9h        3d

The operator caches the files on the claim `<provider>-datasets`, 3Gi,
in the provider's namespace. The provider owns the claim, so deleting
the provider deletes the cache. The claim is on the cluster's
`StorageClass` whose provisioner is `per-node.liken.sh`, whatever class
the libraries use, and each node keeps its own copy of each file. The
`Cached` condition is `True` with the reason `PerNodeClass` when the
claim exists. A cluster with no per-node class gets no claim, and
`Cached` is `False` with the reason `NoPerNodeClass`. Each enricher
run then reads the files from IMDb directly. `Cached` does not change
`Ready` either.

One account with one metadata provider, named by the Libraries of its namespace in spec.sources.

## spec

The provider this account is with, and the facts it may serve. A spec that names no facts serves every fact the operator can request from this provider.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="spec--tmdb"></span>`tmdb` | [object](#spectmdb) | no | The account is with The Movie Database, which serves movies, series, and people. |
| <span id="spec--omdb"></span>`omdb` | [object](#specomdb) | no | The account is with OMDb. OMDb answers on an IMDb id, and it serves the plot, the US certification, and the ratings of IMDb, Rotten Tomatoes, and Metacritic. |
| <span id="spec--fanart"></span>`fanart` | [object](#specfanart) | no | The account is with Fanart.tv, which provides art only. It is the only provider of clearart, banner, landscape, discart, and season-banner files. |
| <span id="spec--tvmaze"></span>`tvmaze` | object | no | The account is with TVmaze, which serves series alone and needs no account. The block is empty, and its presence says that the operator may ask TVmaze. |
| <span id="spec--peertube"></span>`peertube` | [object](#specpeertube) | no | The account is with one PeerTube instance, which provides only the trailer fact and needs no account. Many people run PeerTube, so this block identifies the instance by its address. |
| <span id="spec--archive"></span>`archive` | object | no | The account is with the Internet Archive, whose movie_trailers collection serves the trailer fact alone and needs no account. The block is empty, and its presence says that the operator may ask the archive. The operator asks it no faster than four times a second. |
| <span id="spec--theintrodb"></span>`theintrodb` | [object](#spectheintrodb) | no | The account is with TheIntroDB, a community database of the intro, recap, credits, and preview spans of movies and episodes. It provides only the marks fact, and it finds a work by its TMDb id. A key is optional. Without one, TheIntroDB answers 500 asks a day for each address and serves accepted submissions alone. With one, it answers 1000 asks a day for the account and adds the account's own pending submissions. The operator checks it at /health, which spends none of the daily allowance and cannot test the key. |
| <span id="spec--introdb"></span>`introdb` | object | no | The account is with IntroDB, a community database of the intro, recap, credits, and post-credits spans of movies and episodes. It provides only the marks fact, finds a work by its IMDb id, and needs no account. The block is empty, and its presence says that the operator may ask IntroDB. |
| <span id="spec--imdb"></span>`imdb` | object | no | The account is with IMDb's published datasets, which serve the rating.imdb fact for movies, series, and episodes, and the credits fact for movies and series, and need no account. The credits hold about nine people for each title, so the operator uses them only where no source before this one in a Library's sources had credits. The block is empty, and its presence says that the operator may download the files. IMDb publishes the files for personal and non-commercial use, so each cluster downloads them from IMDb. An enricher reads each file once per run and keeps only the rows of the titles in its gap list. Where the cluster has a StorageClass whose provisioner is per-node.liken.sh, the operator makes the claim <provider>-datasets of 3Gi on it, and each node keeps a copy of each file there. |
| <span id="spec--facts"></span>`facts` | []string | no | The facts this account may serve, from the fixed vocabulary. The list narrows what the operator can request from this provider. Omit it to serve all of them. A Library asks this provider only for a fact that status.facts lists. |

### spec.tmdb

The account is with The Movie Database, which serves movies, series, and people.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="spectmdb--secretref"></span>`secretRef` | [object](#spectmdbsecretref) | yes | The Secret in this namespace that holds the credential, and the key inside it. Either credential TMDb issues works: a v3 API key of 32 hex characters, or a v4 read access token. |

#### spec.tmdb.secretRef

The Secret in this namespace that holds the credential, and the key inside it. Either credential TMDb issues works: a v3 API key of 32 hex characters, or a v4 read access token.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="spectmdbsecretref--name"></span>`name` | string | yes | The Secret's name, in this provider's own namespace. |
| <span id="spectmdbsecretref--key"></span>`key` | string | no | The key inside that Secret. When omitted, token. Default: `token`. |

### spec.omdb

The account is with OMDb. OMDb answers on an IMDb id, and it serves the plot, the US certification, and the ratings of IMDb, Rotten Tomatoes, and Metacritic.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="specomdb--secretref"></span>`secretRef` | [object](#specomdbsecretref) | yes | The Secret in this namespace that holds the OMDb key, and the key inside it. The free tier of a key is a thousand calls a day. |

#### spec.omdb.secretRef

The Secret in this namespace that holds the OMDb key, and the key inside it. The free tier of a key is a thousand calls a day.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="specomdbsecretref--name"></span>`name` | string | yes | The Secret's name, in this provider's own namespace. |
| <span id="specomdbsecretref--key"></span>`key` | string | no | The key inside that Secret. When omitted, token. Default: `token`. |

### spec.fanart

The account is with Fanart.tv, which provides art only. It is the only provider of clearart, banner, landscape, discart, and season-banner files.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="specfanart--secretref"></span>`secretRef` | [object](#specfanartsecretref) | yes | The Secret in this namespace that holds the Fanart.tv project key, and the key inside it. |

#### spec.fanart.secretRef

The Secret in this namespace that holds the Fanart.tv project key, and the key inside it.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="specfanartsecretref--name"></span>`name` | string | yes | The Secret's name, in this provider's own namespace. |
| <span id="specfanartsecretref--key"></span>`key` | string | no | The key inside that Secret. When omitted, token. Default: `token`. |

### spec.peertube

The account is with one PeerTube instance, which provides only the trailer fact and needs no account. Many people run PeerTube, so this block identifies the instance by its address.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="specpeertube--endpoint"></span>`endpoint` | string | yes | The address of the instance, such as https://tube.example. The operator checks it at /api/v1/config, and the trailer fact searches its videos by title. Pattern: `^https://`. |

### spec.theintrodb

The account is with TheIntroDB, a community database of the intro, recap, credits, and preview spans of movies and episodes. It provides only the marks fact, and it finds a work by its TMDb id. A key is optional. Without one, TheIntroDB answers 500 asks a day for each address and serves accepted submissions alone. With one, it answers 1000 asks a day for the account and adds the account's own pending submissions. The operator checks it at /health, which spends none of the daily allowance and cannot test the key.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="spectheintrodb--secretref"></span>`secretRef` | [object](#spectheintrodbsecretref) | no | The Secret in this namespace that holds the TheIntroDB API key, and the key inside it. Omit it to ask with no key. |

#### spec.theintrodb.secretRef

The Secret in this namespace that holds the TheIntroDB API key, and the key inside it. Omit it to ask with no key.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="spectheintrodbsecretref--name"></span>`name` | string | yes | The Secret's name, in this provider's own namespace. |
| <span id="spectheintrodbsecretref--key"></span>`key` | string | no | The key inside that Secret. When omitted, token. Default: `token`. |

## status

What the operator's own check found, written only by the library operator.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="status--provider"></span>`provider` | string | no | The provider block this account names. The PROVIDER column reads it here, because no printer column can read which block a spec holds. |
| <span id="status--facts"></span>`facts` | []string | no | The facts this provider serves now: what the operator can request from this provider, narrowed by spec.facts. The list is empty while the Ready condition is not True, because an unreachable provider serves nothing. |
| <span id="status--lastrefusal"></span>`lastRefusal` | string | no | When the provider last refused the key. The time remains after the key works again, so a person can see that it once failed. |
| <span id="status--imdb"></span>`imdb` | [object](#statusimdb) | no | What the check of an imdb block read from IMDb. A failed check leaves the entries of the last check that read the files, because the last version IMDb published is still a fact. |
| <span id="status--conditions"></span>`conditions` | [\[\]object](#statusconditions) | no | Ready is True with the reason Reachable when the provider answered the operator's check, and False with the reason NoSecret, Refused, LimitReached, Unreachable, or Unavailable. LimitReached is a key the provider answered with its request limit, as OMDb does when a key has spent its calls for the day. The key is good, and the check asks again in an hour. Refused and LimitReached carry the provider's own words in the message. Unreachable is a check that got no answer at all, and its message is the error the check read. Unavailable is a check the provider answered with a status that says nothing about the account, and its message names that status code. For an imdb block, the check sends one HEAD request for each file, and Unavailable names the file. An imdb block also carries two conditions that do not change Ready. Stale is True with the reason NotReplaced when IMDb has not replaced a file for more than three days, and its message names the file and its date. Cached is True with the reason PerNodeClass when the claim <provider>-datasets holds the files, and False with the reason NoPerNodeClass when the cluster has no per-node class, so each run reads the files from IMDb. |

### status.imdb

What the check of an imdb block read from IMDb. A failed check leaves the entries of the last check that read the files, because the last version IMDb published is still a fact.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="statusimdb--updated"></span>`updated` | string | no | The oldest lastModified in datasets, which the UPDATED column shows. |
| <span id="statusimdb--datasets"></span>`datasets` | [\[\]object](#statusimdbdatasets) | no | One entry for each dataset file the served facts read, with the headers IMDb returned for the check's HEAD request. rating.imdb reads title.ratings and title.episode, and credits reads title.principals and name.basics. |

#### status.imdb.datasets[]

One entry for each dataset file the served facts read, with the headers IMDb returned for the check's HEAD request. rating.imdb reads title.ratings and title.episode, and credits reads title.principals and name.basics.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="statusimdbdatasets--name"></span>`name` | string | yes | The file's name at IMDb, without the .tsv.gz suffix. |
| <span id="statusimdbdatasets--lastmodified"></span>`lastModified` | string | no | When IMDb last replaced the file, from its Last-Modified header. IMDb replaces each file every day. |
| <span id="statusimdbdatasets--etag"></span>`etag` | string | no | The file's ETag header. |
| <span id="statusimdbdatasets--size"></span>`size` | integer | no | The size of the gzipped file in bytes, from its Content-Length header. |

### status.conditions[]

Ready is True with the reason Reachable when the provider answered the operator's check, and False with the reason NoSecret, Refused, LimitReached, Unreachable, or Unavailable. LimitReached is a key the provider answered with its request limit, as OMDb does when a key has spent its calls for the day. The key is good, and the check asks again in an hour. Refused and LimitReached carry the provider's own words in the message. Unreachable is a check that got no answer at all, and its message is the error the check read. Unavailable is a check the provider answered with a status that says nothing about the account, and its message names that status code. For an imdb block, the check sends one HEAD request for each file, and Unavailable names the file. An imdb block also carries two conditions that do not change Ready. Stale is True with the reason NotReplaced when IMDb has not replaced a file for more than three days, and its message names the file and its date. Cached is True with the reason PerNodeClass when the claim <provider>-datasets holds the files, and False with the reason NoPerNodeClass when the cluster has no per-node class, so each run reads the files from IMDb.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="statusconditions--type"></span>`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])$`. |
| <span id="statusconditions--status"></span>`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`. |
| <span id="statusconditions--observedgeneration"></span>`observedGeneration` | integer | no | The metadata.generation that this condition reflects. |
| <span id="statusconditions--reason"></span>`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_])?$`. |
| <span id="statusconditions--message"></span>`message` | string | no | The same reason, as a sentence for a person to read. |
| <span id="statusconditions--lasttransitiontime"></span>`lastTransitionTime` | string | yes | When the status last changed. A change of the reason or the message alone does not move it. |

