Furry schedule adapter
The adapter reads one edition from the API and serves that edition's schedule as a Furry Schedule Schema document. Run it if something you use already consumes that format, such as a convention app or a site built around it. If you are writing the consumer yourself, read the schedule from the API directly instead; Showing the schedule covers that.
It is a separate program with its own image, chart and version line, and it only reads: nothing it does changes anything in Eventail.
Before you start
- A running Eventail 0.2.0 or newer the adapter can reach. It is usually on the same network and not exposed.
- A client credentials client at your sign-in provider, set up as described in Sign-in provider. The adapter signs in as itself, the same way any integration does.
- The ID of the edition to publish. Its settings page in the web app shows it, with a button that copies it.
Docker Compose
The Compose setup ships the adapter behind a profile, so it stays out of the way until you ask for it. Fetch its env file next to the others:
base=https://raw.githubusercontent.com/eventail-scheduling/eventail-deploy/main/compose
curl -fsSLO "$base/furry-schedule-adapter.env.example"
cp furry-schedule-adapter.env.example furry-schedule-adapter.envFill in the edition and the client, then start it alongside the rest:
docker compose --profile furry-schedule-adapter up -dThe document is published on 127.0.0.1:8081 by default, which FURRY_SCHEDULE_ADAPTER_PORT in .env changes. Put it behind the same reverse proxy that terminates TLS for the API and the web app.
compose.yml sets the API's address and the token cache path itself, because both follow from the Compose setup rather than from your deployment.
Kubernetes
The adapter has its own chart, versioned separately from the Eventail chart. Create a Secret holding the client secret, then install:
kubectl create secret generic furry-schedule-adapter --namespace eventail \
--from-literal=clientSecret=...
helm install furry-schedule-adapter \
oci://ghcr.io/eventail-scheduling/charts/eventail-furry-schedule-adapter \
--namespace eventail --values values.yamlA minimal values.yaml:
eventail:
baseUrl: http://eventail-api
editionId: 00000000-0000-0000-0000-000000000000
auth:
issuer: https://id.example.com/realms/eventail
clientId: furry-schedule-adapter
existingSecret: furry-schedule-adapter
document:
language: enNothing is exposed by default. To publish the document, set the hostnames and enable whichever router your cluster uses:
hostnames:
- schedule.example.com
ingress:
enabled: true
className: nginx
tls:
- secretName: schedule-tls
hosts:
- schedule.example.comWith Gateway API, name the Gateway to attach to instead:
hostnames:
- schedule.example.com
httpRoute:
enabled: true
parentRefs:
- name: public
namespace: gatewaysThe Gateway holds the listeners and their certificates, so TLS is configured there rather than in these values. Both routers may be enabled at once while you move between them.
The access token is cached on a PersistentVolumeClaim so a restart does not mint another one. Providers meter these, and an emptyDir would lose the cache every time the pod restarts.
A plaintext auth.issuer is refused unless it is on loopback or you set eventail.auth.allowInsecureIssuer, which is for a provider inside the cluster with no TLS to terminate. It also drops the HTTPS requirement for the request that carries the client secret.
What it serves
The document is at /schedule.json. The adapter fetches the schedule at startup and polls for changes after that, so each response is a complete snapshot of what is published.
Until the first poll succeeds, /schedule.json answers 503 rather than an empty document, and it does so again if the schedule goes too stale to serve. Both responses say why. /health answers 200 whenever the process is running, stale or not, so an upstream outage does not restart the container.
Rooms carry the venue they sit in, taken from the edition, so a convention running in two buildings comes out as two venues. An Eventail older than 0.2.0 does not offer the venue include at all, so it refuses every request the adapter makes and /schedule.json keeps answering 503.
Membership levels are off until you name the question that holds them. Ask sessions a choice question whose options are the levels you offer, then set document.membershipCustomFieldKey to its external key. The options become the document's membership levels as soon as a session has answered, and each session's answer says which ones its event is open to. A session that answers nothing is open to everyone.
Configuration
Set each one as an environment variable or in a file the container reads, /app/config/local.toml or /app/config/local.json. Environment variables override the file. An environment variable's name is the setting's path in upper case, with underscores between words and between path parts: eventail.auth.clientId becomes EVENTAIL_AUTH_CLIENT_ID. An empty variable still counts as set. The Setting column shows the variable's name on its second line.
Durations use ISO 8601 notation, such as PT30S or P7D. Intervals accept time units only: PT24H works, P1D does not.
general
| Setting | Type | Default | Description |
|---|---|---|---|
portPORT | integer, 1 to 65535 | 3000 | The port the document is served on. |
log
Logging to standard output: JSON lines when NODE_ENV is production, readable text otherwise.
| Setting | Type | Default | Description |
|---|---|---|---|
log.levelLOG_LEVEL | one of trace, debug, info, warn, error, fatal | info |
eventail
Where the schedule is read from.
| Setting | Type | Default | Description |
|---|---|---|---|
eventail.baseUrlEVENTAIL_BASE_URL | URL | Required | The root of the eventail API. A trailing slash is ignored. The access token is sent here, so reach the API over https or over a network you trust. Plaintext is not refused, because an in-cluster address is the normal case and no rule separates one from a public host. |
eventail.editionIdEVENTAIL_EDITION_ID | string | Required | The edition whose schedule is published. |
eventail.pollIntervalEVENTAIL_POLL_INTERVAL | ISO 8601 duration | How often the published schedule is checked for a new revision outside the convention itself. An ISO 8601 duration in time units only. Examples: PT5M, PT1H | |
eventail.livePollIntervalEVENTAIL_LIVE_POLL_INTERVAL | ISO 8601 duration | How often it is checked from the day before the convention opens until it closes, when a schedule moves far more than it does the rest of the year. An ISO 8601 duration in time units only. Examples: PT30S | |
eventail.requestTimeoutEVENTAIL_REQUEST_TIMEOUT | ISO 8601 duration | How long a single request may take, to the eventail API and to the identity provider alike. An ISO 8601 duration in time units only. Examples: PT10S |
eventail.auth
The client credentials the adapter authenticates with. The API must accept the resulting token as an integration; any other kind of caller reads more, not less, and would publish unconfirmed sessions.
| Setting | Type | Default | Description |
|---|---|---|---|
eventail.auth.issuerEVENTAIL_AUTH_ISSUER | URL | Required | The OpenID Connect issuer to mint access tokens from. Its discovery document is fetched at startup, so the adapter does not start while the issuer is unreachable, and the document must name this same issuer back. |
eventail.auth.clientIdEVENTAIL_AUTH_CLIENT_ID | string | Required | The client credentials client the adapter authenticates as. |
eventail.auth.clientSecretEVENTAIL_AUTH_CLIENT_SECRET | string | Required | Supply this through the environment rather than a config file: EVENTAIL_AUTH_CLIENT_SECRET. Anyone holding it can mint tokens with the adapter's access. |
eventail.auth.scopeEVENTAIL_AUTH_SCOPE | string | Sent with the client credentials request when a provider needs one to issue a token carrying the right claims. | |
eventail.auth.audienceEVENTAIL_AUTH_AUDIENCE | string | Sent with the client credentials request. Auth0 needs it to issue a token for the API; a provider that maps the audience on the client instead, as Keycloak does, needs it left out. Either way it has to end up matching the API's jwt.audience. | |
eventail.auth.allowInsecureIssuerEVENTAIL_AUTH_ALLOW_INSECURE_ISSUER | boolean | false | Accept a plaintext issuer that is not on loopback. Needed only for a provider reached inside a cluster or a compose network, where there is no TLS to terminate. It also tells openid-client to drop its HTTPS rule for every request to that provider, including the one carrying the client secret. |
eventail.auth.tokenCachePathEVENTAIL_AUTH_TOKEN_CACHE_PATH | string | A file to hold the access token between restarts, created 0600. Left out, the token lives only in memory and every start mints a new one, which some providers meter. The entry is keyed by the credentials, so editing other settings reuses it. The adapter refuses to start if it cannot write here. Examples: /var/cache/adapter/token.json |
document
What the emitted document says.
| Setting | Type | Default | Description |
|---|---|---|---|
document.languageDOCUMENT_LANGUAGE | string | Required | The language tag every localized string is keyed by. eventail stores one language per edition without naming it, so the tag is stated here. Case is normalized. A tag beyond a language and a region is accepted but warned about, since a consumer matching the pattern the schema names would not find the text. Examples: en, de, en-GB |
document.descriptionSourceDOCUMENT_DESCRIPTION_SOURCE | one of abstract, description | abstract | Which session field becomes the event description. Both are optional per edition, so an edition that collects only one has to name it. |
document.membershipCustomFieldKeyDOCUMENT_MEMBERSHIP_CUSTOM_FIELD_KEY | string | The external key of the choice question asked of sessions whose options are the membership levels, and whose answer says which ones an event is open to. Single and multiple choice both work. Left out, the document names no membership levels and restricts no event, and a key no session question carries does the same. Examples: membership | |
document.maxStalenessDOCUMENT_MAX_STALENESS | ISO 8601 duration | How long the last document keeps being served once refreshing it stops working, measured from the last refresh that succeeded rather than the last attempted. Past it the document is refused rather than served stale, which is the only signal a consumer gets. An ISO 8601 duration in time units only. Examples: PT6H, PT24H |
source
Identifies this publisher to whoever consumes the document.
| Setting | Type | Default | Description |
|---|---|---|---|
source.nameSOURCE_NAME | string | eventail | |
source.vendorIdSOURCE_VENDOR_ID | string |