-
Notifications
You must be signed in to change notification settings - Fork 484
Distribution Support
Distribution Support gives OBS a first-class way to describe the Linux distributions an instance knows about, replacing a flat admin-curated list with a hierarchy that distribution maintainers own themselves. The hierarchy has three levels — Vendor, Distro, DistroRelease — anchored to a Project at the top and terminating in the RepositoryArchitecture records that a release is actually built from.
The whole feature sits behind the :enhanced_distribution_support feature toggle.

OBS already has a Distribution model, and the new hierarchy deliberately does not reuse or extend it. The two solve different problems:
Distribution (legacy) |
Distribution Support | |
|---|---|---|
| Shape | Flat — vendor is a string column on each row |
Three linked models |
| Ownership | Admin-only (DistributionsController restricts create/update/destroy) |
Project maintainers, via the policy chain |
| Scope | Instance-global catalog | Rooted in a Project
|
| Purpose | Populates the "Add repository" wizard | Describes what a vendor ships, and what it is built from |
Because a legacy Distribution carries its vendor as a bare string, there is nowhere to hang releases, no identity for a vendor, and no way for anyone but an admin to curate it. The new models are additive — the legacy table and the repository wizard are untouched.
Note the naming: the new model is Distro, not Distribution, precisely so the two can coexist. There is no namespace involved; the class names are distinct.
The organisation that publishes distributions — openSUSE, SUSE, Debian.
A Vendor belongs to exactly one Project, and a Project has at most one Vendor (has_one :vendor, dependent: :destroy). That project is the vendor's home in OBS and the source of all authorization for the subtree beneath it. The one-to-one is enforced by a UNIQUE index on vendors.project_id.
Fields: name, description, url.
belongs_to :project, optional: false
has_many :distros, dependent: :destroyA product line published by a vendor — Leap, Tumbleweed, SLES, Debian GNU/Linux.
Names are unique per vendor, not globally, so two vendors may both ship something called "Micro". Enforced both in the model and by a composite UNIQUE index on (vendor_id, name).
Fields: name, description, url.
belongs_to :vendor, optional: false
has_many :distro_releases, dependent: :destroy
has_one :project, through: :vendorA specific shipped version of a distro — Leap 15.6, SLES 15 SP6, Debian 13 "Trixie".
Names are unique per distro, via (distro_id, name). Fields: name, description, url.
belongs_to :distro, optional: false
has_many :distro_release_repository_architectures, dependent: :destroy
has_many :repository_architectures, through: :distro_release_repository_architectures
has_many :repositories, through: :repository_architectures
has_many :projects, through: :repositoriesThe join model that says what a release is actually made of. A release links to many RepositoryArchitectures, and one RepositoryArchitecture may serve many releases. A UNIQUE index on (distro_release_id, repository_architecture_id) keeps the pairing from being recorded twice.
This is the central modelling decision, and it is easy to get wrong in either direction.
Not projects. The obvious framing — "a release is a set of projects" — cannot express reality, because projects are shared across releases. openSUSE:Maintenance feeds several Leap versions simultaneously. Given only a project, there is no way to say which release a given piece of content belongs to, so any per-release query built on projects returns answers that are silently wrong.
Not repositories alone. Repository grain fixes that, but a repository is still a set of architectures, and a release does not necessarily ship all of them. A repository may build x86_64, aarch64 and ppc64le while the release in question only ships the first two.
RepositoryArchitecture is the finest grain that is still a real, existing OBS object — it is the row that a build actually targets. Anchoring there means the composition of a release is exact, and the coarser views come for free by traversal.
repositories and projects on DistroRelease are conveniences reached through the join. If a release links three architectures of the same repository, that repository appears three times:
release.repositories.count # => 3, for one repository
release.repositories.distinct.count # => 1The same applies to projects, compounded — a project with two repositories at three arches each yields six rows. Always add .distinct when you want the set rather than the multiset, and treat both associations as read-only views; writes belong on repository_architectures.
Every policy delegates upward, so permission is decided once, at the project:
DistroReleasePolicy#create? → DistroPolicy#create? → VendorPolicy#create? → ProjectPolicy#update?
new?, update? and destroy? all alias create? at each level. The practical rule: if you can modify the project, you can manage its entire distribution tree. There is no separate vendor-maintainer role.
Reads are public — VendorPolicy#show? and DistroPolicy#show? both return true. DistroReleasePolicy has no show? because releases have no show action; they are rendered inside the distro page.
All three controllers use after_action :verify_authorized, and VendorsController#show calls skip_authorization when the project has no vendor yet, since there is no record to authorize.
Constraints are declared at both layers — model validation for usable error messages, database index for the actual guarantee.
| Table | Unique index | Foreign keys |
|---|---|---|
vendors |
(project_id) |
→ projects
|
distros |
(vendor_id, name) |
→ vendors
|
distro_releases |
(distro_id, name) |
→ distros
|
distro_release_repository_architectures |
(distro_release_id, repository_architecture_id) |
→ distro_releases, repository_architectures
|
Deletion cascades through dependent: :destroy at every level, so destroying a vendor removes its distros, their releases, and the join rows — but never touches the RepositoryArchitecture records themselves, which belong to the repositories.
- Development Environment Overview
- Development Environment Tips & Tricks
- Spec-Tips
- Code Style
- Rubocop
- Testing with VCR
- Test in kanku
- Authentication
- Authorization
- Autocomplete
- BS Requests
- Events
- ProjectLog
- Notifications
- Feature Toggles
- Build Results
- Attrib classes
- Flags
- The BackendPackage Cache
- Maintenance classes
- Cloud uploader
- Delayed Jobs
- Staging Workflow
- StatusHistory
- OBS API
- Owner Search
- Search
- Links
- Distributions
- Distribution Support
- Repository
- Data Migrations
- Package Versions
- Rails Update
- Ruby Update
- Updating-rake-or-rack
- Brakeman
- Run OpenQA smoketest locally
- Factory Dashboard
- Responsive Guidelines
- osc
- Importing database dumps
- Problem Statement & Solution
- Kickoff New Stuff
- Mob-Programming Sessions
- New Swagger API doc
- Documentation and Communication
- GitHub Actions
- Setup an OBS Development Environment on macOS
- Remote Pairing Setup Guide
- How to Introduce Software Design Patterns
- Query Objects
- Services
- View Components
- RFC: Core Components
- RFC: Decorator Pattern
- RFC: Backend models
- RFC: Hotwire Turbo Frames Pattern