Add deb repository and remote pages - #347
warisshaikh1 wants to merge 9 commits into
Conversation
There was a problem hiding this comment.
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
| { name, pulp_href, pulpId }, | ||
| { addAlert, setState, listQuery }, | ||
| ) { | ||
| // TODO: handle more pages |
There was a problem hiding this comment.
MUST: Bare // TODO isn't enough here - if the repository being deleted has more than 100 distributions, the excess are silently orphaned. I'm aware this same pattern exists in both ansible and file repository actions, but I don't want to carry it over into deb as well.
Please either link this TODO to a tracked Github issue, or implement pagination using the offset value returned by the API.
There was a problem hiding this comment.
Implemented the pagination. listDistributions() walks pages until it has collected as many as count reports, and breaks early on an empty page so a count that disagrees with reality cannot spin the loop.
| @@ -0,0 +1,149 @@ | |||
| import { msg, t } from '@lingui/core/macro'; | |||
There was a problem hiding this comment.
MUST (mirror default): Good to see this flagged on the PR already - I'd agree it's worth resolving to match what pulp_deb defaults to, since that's the safer, non-destruction option. Aware ansible and file hardcode true too, but I'll raise a separate issue to track.
SHOULD (SyncModal duplication): Also flagged by you already - agreed this is worth doing, but I'd suggest pulling SyncModal out into a shared component as a precursor PR rather than here, since it's identical to file and ansible.
There was a problem hiding this comment.
Flipped to mirror: false. It was written out in three places, so it is one DEFAULT_SYNC_PARAMS constant now, with a note there on why deb does not follow ansible and file.
On the SyncModal duplication: agreed, and agreed it belongs in its own PR rather than this one. I will follow up with one that pulls it out for ansible and file, and drop this copy once that lands.
There was a problem hiding this comment.
Opened #363 for the extraction. Once that lands I will rebase this branch on it and drop the copy here.
| listUrl: formatPath(Paths.deb.remote.list), | ||
| query: ({ name }) => | ||
| DebRemoteAPI.list({ name }) | ||
| .then(({ data: { results } }) => results[0]) |
There was a problem hiding this comment.
MUST: results[0] assumes an array with no local .catch - if the response shape is ever wrong, it throws past page-with-tabs.tsx catch handler, and the page would hang silently. Also, the comment here is a little misleading, worth rewording so it's clear we're synthesizing a not-found rejection, not describing actual API behaviour.
There was a problem hiding this comment.
Both detail pages read data?.results?.[0] now and reject explicitly, so nothing throws past the catch. Reworded the comment to say what is actually happening: the list endpoint answers 200 with an empty list, and it is this code that synthesizes the 404. Did the repository page in the same commit since it had the same shape.
| : plugin === 'file' | ||
| ? FileRemoteAPI.list({ ...(name ? { name__icontains: name } : {}) }) | ||
| : Promise.reject(plugin) | ||
| : plugin === 'deb' |
There was a problem hiding this comment.
SHOULD: Nested ternary here is easy to misread and easy to break silently if a fourth plugin is added. Suggest breaking it out into a helper function with a switch statement instead.
There was a problem hiding this comment.
Dropped the chain. plugin2api already maps a plugin to its repository and distribution APIs, so I added RemoteAPI to it and the form asks for that -- one switch to extend when a plugin arrives, rather than a conditional to unpick.
| addAlert, | ||
| query, | ||
| hasPermission, | ||
| hasObjectPermission: (_p: string): boolean => true, |
There was a problem hiding this comment.
SHOULD: hasObjectPermission here always returns true, unlike the ansible implementation which conducts a real check against item.my_permissions. It's most likely harmless today, but it's a potential landmine for whoever wires up a permission-gated action later and copies in this stub.
There was a problem hiding this comment.
Took it out rather than filling it in.
Checked against a live pulp_deb 3.10: repositories/deb/apt/ does not return my_permissions (nor does repositories/file/file/), so the ansible-style check would answer no to everyone here -- a quieter landmine than the stub. Nothing in this list is gated on a permission, so the field is gone and there is a comment in its place saying that an action added later wants a real check.
| @@ -0,0 +1,71 @@ | |||
| import { PulpAPI } from './pulp'; | |||
|
|
|||
| export class DebRemoteType { | |||
There was a problem hiding this comment.
WOULD: This class only holds property definitions - no constructor or methods, never instantiated. Per TypeScript's own docs, this fits an interface better.
There was a problem hiding this comment.
Converted to an interface.
| @@ -0,0 +1,42 @@ | |||
| import { PulpAPI } from './pulp'; | |||
|
|
|||
| export class DebRepositoryType { | |||
There was a problem hiding this comment.
WOULD: This class only holds property definitions - no constructor or methods, never instantiated. Per TypeScript's own docs, this fits an interface better.
There was a problem hiding this comment.
Converted to an interface.
| remote[field] = null; | ||
| } | ||
|
|
||
| // API returns headers:null bull doesn't accept it .. and we don't edit headers |
There was a problem hiding this comment.
COULD: Typo - bull should be but.
There was a problem hiding this comment.
Fixed.
| const DebRepositoryDetail = PageWithTabs< | ||
| DebRepositoryType & { remote?: DebRemoteType } | ||
| >({ | ||
| breadcrumbs: ({ name, tab, params: { repositoryVersion } }) => |
There was a problem hiding this comment.
WOULD: The three tab === 'repositoru-versions' tenaries repeat the same guard to build what's really one decision - harder to read than it needs to be. Suggest collapsing into a single guarded branch instead.
There was a problem hiding this comment.
Collapsed into a single guarded branch.
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)
019b5a2 to
9772856
Compare
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)
9772856 to
9ce27a7
Compare
|
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 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 |
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 debmenu section gated onhasPlugin('deb'), with Repositories and Remotes, mirroringPulp file:remotes/deb/aptrepositories/deb/aptWhat it deliberately leaves out
publications/deb/aptgenerates fresh metadata and needs a signing service to be consumable, whilepublications/deb/verbatimrepublishes upstream'sRelease/InReleasebyte for byte so an ordinaryubuntu-archive-keyringverifies 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/deb/packagesis 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
RemoteFormneeded the APT fields, which have no equivalent elsewhere:distributions(suites),components,architectures,gpgkey, and thesync_sources/sync_udebs/sync_installerswitches.distributionsis required, for deb only. Every other plugin's remote needs just a URL; pulp_deb rejects one without suites:So
requiredFieldsbecomes conditional.gpgkeyreuses theFileUploadtreatmentca_certandclient_certalready get, since it's an armoured key file rather than a one-line value.Everything else is registry entries —
plugin2api, the plugin unions onLazyRepositories/LazyDistributions/RepositoryForm, and deb-only optional fields on the sharedRemoteType. All additive; ansible, container and file behaviour is unchanged (theRemoteFormdiff 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):ordering/name__icontains/pulp_label_select/remotefilters,…/versions/,…/versions/?number=N, anddistributions/deb/apt/?repository=<href>DebRemoteEditsends → 201; thesmartUpdatePUT with all APT fields → 202, and the fields read back verbatimpluginRepositoryBasePath('deb', …)resolves the base_path the Repository URL field showsnpm run lint:js,lint:ts,lint:ls, thethis-without-classcheck andnpm run buildare all clean (build warnings unchanged at 18, all pre-existing bundle-size ones). NoCHANGES.mdentry, since that's generated from PR titles at release.Two things I'd flag for review
SyncModal.ansible-repository-sync.tsxandfile-repository-sync.tsxalready 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.mirrordefaults totruein the sync modal, matching the other plugins, though pulp_deb's own API default isfalse. I kept UI consistency, but say the word and I'll flip it — mirroring is the destructive direction.@bmbouter @himdel @dkliban @ipanova