This is an automated email from the ASF dual-hosted git repository. sbp pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tooling-atr-experiments.git
The following commit(s) were added to refs/heads/main by this push: new 7a333f6 Allow package deletion 7a333f6 is described below commit 7a333f61052adb37f37f84db4a6efbcadbbb30c2 Author: Sean B. Palmer <s...@miscoranda.com> AuthorDate: Thu Feb 20 18:47:33 2025 +0200 Allow package deletion --- atr/routes.py | 80 +++++++++++++++++++++++++++++++++++++ atr/templates/candidate-review.html | 34 +++++++++++++++- docs/plan.html | 4 +- docs/plan.md | 4 +- 4 files changed, 117 insertions(+), 5 deletions(-) diff --git a/atr/routes.py b/atr/routes.py index 1536891..e55f70d 100644 --- a/atr/routes.py +++ b/atr/routes.py @@ -671,6 +671,86 @@ async def root_candidate_review() -> str: return await render_template("candidate-review.html", releases=user_releases) +async def delete_package_files(package: Package, uploads_path: Path) -> None: + """Delete the artifact and signature files associated with a package.""" + if package.artifact_sha3: + artifact_path = uploads_path / package.artifact_sha3 + if await aiofiles.os.path.exists(artifact_path): + await aiofiles.os.remove(artifact_path) + + if package.signature_sha3: + signature_path = uploads_path / package.signature_sha3 + if await aiofiles.os.path.exists(signature_path): + await aiofiles.os.remove(signature_path) + + +async def validate_package_deletion( + db_session: AsyncSession, artifact_sha3: str, release_key: str, session_uid: str +) -> Package: + """Validate package deletion request and return the package if valid.""" + # Get the package and its associated release + if Package.release is None: + raise FlashError("Package has no associated release") + if Release.pmc is None: + raise FlashError("Release has no associated PMC") + + pkg_release = cast(InstrumentedAttribute[Release], Package.release) + rel_pmc = cast(InstrumentedAttribute[PMC], Release.pmc) + statement = ( + select(Package) + .options(selectinload(pkg_release).selectinload(rel_pmc)) + .where(Package.artifact_sha3 == artifact_sha3) + ) + result = await db_session.execute(statement) + package = result.scalar_one_or_none() + + if not package: + raise FlashError("Package not found") + + if package.release_key != release_key: + raise FlashError("Invalid release key") + + # Check permissions + if package.release and package.release.pmc: + if session_uid not in package.release.pmc.pmc_members and session_uid not in package.release.pmc.committers: + raise FlashError("You don't have permission to delete this package") + + return package + + +@APP.route("/package/delete", methods=["POST"]) +@require(Requirements.committer) +async def root_package_delete() -> Response: + """Delete a package from a release candidate.""" + session = await session_read() + if (session is None) or (session.uid is None): + raise ASFQuartException("Not authenticated", errorcode=401) + + form = await request.form + artifact_sha3 = form.get("artifact_sha3") + release_key = form.get("release_key") + + if not artifact_sha3 or not release_key: + await flash("Missing required parameters", "error") + return redirect(url_for("root_candidate_review")) + + async with get_session() as db_session: + async with db_session.begin(): + try: + package = await validate_package_deletion(db_session, artifact_sha3, release_key, session.uid) + await delete_package_files(package, Path(get_release_storage_dir())) + await db_session.delete(package) + except FlashError as e: + await flash(str(e), "error") + return redirect(url_for("root_candidate_review")) + except Exception as e: + await flash(f"Error deleting files: {e!s}", "error") + return redirect(url_for("root_candidate_review")) + + await flash("Package deleted successfully", "success") + return redirect(url_for("root_candidate_review")) + + async def save_file_by_hash(base_dir: Path, file: FileStorage) -> tuple[str, int]: """ Save a file using its SHA3-256 hash as the filename. diff --git a/atr/templates/candidate-review.html b/atr/templates/candidate-review.html index 94dfe5f..f2b8a06 100644 --- a/atr/templates/candidate-review.html +++ b/atr/templates/candidate-review.html @@ -61,6 +61,25 @@ color: white; } + .delete-form { + display: inline-block; + margin-left: 1rem; + } + + .delete-button { + padding: 0.5rem 1rem; + background: #dc3545; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-weight: 500; + } + + .delete-button:hover { + background: #c82333; + } + .package-separator { height: 2rem; background-color: #f5f5f5; @@ -181,8 +200,21 @@ <td class="prose"> <a class="verify-link" href="{{ url_for('root_candidate_signatures_verify', release_key=release.storage_key) }}"> - Verify Signatures + Verify signatures </a> + <form method="post" + action="{{ url_for('root_package_delete') }}" + class="delete-form"> + <input type="hidden" + name="artifact_sha3" + value="{{ package.artifact_sha3 }}" /> + <input type="hidden" name="release_key" value="{{ release.storage_key }}" /> + <button type="submit" + class="delete-button" + onclick="return confirm('Are you sure you want to delete this package?')"> + Delete package + </button> + </form> </td> </tr> {% endfor %} diff --git a/docs/plan.html b/docs/plan.html index 782115c..961e6b6 100644 --- a/docs/plan.html +++ b/docs/plan.html @@ -8,8 +8,8 @@ <li>[DONE] Allow upload of checksum file alongside artifacts and signatures</li> <li>[DONE] Add a form field to choose the RC artifact type</li> <li>[DONE] Allow extra types of artifact, such as reproducible binary and convenience binary</li> -<li>Differentiate between podling PPMCs and top level PMCs</li> -<li>Allow package deletion</li> +<li>[DONE] Differentiate between podling PPMCs and top level PMCs</li> +<li>[DONE] Allow package deletion</li> </ul> </li> <li> diff --git a/docs/plan.md b/docs/plan.md index ad55597..366cbd4 100644 --- a/docs/plan.md +++ b/docs/plan.md @@ -8,8 +8,8 @@ This is a rough plan of immediate tasks. The priority of these tasks may change, - [DONE] Allow upload of checksum file alongside artifacts and signatures - [DONE] Add a form field to choose the RC artifact type - [DONE] Allow extra types of artifact, such as reproducible binary and convenience binary - - Differentiate between podling PPMCs and top level PMCs - - Allow package deletion + - [DONE] Differentiate between podling PPMCs and top level PMCs + - [DONE] Allow package deletion 2. Enhance RC display - [DONE] Augment raw file hashes with the original filenames in the UI --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tooling.apache.org For additional commands, e-mail: dev-h...@tooling.apache.org