Skip to content

Add deb repository and remote pages - #347

Merged
ggainey merged 10 commits into
pulp:mainfrom
warisshaikh1:deb-repositories-and-remotes
Sep 18, 2026
Merged

ggainey merged 10 commits into
pulp:mainfrom
warisshaikh1:deb-repositories-and-remotes

Conversation

@warisshaikh1

@warisshaikh1 warisshaikh1 commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

Addresses part of #277 (deb in the side menu).

Draft on purpose — I asked three scoping questions in #277 and haven't heard back yet. This is here so there's something concrete to react to rather than to pre-empt the answers. Happy to cut it down, extend it, or restructure it entirely.

What this adds

A Pulp deb menu section gated on hasPlugin('deb'), with Repositories and Remotes, mirroring Pulp file:

list detail edit tabs
remotes/deb/apt yes yes yes details
repositories/deb/apt yes yes yes details, versions, distributions

What it deliberately leaves out

  • Publications. deb has two publication endpoints rather than a field: publications/deb/apt generates fresh metadata and needs a signing service to be consumable, while publications/deb/verbatim republishes upstream's Release/InRelease byte for byte so an ordinary ubuntu-archive-keyring verifies it. A publications tab has to either show both or ask which to create, which is a design decision rather than wiring, so it seemed better to agree it first.
  • Content browsing. content/deb/packages is the useful one, but there are a dozen deb content endpoints where rpm has one. Easy follow-up once the shape here is agreed.

The one place deb genuinely differs from file

RemoteForm needed the APT fields, which have no equivalent elsewhere: distributions (suites), components, architectures, gpgkey, and the sync_sources / sync_udebs / sync_installer switches.

distributions is required, for deb only. Every other plugin's remote needs just a URL; pulp_deb rejects one without suites:

POST /pulp/api/v3/remotes/deb/apt/
{"name":"x","url":"http://deb.debian.org/debian/","tls_validation":true,"download_concurrency":10}
-> 400 {"distributions": ["This field is required."]}

So requiredFields becomes conditional. gpgkey reuses the FileUpload treatment ca_cert and client_cert already get, since it's an armoured key file rather than a one-line value.

Everything else is registry entries — plugin2api, the plugin unions on LazyRepositories / LazyDistributions / RepositoryForm, and deb-only optional fields on the shared RemoteType. All additive; ansible, container and file behaviour is unchanged (the RemoteForm diff is worth a look on that point specifically).

Testing

Verified against a live pulpcore 3.116.0 / pulp_deb 3.10.0 mirroring Ubuntu noble, with the dev server proxied at it (API_PROXY):

  • every query the new pages issue returns 200 — remote and repository lists with ordering / name__icontains / pulp_label_select / remote filters, …/versions/, …/versions/?number=N, and distributions/deb/apt/?repository=<href>
  • the exact create payload DebRemoteEdit sends → 201; the smartUpdate PUT with all APT fields → 202, and the fields read back verbatim
  • the repository create plus auto-created distribution path → 201 / 202, and pluginRepositoryBasePath('deb', …) resolves the base_path the Repository URL field shows
  • test objects cleaned up afterwards, existing content untouched

npm run lint:js, lint:ts, lint:ls, the this-without-class check and npm run build are all clean (build warnings unchanged at 18, all pre-existing bundle-size ones). No CHANGES.md entry, since that's generated from PR titles at release.

Two things I'd flag for review

  1. A third copy of SyncModal. ansible-repository-sync.tsx and file-repository-sync.tsx already carry identical copies and I've followed suit rather than refactoring inside a feature PR. Happy to extract all three into a shared component, here or separately, if you'd prefer.
  2. mirror defaults to true in the sync modal, matching the other plugins, though pulp_deb's own API default is false. I kept UI consistency, but say the word and I'll flip it — mirroring is the destructive direction.

@bmbouter @himdel @dkliban @ipanova

@warisshaikh1
warisshaikh1 marked this pull request as ready for review September 1, 2026 09:51

@Redtigercod4 Redtigercod4 left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this - solid work overall, and I've left comments on the specific issues, tagged using MoSCoW. A couple of things I'd suggest:

  • Commit size: This is a large PR, 1,942 lines across 32 files submitted as a single commit. That makes it much harder for reviewers to follow, especially when trying to understand how the implementation came together. Smaller, sequential PRs would be preferred going forward.

  • Cypress Testing: As this is new UI functionality within PulpUI, could you please add corresponding test cases to cypress/e2e/smoke.js.

  • AI-assisted contributions: No issue either way, but if any AI tooling was used, please make sure the appropriate attributions is included, in line with Pulp's AI policy.

Ref:
https://github.com/pulp/.github/blob/cf562eff2ec6fe0f49bdfd693c3627453eef5203/profile/README.md

Comment thread src/actions/deb-repository-delete.tsx Outdated
Comment thread src/actions/deb-repository-sync.tsx
Comment thread src/containers/deb-remote/detail.tsx Outdated
Comment thread src/components/repository-form.tsx Outdated
Comment thread src/containers/deb-repository/tab-distributions.tsx Outdated
Comment thread src/api/deb-remote.ts Outdated
Comment thread src/api/deb-repository.ts Outdated
Comment thread src/api/deb-remote.ts Outdated
Comment thread src/containers/deb-repository/detail.tsx Outdated
@warisshaikh1
warisshaikh1 force-pushed the deb-repositories-and-remotes branch 2 times, most recently from 9772856 to 9ce27a7 Compare September 15, 2026 10:35
@warisshaikh1

Copy link
Copy Markdown
Contributor Author

Thanks for the review -- all nine comments are addressed, force-pushed and rebased on current main. Details are in the individual threads; the three broader points:

Cypress. Added Deb repositories and Deb remotes to cypress/e2e/smoke.js. Both reach the list and its empty state, and the remotes case clicks through to the create form and checks the APT-only fields render, since those are what the shared RemoteForm gained here. Green in CI.

Commit size. Point taken. I have not retro-split the original commit, since the pages only work once the API layer, routes and menu entry are all present, but the review fixes are eight separate commits, one per point, so the diffs should be easy to follow. Anything further I send will be scoped smaller from the start -- starting with the SyncModal extraction, as its own PR.

AI-assisted. Yes, AI-assisted throughout, and thanks for pointing at the policy. Every commit now carries an Assisted By: Cursor (Claude Opus 5) trailer, including the original, which I amended. Everything here has been reviewed by me, and the deb pages are running against a live Pulp with a mirrored Ubuntu archive behind them, which is where the required-field and duplicate-path behaviours in the original commit message came from.

ggainey pushed a commit that referenced this pull request Sep 17, 2026
ansible-repository-sync and file-repository-sync each carried a byte
identical copy of the modal, some ninety lines apiece, so the mirror and
optimize wording had to be changed in two places. Moved to
src/components/sync-modal.tsx as it stood; the only edit is the inline
prop type becoming an IProps interface, as the other components declare
theirs.

No behaviour change. Each action keeps its own permission check, its own
disabled reasons and its own sync call.

Refs #347
Assisted By: Cursor (Claude Opus 5)
@ggainey

ggainey commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

@Redtigercod4 are you happy that this meets your list of suggested changes?

Adds a "Pulp deb" menu section with Repositories and Remotes, mirroring
the structure of Pulp file: list, detail and edit for each, plus the
versions and distributions tabs on a repository.

Deliberately scoped to repositories and remotes. Publications are left
out because deb has two publication endpoints rather than a field --
publications/deb/apt generates fresh metadata and needs a signing
service, publications/deb/verbatim republishes upstream's Release byte
for byte -- so a publications tab needs a design decision rather than
just wiring. Content browsing is left out for the same reason: deb has
a dozen content endpoints where rpm has one.

RemoteForm gains the APT fields, which have no equivalent in the other
plugins: distributions (suites), components, architectures, gpgkey, and
the sync_sources/sync_udebs/sync_installer switches. `distributions` is
added to requiredFields for deb only -- pulp_deb answers a remote
without it with "This field is required.", unlike every other plugin
where url alone is enough. gpgkey reuses the FileUpload treatment the
certificate fields already use, since it is an armoured key file.

The remaining changes are registry entries: plugin2api, the plugin
unions on LazyRepositories/LazyDistributions/RepositoryForm, and the
deb-only fields on the shared RemoteType. All are additive; ansible,
container and file behaviour is unchanged.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)
The lookup asked for the first hundred and left a TODO for the rest, so
deleting a repository with more distributions than that would leave the
excess pointing at a repository that no longer exists.

Walk the pages until as many have been collected as `count` reports,
stopping early on an empty page so a disagreeing count cannot loop.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)
Opening the sync modal offered to mirror, which deletes local content the
remote no longer has. The API's own default is the non-destructive one and
the modal should agree with it, so the deletion is opted into.

The value was written out three times; it is now one constant, which is
also where the divergence from ansible and file is explained.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)
Both detail pages read results[0] straight out of the response, which
throws on any unexpected shape, and past page-with-tabs' catch that leaves
the page loading with nothing to show. Read the first result defensively
instead.

The comment claimed the API returns a 404; it returns 200 and an empty
list, and it is this code that synthesizes the 404.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)
Three entries tested the same tab and were filtered back out again when
the answer was no. It is one decision -- which crumbs the versions tab
adds -- so make it once.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)
Three chained ternaries picked the remote API by plugin, repeating the
same list call in each branch. plugin2api already maps a plugin to its
repository and distribution APIs, so add the remote API there and let the
form ask for it: one switch to extend when a plugin is added, not a
conditional to unpick.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)
The stub answered true to every permission. Nothing in this list is gated
on one, and an apt repository carries no my_permissions field for the
ansible-style check to read, so copying that check would answer no to
everyone instead.

Leave the permission out and say why, so an action added later gets a real
check rather than a stub that always agrees.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)
Both types hold property declarations only, with no constructor, no
methods and no instantiation, which is what an interface is for. Also
fixes a typo in the smartUpdate comment.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)
Reaches both lists and their empty states, then opens the remote form to
check the APT-only fields render, since those are what the shared form
gained for this plugin.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)
pulp#363 has landed, so drop the copy this branch was carrying and use the
shared component. The deb sync action goes from 154 lines to 64.

The one thing deb needs that ansible and file do not is the mirror switch
starting off, so the component takes a defaultMirror prop that defaults to
what the other two have always sent. Their behaviour is unchanged.

A boolean rather than a params object deliberately: the effect that resets
the switches has to depend on it, and an object prop would either lie
about that dependency or, passed as a literal, reset the switches on every
render.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)
@warisshaikh1
warisshaikh1 force-pushed the deb-repositories-and-remotes branch from 9ce27a7 to 159ea94 Compare September 18, 2026 09:44

@Redtigercod4 Redtigercod4 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the work on adding deb support and addressing the changes. I will speak to the core team regarding releasing this in the near future.

@warisshaikh1

Copy link
Copy Markdown
Contributor Author

Done -- #363 merged yesterday, so this branch is rebased on main and now uses the shared SyncModal. Its own copy is gone and the deb sync action drops from 154 lines to 64.

One addition to the shared component: deb wants the mirror switch starting off, since that is what pulp_deb defaults a sync to, so SyncModal takes a defaultMirror prop. It defaults to what ansible and file have always sent, so their behaviour is unchanged and neither call site needed touching.

It is a boolean rather than a sync-params object on purpose. The effect that resets the switches has to depend on whatever carries that default, and an object prop would either lie about that dependency or, passed as a literal at the call site, reset the switches on every render. If you would rather it were an object, or want the mirror default handled some other way, happy to change it.

159ea94. Green in CI.

@Redtigercod4

Copy link
Copy Markdown
Contributor

@ggainey Happy with the reviewed changes. Please feel free to merge.

@ggainey ggainey left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work, good discussion - thanks for the contribution and the responsiveness, both of you!

@ggainey
ggainey merged commit 99d1bd3 into pulp:main Sep 18, 2026
4 checks passed
Redtigercod4 pushed a commit to Redtigercod4/pulp-ui that referenced this pull request Sep 23, 2026
* Add deb repository and remote pages

Adds a "Pulp deb" menu section with Repositories and Remotes, mirroring
the structure of Pulp file: list, detail and edit for each, plus the
versions and distributions tabs on a repository.

Deliberately scoped to repositories and remotes. Publications are left
out because deb has two publication endpoints rather than a field --
publications/deb/apt generates fresh metadata and needs a signing
service, publications/deb/verbatim republishes upstream's Release byte
for byte -- so a publications tab needs a design decision rather than
just wiring. Content browsing is left out for the same reason: deb has
a dozen content endpoints where rpm has one.

RemoteForm gains the APT fields, which have no equivalent in the other
plugins: distributions (suites), components, architectures, gpgkey, and
the sync_sources/sync_udebs/sync_installer switches. `distributions` is
added to requiredFields for deb only -- pulp_deb answers a remote
without it with "This field is required.", unlike every other plugin
where url alone is enough. gpgkey reuses the FileUpload treatment the
certificate fields already use, since it is an armoured key file.

The remaining changes are registry entries: plugin2api, the plugin
unions on LazyRepositories/LazyDistributions/RepositoryForm, and the
deb-only fields on the shared RemoteType. All are additive; ansible,
container and file behaviour is unchanged.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)

* Page through the distributions of a deb repository being deleted

The lookup asked for the first hundred and left a TODO for the rest, so
deleting a repository with more distributions than that would leave the
excess pointing at a repository that no longer exists.

Walk the pages until as many have been collected as `count` reports,
stopping early on an empty page so a disagreeing count cannot loop.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)

* Default a deb sync to mirror: false, as pulp_deb does

Opening the sync modal offered to mirror, which deletes local content the
remote no longer has. The API's own default is the non-destructive one and
the modal should agree with it, so the deletion is opted into.

The value was written out three times; it is now one constant, which is
also where the divergence from ansible and file is explained.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)

* Turn an empty deb detail lookup into a not-found rejection

Both detail pages read results[0] straight out of the response, which
throws on any unexpected shape, and past page-with-tabs' catch that leaves
the page loading with nothing to show. Read the first result defensively
instead.

The comment claimed the API returns a 404; it returns 200 and an empty
list, and it is this code that synthesizes the 404.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)

* Collapse the deb repository breadcrumb branches

Three entries tested the same tab and were filtered back out again when
the answer was no. It is one decision -- which crumbs the versions tab
adds -- so make it once.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)

* Resolve the remote API through plugin2api

Three chained ternaries picked the remote API by plugin, repeating the
same list call in each branch. plugin2api already maps a plugin to its
repository and distribution APIs, so add the remote API there and let the
form ask for it: one switch to extend when a plugin is added, not a
conditional to unpick.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)

* Stop stubbing hasObjectPermission on deb distributions

The stub answered true to every permission. Nothing in this list is gated
on one, and an apt repository carries no my_permissions field for the
ansible-style check to read, so copying that check would answer no to
everyone instead.

Leave the permission out and say why, so an action added later gets a real
check rather than a stub that always agrees.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)

* Describe the deb remote and repository as interfaces

Both types hold property declarations only, with no constructor, no
methods and no instantiation, which is what an interface is for. Also
fixes a typo in the smartUpdate comment.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)

* Add smoke tests for the deb pages

Reaches both lists and their empty states, then opens the remote form to
check the APT-only fields render, since those are what the shared form
gained for this plugin.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)

* Take the shared SyncModal for the deb sync action

shared component. The deb sync action goes from 154 lines to 64.

The one thing deb needs that ansible and file do not is the mirror switch
starting off, so the component takes a defaultMirror prop that defaults to
what the other two have always sent. Their behaviour is unchanged.

A boolean rather than a params object deliberately: the effect that resets
the switches has to depend on it, and an object prop would either lie
about that dependency or, passed as a literal, reset the switches on every
render.

Refs pulp#277
Assisted By: Cursor (Claude Opus 5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants