Profile bundles
What a profile's directory becomes when the catalog is published, and what it may hold. For profile authors. The cluster's side of the same rules is in gentian-os, docs/guides/custom-catalogs.md.
One profile, one file, one fingerprint
A cluster installs an app from one file, profiles/<name>.yaml of the published catalog, at one
digest. That file is the profile's bundle: the ComponentProfile first and, after it, every
other object the app needs on a cluster — its companions. The digest in index.yaml is the
sha256 of the whole file, so an install is pinned to the profile and to everything that travels with
it.
A profile with no companions is published byte for byte as its profile.yaml.
Nothing else from a profile's directory reaches a cluster. There is no other path: what is not in the bundle is not there.
The sources
Everything stays in the profile's directory, profiles/[<family>/]<name>/:
| File | Becomes |
|---|---|
profile.yaml | the bundle's first document, unchanged |
composition.yaml | a Composition companion |
oidc-catalog.yaml | an OIDCPackCatalog companion |
customizations/<record>.yaml | a Customization companion |
files under assets/, named by a configMapGenerator | a ConfigMap companion |
*.md, assets/tile.png, anything not listed | nothing; it stays in the repository |
The catalog listing — display name, summary, description, categories, links, author, license,
logo — is the App Store's and lives in its database, not here. A profile carries what installing
the app needs, and its tile: the PNG inlined in tile.logo, which assets/tile.png is the
committed copy of.
kustomization.yaml is the list. scripts/build-catalogue-source.py reads it and puts into the
bundle exactly what it names:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- profile.yaml
- customizations/sign-in-sidecar.yaml
configMapGenerator:
- name: openproject-ce.sign-in-handler
files:
- handler.js=assets/sign-in-handler.js
generatorOptions:
disableNameSuffixHash: true
labels:
gentianos.io/profile-name: openproject-ce
gentianos.io/asset: sign-in-handler
The file is read, not run: only resources, configMapGenerator (name and files) and
generatorOptions.labels mean anything, and the build fails on any other key. Each file under
resources holds one object and goes in unchanged, comments included. Companions follow the
profile ordered by kind and then by name, so the same tree always gives the same bytes and the same
digest. kubectl kustomize <dir> still renders the directory, and CI still runs it.
The family's AppPackage presets (profiles/<family>/packages/) are not companions. Nothing on a
cluster reads a preset; they are published under packages/ for the App Store.
The platform a build needs
A profile declares the oldest platform version line it runs on, as a floor:
spec:
requires:
platform: ">=0.5"
The catalog's index repeats it beside the digest, so a cluster on an older line lists the entry
with the reason and refuses to install it, and scripts/validate-platform-schema.py holds every
bundle to the schema of each line its floor admits (contracts/platform/<line>/). No upper bound is
declared: what a later platform stops reading, the platform says, one line ahead. The profile's own
spec.version is the build's number, bumped on every change to the bundle
(scripts/validate-profile-versions.py); on develop it carries the -dev suffix of the next-dev
model, which a release from main removes.
What a bundle may hold
Four kinds, each with the one name it may have. The cluster refuses the whole bundle for anything else, and the build refuses it first.
| Kind | Name | Also |
|---|---|---|
Composition | app-<profile>, at most one | composes XApp; spec.package.composition in the profile names it |
OIDCPackCatalog | <profile>-oidc, at most one | every pack is keyed by a clientId or oidcPackRef the profile states; no serviceClient |
ConfigMap | <profile>.<asset> | label gentianos.io/asset: <asset>; text files only |
Customization | <profile>.<record> | spec.target.profile is the profile; spec.scope: profile |
Every companion:
- carries the label
gentianos.io/profile-name: <profile>and no other label (a ConfigMap: that andgentianos.io/asset); - states no namespace. A ConfigMap or a Customization is applied where the cluster applies its catalog. A composition that reads a companion finds it by its labels, never by a namespace;
- states no annotations, owner, finalizer or status.
The bundle may be at most 180 KiB.
A companion in one bundle is that profile's alone. An add-on that needs what its base brings uses the base's: an add-on is active only inside its base, whose bundle is on the cluster with it.
Who may bring companions
A catalog that a cluster's administrator added for the whole cluster. This catalog is one. A
bundle served from a tenant's own catalog is its profile and nothing else — a profile written for
such a catalog must work with app-default and without an OIDC pack.
A Composition creates objects with the rights of the cluster's providers. An administrator who
adds this catalog trusts every Composition in it as they trust the platform's own; treat a change
to a composition.yaml accordingly.
Checking a bundle
python3 scripts/build-catalogue-source.py --check # every bundle, against the rules above
python3 scripts/build-catalogue-source.py --out dist/catalogue
The second leaves the published files in dist/catalogue/profiles/. gentian-os tests copies of
these against the director's and the operator's own checks
(internal/profilebundle/testdata/bundles); when a bundle's companions change shape, refresh them
there.