Writing environment-specific documentation#
There are multiple science platforms deployed in different locations for different purposes. Each science platform instance is a separate Phalanx environment. A separate version of this documentation is built for each science platform environment and deployed using LSST the Docs’s editions feature. The documentation corresponding to the primary, public-facing science platform is always deployed as the main edition at the root URL (https://rsp.lsst.io). Other editions are available from https://rsp.lsst.io/v and the Science Platform homepages of each environment also link directly to these editions.
Information about the RSP environments is fetched at build time from the per-environment Repertoire service-discovery data that Phalanx publishes at https://phalanx.lsst.io/discovery/environments/{env}.json.
A small supplementary shim in the documentation repository (src/rspdocs/discovery/environments.json) provides the handful of things discovery doesn’t cover, such as human-readable environment titles and the roster of environments to build.
When the network is unavailable, the build falls back to a local cache (in _build/discovery/) and announces the fallback in the build output.
This page describes the supported approaches for writing documentation that differs between environments. Reach for them in this order, from the most targeted to the most general:
Linking to RSP services with roles for inline URLs and links to RSP services.
Using reStructuredText substitutions for env-specific words and short phrases that a role can’t produce.
Conditional content with the rsp-only directive to include or exclude whole blocks of content per environment.
Using Jinja templating when a branch’s text must be computed from environment data (interpolation, loops, or expressions).
Using source file includes (*.in.rst) with rsp-only and jinja to swap large included files.
Leaving a whole page out of some environments to leave a whole page out of some environments: conditionalize its toctree entry and exclude its source so it isn’t flagged as an orphan.
The roles and the rsp-only directive are provided by the in-repo src/rspdocs/sphinxext Sphinx extension.
The homepage (docs/index.rst) and log-in (docs/guides/getting-started/get-an-account.rst) pages demonstrate these techniques.
Linking to RSP services with roles#
For an inline link or URL to an RSP service, use one of these two roles. Each takes a service name — listed in the table below — and resolves it to that service’s URL for the environment being built.
rsp-url:rsp-url:`service`renders the service’s URL as a code literal. For example,:rsp-url:`rsp`renders ashttps://data.lsst.cloud/.rsp-link:rsp-link:`service`renders a hyperlink to the service whose link text is the URL itself. For example,:rsp-link:`rsp`renders as https://data.lsst.cloud/.Give an explicit link title with the familiar
title <target>syntax::rsp-link:`Rubin Science Platform <rsp>`renders as Rubin Science Platform.
Some services aren’t deployed in every environment; targeting a service that is absent in the environment being built raises a warning (fatal under -W), which usually means the reference should be wrapped in a matching rsp-only block.
Name |
Service |
In every environment? |
|---|---|---|
|
RSP homepage |
yes |
|
Portal Aspect |
no |
|
Notebook Aspect |
no |
|
VO API root |
no |
|
TAP service |
no |
|
WebDAV service |
no |
|
Times Square |
no |
|
Access-token page |
yes |
|
Phalanx environment docs |
yes |
Using reStructuredText substitutions#
For env-specific prose — a word, a name, or a derived path that a role can’t produce — use reStructuredText substitutions.
These substitutions are defined in rst_epilog.rst.jinja, which is itself templated with Jinja so the replacement text can vary by environment.
Syntax |
Example |
|---|---|
|
|
|
data.lsst.cloud |
|
|
|
|
|
|
|
|
Prefer a role whenever you only need a service’s URL or a link to it — the roles replaced the per-service URL and link substitutions the docs used to define.
Conditional content with the rsp-only directive#
To include a block of content only in certain environments, wrap it in the rsp-only directive:
.. rsp-only:: primary
This content appears only in the primary (public) build.
The directive takes one or more bare condition tokens, which may be:
a service name (from the table above) — true where that service is deployed;
an environment name (
base,idfdev,idfint,idfprod,summit,tucson-teststand,usdfdev,usdfprod) — true only in that environment; orthe keyword
primary— synonymous withidfprod, the primary environment, whose documentation is the default edition.
By default every token must hold (logical AND):
.. rsp-only:: portal nublado
This content appears only where both the Portal and Notebook aspects exist.
Use the :any: option for a logical OR, and the :not: option to negate the result:
.. rsp-only:: summit base
:any:
This content appears in the summit or base environments.
.. rsp-only:: primary
:not:
This content appears in every environment except the primary one.
For an “AND of an OR” condition, nest rsp-only directives.
Unlike Sphinx’s built-in only directive, rsp-only excludes non-matching content at parse time: excluded content never enters the doctree, so it can’t leak into the table of contents, the index, or search results.
This is safe here because each environment is built separately (one sphinx-build per environment), so rsp-only is the right tool for conditional content — there’s no need to avoid it the way you would avoid only.
Using Jinja templating#
The rsp-only directive only includes or excludes static reStructuredText.
Reach for the jinja directive — available through sphinx-jinja — when a branch’s text has to be computed rather than merely shown or hidden: interpolating an expression like {{ env.title }}, running a loop, or testing an env attribute that isn’t a service name, an environment name, or primary.
The deciding question is not how many branches you have, but whether the prose inside a branch depends on a value — and one that no rsp-url/rsp-link role or substitution already provides.
If every branch is self-contained rST, prefer stacking rsp-only blocks; use jinja only when a branch needs to say something built from the environment’s data.
For example, a three-way switch whose branches are worded differently and each interpolates an environment value that has no substitution of its own (here the short title env.title and the env.domain host):
.. jinja:: rsp
{% if env.is_primary %}
The public Science Platform is served at {{ env.domain }}.
{% elif env.name in ("idfint", "idfdev") %}
{{ env.title }} is a staff integration environment.
{% else %}
{{ env.title }} runs at {{ env.domain }} for internal use.
{% endif %}
The argument to the jinja directive is always rsp.
Inside it, env is the environment being built (an instance of rspdocs.discovery.models.PhalanxEnv, so any of its attributes are available), and all_envs maps environment names to their PhalanxEnv.
As with any Sphinx directive, indent the content consistently with respect to the directive’s scope, as shown above.
Two things to keep in mind:
For a simple two-way include/exclude, prefer Conditional content with the rsp-only directive: it reads more clearly and keeps excluded content out of search and the table of contents.
Each build fetches only the target environment and the primary environment (for speed), so
all_envsholds just those one or two entries — a{% for %}loop over it will not enumerate all eight environments.
Using source file includes (*.in.rst) with rsp-only and jinja#
Both the rsp-only and jinja approaches work well for tailoring specific paragraphs for different environments, but writing a large amount of content inside a directive is inconvenient.
To customize large portions of text, you can combine the rsp-only directive (or a jinja directive for multi-way switches) with the include directive:
.. rsp-only:: primary
.. include:: the-page.primary.in.rst
.. rsp-only:: primary
:not:
.. include:: the-page.notprimary.in.rst
This inserts content from the included source files, either the-page.primary.in.rst or the-page.notprimary.in.rst.
Those included files are in the familiar reStructuredText syntax (you shouldn’t need further Jinja syntax within them, though you can certainly use roles and substitutions).
The included files must have a .in.rst suffix so that the Sphinx build won’t incorporate those files as separate pages.
Our further convention is to prefix the name with the root name of the page, followed by a description of the environment or context where the content applies.
Leaving a whole page out of some environments#
Some pages document a single service end-to-end — for example, the Times Square guides or the “using TOPCAT outside the RSP” page — and shouldn’t appear at all in an environment where that service isn’t deployed.
Leaving such a page out of a build is a two-part change: conditionalize its toctree entry, and exclude its source file.
Both parts are necessary, and they must agree on the gating service.
Conditionalize the toctree entry#
A page appears in a build only if a toctree links to it, so the first step is to drop the page’s toctree entry in the environments that shouldn’t have it.
Wrap the entry in a jinja conditional on the gating service’s URL attribute.
For instance, docs/guides/auth/index.rst lists the TOPCAT page only where the TAP service exists:
.. jinja:: rsp
.. toctree::
:titlesonly:
creating-user-tokens
token-scopes
{% if env.api_tap_url %}using-topcat-outside-rsp{% endif %}
The same pattern gates an entire subtree by its index page — docs/guides/index.rst includes times-square/index (and everything under it) only when env.times_square_url is set.
Exclude the now-unreferenced source#
Conditionalizing the toctree stops linking the page, but its source file still sits in docs/.
Sphinx flags any document that no toctree reaches as an orphan (document isn't included in any toctree), which is a warning — and the build runs under -W, so a stray orphan fails the build.
The fix is to exclude the page’s source in exactly the environments where its toctree entry disappears, so Sphinx never parses it and never sees an orphan.
These exclusions live in src/rspdocs/sphinxext/page_excludes.yaml.
The file maps each service token to a list of glob patterns, relative to the docs/ source directory, for the pages that document that service:
times-square:
- guides/times-square/*.rst
- guides/times-square/**/*.rst
tap:
- guides/auth/using-topcat-outside-rsp.rst
When an environment is built, the patterns for every service absent from that environment are added to Sphinx’s exclude_patterns.
A service counts as absent when discovery reports no URL for it (for example, TAP in an environment that serves no datasets) or when it’s listed in that environment’s hidden_services shim (see src/rspdocs/discovery/environments.json) — the same condition the toctree’s {% if env.… %} tests, which is why the two stay in sync.
To leave a page out where a service is missing:
gate its
toctreeentry on the service’s URL attribute, as above; andadd its source path (or a glob for its subtree) under the matching service token in
page_excludes.yaml.
When excluding an index page that heads a subtree, exclude the subtree too (the ** glob above), or its child pages become orphans in turn.
The YAML keys must be recognized service tokens from the service table; a typo fails the build rather than silently excluding nothing.
This and the other hand-edited bundled config (src/rspdocs/discovery/environments.json) are checked by the rspdocs-validate-config command, run automatically by pre-commit and the test suite, so a mistake is caught at commit time rather than only during a build.