Why prerequisite packages accept name or names, and how id groups alternatives
PackagePrerequisites (colonel-byte.dev/v1alpha1) allows each entry under requires.packages to specify either a single package name or a list of acceptable names. This records why names exists beside name, why they are mutually exclusive, and how grouping by id extends this to alternative packages that carry distinct version constraints.
Why names exists beside name
A prerequisite package often exists under multiple published names that provide identical cluster capability. The canonical example is upstream Zarf’s init package versus rebuilds such as init-slim: a downstream package requiring an initialized cluster can run on either.
The expressive alternative – stating capabilities in provides and requiring a capability – cannot be evaluated on cluster because Kubernetes and zarf package list track package names, versions, and components, not PackagePrerequisites documents. The name is the only token zarf_package_info can read from the cluster without out-of-band package inspection.
Declaring separate requires.packages entries for each alias does not work:
- Multiple requirements would mean all of them must be present, when the author means any of them satisfies the requirement.
- Attaching version constraints across multiple alternative requirements causes duplicate constraints that drift when one is updated and another is forgotten.
nameskeeps the version constraint bound to the requirement itself, applying whichever matching package is found on the cluster.
Why oneOf mutual exclusivity, not precedence or anyOf
A requirement that carries both name and names creates ambiguity:
- Under
anyOf, a document with both keys is valid schema, forcing consumers to invent precedence rules. If the role readsnamesfirst, an author writingname: cluster-initandnames: [init, init-slim]would havenamesilently ignored. - Under
oneOf, a document declaring both keys or neither key is rejected immediately at schema validation time.
Because playbooks can pass prerequisite documents directly to the prerequisites role without running schema validation first, the role repeats this check explicitly in roles/prerequisites/tasks/collect.yml:
- It asserts that no package entry defines both
nameandnames. - It asserts that no package entry defines neither
namenornames. - It asserts that
namesis non-empty andnameis non-empty (minLength=1andminItems=1).
How the role evaluates them today
In collect.yml, the role immediately normalizes every entry into an internal names list:
'names': item.names | default([item.name | default('', true)], true) | select() | list
This normalization means downstream task logic (check_resources.yml and check_versions.yml) only handles a single list structure:
- Resource checks verify that at least one name in
namesappears in deployed packages on the cluster. - Version checks apply the constraint against whichever matching package name is deployed.
Grouping alternatives with distinct versions via id
The existing names: [a, b] mechanism binds a single version constraint across all names in that list. When alternatives have different version histories – such as two distinct storage providers satisfying a common requirement:
requires:
packages:
- name: csi-rook-ceph
version: ">=1.21.0"
id: storage
- name: csi-longhorn
version: ">=1.13.0"
id: storage
A single version field under names: [csi-rook-ceph, csi-longhorn] cannot express >=1.21.0 for Rook Ceph and >=1.13.0 for Longhorn simultaneously.
Why id instead of requires.capabilities
provides.capabilities exists in the format for bundlers and orchestrators that inspect package documents ahead of time. However, on a running cluster, zarf package list only reports deployed package names and versions; it does not read or query capability declarations. id solves this by grouping concrete package names that can be checked directly against the cluster’s installed package inventory, while keeping separate version ranges per package.
Semantics of id grouping
- Local scope:
idis scoped strictly withinrequires.packages. It groups alternative entries in that list and does not collide with other requirement types. - Group satisfaction: An
idgroup represents a disjunction (OR). A group is satisfied if at least one branch in the group has its package deployed on the cluster and satisfies its specific version constraint. - Handling multiple installed packages: If multiple packages from the same
idgroup are deployed on the cluster (e.g. both Rook Ceph and Longhorn are present), the requirement is satisfied if at least one satisfies its constraint. An obsolete or failing sibling does not fail the group if another branch meets its constraint. - Ungrouped requirements: Entries without an
idremain independent requirements that must each be present on the cluster (the existing behavior). - Structure per entry: Each entry in an
idgroup may use eithernameornames. For example, an entry in the group can still usenames: [csi-rook-ceph, csi-ceph]if a specific branch has aliases sharing a single version constraint. - Schema & Go types:
ID string(minLength=1) is an optional field onPackageRequirement. The existingoneOfbetweennameandnamesremains intact. - Error reporting: If no branch in an
idgroup is satisfied, the failure is reported as a single grouped unmet prerequisite naming all candidate packages and their constraints, rather than separate missing package failures.