From a670832fdb58fcc84b6db777d863da82a31e2de1 Mon Sep 17 00:00:00 2001 From: Ricardo Dias Date: Fri, 30 Oct 2026 21:34:01 +0000 Subject: [PATCH] release: drop releasetargets left pointing at the deleted project OBS rewrites references to a force-deleted project to project="deleted". A hop-1 release target left on a staging project by a cancelled populate became such a target once sync prerelease deleted the old pre-release tree, and osc release then failed with "no permission to write in project deleted". _remove_release_targets (the self-heal run before every osc release) now also drops targets in the deleted project, so the next populate repairs the source project. Signed-off-by: Ricardo Dias --- percona_obs/obs_api.py | 6 +++++- tests/test_obs_api_markers.py | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/percona_obs/obs_api.py b/percona_obs/obs_api.py index 8cc2cecc0..39d37245e 100644 --- a/percona_obs/obs_api.py +++ b/percona_obs/obs_api.py @@ -1966,6 +1966,10 @@ def _remove_release_targets( ) -> None: """Remove entries added by _add_release_targets. + Also drops targets in the ``deleted`` project: OBS rewrites references to + a force-deleted project that way, so a leftover target whose release + project was since deleted (e.g. a recreated pre-release) would make + ``osc release`` fail with "no permission to write in project deleted". Safe to call when no releasetargets are present (no-op). Never raises — errors are printed as warnings so the caller's finally-block does not mask the primary exception. @@ -1979,7 +1983,7 @@ def _remove_release_targets( removed = 0 for repo_elem in root.findall("repository"): for rt in repo_elem.findall("releasetarget"): - if rt.get("project") == release_obs_project: + if rt.get("project") in (release_obs_project, "deleted"): repo_elem.remove(rt) removed += 1 diff --git a/tests/test_obs_api_markers.py b/tests/test_obs_api_markers.py index 5a13c5743..b8699a4e8 100644 --- a/tests/test_obs_api_markers.py +++ b/tests/test_obs_api_markers.py @@ -130,3 +130,27 @@ def test_single_marker_is_appended_last(monkeypatch): desc = ET.fromstring(pushed["meta"]).findtext("description") assert desc.splitlines()[-1] == "release_id: 17.11-2" assert "source_commit: old" in desc + + +def test_remove_release_targets_drops_deleted_project_targets(monkeypatch): + """OBS rewrites targets of a force-deleted project to project="deleted"; + those can never be released into and are removed with ours.""" + meta = ( + 't' + '' + '' + '' + '' + "" + ) + pushed = {} + monkeypatch.setattr(obs_api.osc.core, "show_project_meta", lambda a, p: meta) + monkeypatch.setattr( + obs_api, + "_edit_project_meta", + lambda a, p, meta, force: pushed.setdefault("meta", meta) and False, + ) + obs_api._remove_release_targets("http://obs", "S", "PR:rel") + assert 'project="deleted"' not in pushed["meta"] + assert 'project="PR:rel"' not in pushed["meta"] + assert 'project="other"' in pushed["meta"]