This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


The following commit(s) were added to refs/heads/sbp by this push:
     new b1a43d2c Move Trusted Publishing fields into a separate form
b1a43d2c is described below

commit b1a43d2c10c416a23d383d5a090381c0ef1b28a7
Author: Sean B. Palmer <[email protected]>
AuthorDate: Fri Mar 20 15:21:58 2026 +0000

    Move Trusted Publishing fields into a separate form
---
 atr/get/projects.py           |  33 ++++++++++--
 atr/post/projects.py          |  22 ++++++++
 atr/shared/projects.py        | 120 ++++++++++++++++++++----------------------
 atr/storage/writers/policy.py |  17 ++++--
 tests/e2e/policy/conftest.py  |   5 +-
 tests/e2e/policy/helpers.py   |  20 +++++++
 tests/e2e/policy/test_post.py |   6 +--
 7 files changed, 146 insertions(+), 77 deletions(-)

diff --git a/atr/get/projects.py b/atr/get/projects.py
index 9090397e..8f1215a9 100644
--- a/atr/get/projects.py
+++ b/atr/get/projects.py
@@ -202,6 +202,7 @@ async def view(
             page.append(_render_compose_form(project))
             page.append(_render_vote_form(project))
             page.append(_render_finish_form(project))
+            page.append(_render_trusted_publishing_form(project))
         else:
             page.append(_render_policy_readonly(project))
 
@@ -358,9 +359,6 @@ def _render_compose_form(project: sql.Project) -> 
htm.Element:
                 "source_excludes_lightweight": 
"\n".join(project.policy_source_excludes_lightweight),
                 "source_excludes_rat": 
"\n".join(project.policy_source_excludes_rat),
                 "binary_artifact_paths": 
"\n".join(project.policy_binary_artifact_paths),
-                "github_repository_name": 
project.policy_github_repository_name or "",
-                "github_repository_branch": 
project.policy_github_repository_branch or "",
-                "github_compose_workflow_path": 
"\n".join(project.policy_github_compose_workflow_path),
                 "file_tag_mappings": atr_tag_yaml,
                 "strict_checking": project.policy_strict_checking,
             },
@@ -427,7 +425,6 @@ def _render_finish_form(project: sql.Project) -> 
htm.Element:
             submit_label="Save",
             defaults={
                 "project_key": project.key,
-                "github_finish_workflow_path": 
"\n".join(project.policy_github_finish_workflow_path),
                 "announce_release_subject": 
project.policy_announce_release_subject or "",
                 "announce_release_template": 
project.policy_announce_release_template or "",
                 "preserve_download_files": 
project.policy_preserve_download_files,
@@ -615,6 +612,33 @@ async def _render_releases_sections(
     return sections.collect()
 
 
+def _render_trusted_publishing_form(project: sql.Project) -> htm.Element:
+    card = htm.Block(htm.div, classes=".card.mb-4")
+    
card.div(".card-header.bg-light.d-flex.justify-content-between.align-items-center")[
+        htm.h3(".mb-0")["Release policy - Trusted Publishing"]
+    ]
+
+    with card.block(htm.div, classes=".card-body") as card_body:
+        form.render_block(
+            card_body,
+            model_cls=shared.projects.TrustedPublishingPolicyForm,
+            action=util.as_url(post.projects.view, name=str(project.key)),
+            submit_label="Save",
+            defaults={
+                "project_key": str(project.key),
+                "github_repository_name": 
project.policy_github_repository_name or "",
+                "github_repository_branch": 
project.policy_github_repository_branch or "",
+                "github_compose_workflow_path": 
"\n".join(project.policy_github_compose_workflow_path),
+                "github_vote_workflow_path": 
"\n".join(project.policy_github_vote_workflow_path),
+                "github_finish_workflow_path": 
"\n".join(project.policy_github_finish_workflow_path),
+            },
+            form_classes=".atr-canary.py-4.px-5",
+            border=True,
+            textarea_rows=5,
+        )
+    return card.collect()
+
+
 def _render_vote_form(project: sql.Project) -> htm.Element:
     card = htm.Block(htm.div, classes=".card.mb-4")
     
card.div(".card-header.bg-light.d-flex.justify-content-between.align-items-center")[
@@ -623,7 +647,6 @@ def _render_vote_form(project: sql.Project) -> htm.Element:
 
     defaults_dict = {
         "project_key": str(project.key),
-        "github_vote_workflow_path": 
"\n".join(project.policy_github_vote_workflow_path),
         "mailto_addresses": project.policy_mailto_addresses[0]
         if project.policy_mailto_addresses
         else f"dev@{project.key}.apache.org",
diff --git a/atr/post/projects.py b/atr/post/projects.py
index e22c5c8b..ef11b0d4 100644
--- a/atr/post/projects.py
+++ b/atr/post/projects.py
@@ -110,6 +110,9 @@ async def view(
         case shared.projects.FinishPolicyForm() as finish_form:
             return await _process_finish_form(session, finish_form)
 
+        case shared.projects.TrustedPublishingPolicyForm() as tp_form:
+            return await _process_trusted_publishing_form(session, tp_form)
+
         case shared.projects.RemoveCategoryForm() as remove_form:
             return await _process_remove_category(session, remove_form)
 
@@ -305,6 +308,25 @@ async def _process_remove_language(
     )
 
 
+async def _process_trusted_publishing_form(
+    session: web.Committer, tp_form: 
shared.projects.TrustedPublishingPolicyForm
+) -> web.WerkzeugResponse:
+    project_key = tp_form.project_key
+
+    async with storage.write(session) as write:
+        wacm = await write.as_project_committee_member(project_key)
+        try:
+            await wacm.policy.edit_trusted_publishing(tp_form)
+        except storage.AccessError as e:
+            return await session.redirect(
+                get.projects.view, project_key=project_key, error=f"Error 
editing Trusted Publishing policy: {e}"
+            )
+
+    return await session.redirect(
+        get.projects.view, project_key=project_key, success="Trusted 
Publishing options saved successfully."
+    )
+
+
 async def _process_vote_form(session: web.Committer, vote_form: 
shared.projects.VotePolicyForm) -> web.WerkzeugResponse:
     project_key = vote_form.project_key
 
diff --git a/atr/shared/projects.py b/atr/shared/projects.py
index 84b1c856..470a32f9 100644
--- a/atr/shared/projects.py
+++ b/atr/shared/projects.py
@@ -28,8 +28,9 @@ import atr.models.sql as sql
 import atr.util as util
 
 type COMPOSE = Literal["compose"]
-type VOTE = Literal["vote"]
 type FINISH = Literal["finish"]
+type TRUSTED_PUBLISHING = Literal["trusted_publishing"]
+type VOTE = Literal["vote"]
 type ADD_CATEGORY = Literal["add_category"]
 type REMOVE_CATEGORY = Literal["remove_category"]
 type ADD_LANGUAGE = Literal["add_language"]
@@ -133,19 +134,6 @@ class ComposePolicyForm(form.Form):
         "Paths to binary artifacts to be included in the release.",
         widget=form.Widget.TEXTAREA,
     )
-    github_repository_name: str = form.label(
-        "GitHub repository name",
-        "The name of the GitHub repository to use for the release, excluding 
the apache/ prefix.",
-    )
-    github_repository_branch: str = form.label(
-        "GitHub repository branch",
-        "Branch used for release builds (for example, main or 2.5.x). 
Optional.",
-    )
-    github_compose_workflow_path: str = form.label(
-        "GitHub compose workflow paths",
-        "The full paths to the GitHub workflows to use for the release, 
including the .github/workflows/ prefix.",
-        widget=form.Widget.TEXTAREA,
-    )
     file_tag_mappings: str = form.label(
         "Tagging spec",
         "Spec for which files should be tagged for release in specific 
distribution types, YAML format",
@@ -156,38 +144,10 @@ class ComposePolicyForm(form.Form):
         "If enabled, then the release cannot be voted upon unless all checks 
pass.",
     )
 
-    @pydantic.model_validator(mode="after")
-    def validate_github_fields(self) -> ComposePolicyForm:
-        github_repository_name = self.github_repository_name.strip()
-        github_repository_branch = self.github_repository_branch.strip()
-        compose_raw = self.github_compose_workflow_path or ""
-        compose = [p.strip() for p in compose_raw.split("\n") if p.strip()]
-
-        if compose and (not github_repository_name):
-            raise ValueError("GitHub repository name is required when any 
workflow path is set.")
-
-        if github_repository_branch and (not github_repository_name):
-            raise ValueError("GitHub repository name is required when a GitHub 
branch is set.")
-
-        if github_repository_name and ("/" in github_repository_name):
-            raise ValueError("GitHub repository name must not contain a 
slash.")
-
-        if compose:
-            for p in compose:
-                if not p.startswith(".github/workflows/"):
-                    raise ValueError("GitHub workflow paths must start with 
'.github/workflows/'.")
-
-        return self
-
 
 class VotePolicyForm(form.Form):
     variant: VOTE = form.value(VOTE)
     project_key: safe.ProjectKey = form.label("Project name", 
widget=form.Widget.HIDDEN)
-    github_vote_workflow_path: str = form.label(
-        "GitHub vote workflow paths",
-        "The full paths to the GitHub workflows to use for the release, 
including the .github/workflows/ prefix.",
-        widget=form.Widget.TEXTAREA,
-    )
     mailto_addresses: form.Email = form.label(
         "Email",
         f"The mailing list where vote emails are sent. This is usually your 
dev list. "
@@ -227,14 +187,6 @@ class VotePolicyForm(form.Form):
 
     @pydantic.model_validator(mode="after")
     def validate_vote_fields(self) -> VotePolicyForm:
-        vote_raw = self.github_vote_workflow_path or ""
-        vote = [p.strip() for p in vote_raw.split("\n") if p.strip()]
-
-        if vote:
-            for p in vote:
-                if not p.startswith(".github/workflows/"):
-                    raise ValueError("GitHub workflow paths must start with 
'.github/workflows/'.")
-
         min_hours = self.min_hours
         if (min_hours != 0) and ((min_hours < 72) or (min_hours > 144)):
             raise ValueError("Minimum voting period must be 0 or between 72 
and 144 hours inclusive.")
@@ -245,11 +197,6 @@ class VotePolicyForm(form.Form):
 class FinishPolicyForm(form.Form):
     variant: FINISH = form.value(FINISH)
     project_key: safe.ProjectKey = form.label("Project name", 
widget=form.Widget.HIDDEN)
-    github_finish_workflow_path: str = form.label(
-        "GitHub finish workflow paths",
-        "The full paths to the GitHub workflows to use for the release, 
including the .github/workflows/ prefix.",
-        widget=form.Widget.TEXTAREA,
-    )
     announce_release_subject: str = form.label(
         "Announce release subject",
         widget=form.Widget.CUSTOM,
@@ -263,15 +210,61 @@ class FinishPolicyForm(form.Form):
         "If enabled, existing download files will not be overwritten.",
     )
 
+
+class TrustedPublishingPolicyForm(form.Form):
+    variant: TRUSTED_PUBLISHING = form.value(TRUSTED_PUBLISHING)
+    project_key: safe.ProjectKey = form.label("Project name", 
widget=form.Widget.HIDDEN)
+    github_repository_name: str = form.label(
+        "GitHub repository name",
+        "The name of the GitHub repository to use for the release, excluding 
the apache/ prefix.",
+    )
+    github_repository_branch: str = form.label(
+        "GitHub repository branch",
+        "Branch used for release builds (for example, main or 2.5.x). 
Optional.",
+    )
+    # TODO: We should think about making these a list[RelUrlPath]
+    # But note that they contain .github, so that will be awkward
+    github_compose_workflow_path: str = form.label(
+        "Compose workflow paths",
+        "GitHub workflow paths for the compose phase, including the 
.github/workflows/ prefix.",
+        widget=form.Widget.TEXTAREA,
+    )
+    github_vote_workflow_path: str = form.label(
+        "Vote workflow paths",
+        "GitHub workflow paths for the vote phase, including the 
.github/workflows/ prefix.",
+        widget=form.Widget.TEXTAREA,
+    )
+    github_finish_workflow_path: str = form.label(
+        "Finish workflow paths",
+        "GitHub workflow paths for the finish phase, including the 
.github/workflows/ prefix.",
+        widget=form.Widget.TEXTAREA,
+    )
+
     @pydantic.model_validator(mode="after")
-    def validate_finish_fields(self) -> FinishPolicyForm:
-        finish_raw = self.github_finish_workflow_path or ""
-        finish = [p.strip() for p in finish_raw.split("\n") if p.strip()]
+    def validate_trusted_publishing_fields(self) -> 
TrustedPublishingPolicyForm:
+        github_repository_name = self.github_repository_name.strip()
+        github_repository_branch = self.github_repository_branch.strip()
+
+        all_paths: list[str] = []
+        for raw in (
+            self.github_compose_workflow_path,
+            self.github_vote_workflow_path,
+            self.github_finish_workflow_path,
+        ):
+            all_paths.extend(p.strip() for p in (raw or "").split("\n") if 
p.strip())
 
-        if finish:
-            for p in finish:
-                if not p.startswith(".github/workflows/"):
-                    raise ValueError("GitHub workflow paths must start with 
'.github/workflows/'.")
+        if all_paths and (not github_repository_name):
+            raise ValueError("GitHub repository name is required when any 
workflow path is set.")
+
+        if github_repository_branch and (not github_repository_name):
+            raise ValueError("GitHub repository name is required when a GitHub 
branch is set.")
+
+        if github_repository_name and ("/" in github_repository_name):
+            raise ValueError("GitHub repository name must not contain a 
slash.")
+
+        for p in all_paths:
+            if not p.startswith(".github/workflows/"):
+                raise ValueError("GitHub workflow paths must start with 
'.github/workflows/'.")
 
         return self
 
@@ -311,8 +304,9 @@ class DeleteSelectedProject(form.Form):
 
 type ProjectViewForm = Annotated[
     ComposePolicyForm
-    | VotePolicyForm
     | FinishPolicyForm
+    | TrustedPublishingPolicyForm
+    | VotePolicyForm
     | AddCategoryForm
     | RemoveCategoryForm
     | AddLanguageForm
diff --git a/atr/storage/writers/policy.py b/atr/storage/writers/policy.py
index 7c694d24..8d56fad8 100644
--- a/atr/storage/writers/policy.py
+++ b/atr/storage/writers/policy.py
@@ -123,9 +123,6 @@ class CommitteeMember(CommitteeParticipant):
         release_policy.source_excludes_lightweight = 
_split_lines_verbatim(form.source_excludes_lightweight)
         release_policy.source_excludes_rat = 
_split_lines_verbatim(form.source_excludes_rat)
         release_policy.binary_artifact_paths = 
_split_lines(form.binary_artifact_paths)
-        release_policy.github_repository_name = 
form.github_repository_name.strip()
-        release_policy.github_repository_branch = 
form.github_repository_branch.strip()
-        release_policy.github_compose_workflow_path = 
_split_lines(form.github_compose_workflow_path)
         release_policy.file_tag_mappings = atr_tags_dict
         release_policy.strict_checking = form.strict_checking
 
@@ -135,13 +132,24 @@ class CommitteeMember(CommitteeParticipant):
         project_key = form.project_key
         project, release_policy = await 
self.__get_or_create_policy(project_key)
 
-        release_policy.github_finish_workflow_path = 
_split_lines(form.github_finish_workflow_path)
         self.__set_announce_release_subject(form.announce_release_subject or 
"", project, release_policy)
         self.__set_announce_release_template(form.announce_release_template or 
"", project, release_policy)
         release_policy.preserve_download_files = form.preserve_download_files
 
         await self.__commit_and_log(str(project_key))
 
+    async def edit_trusted_publishing(self, form: 
shared.projects.TrustedPublishingPolicyForm) -> None:
+        project_key = form.project_key
+        _, release_policy = await self.__get_or_create_policy(project_key)
+
+        release_policy.github_repository_name = 
form.github_repository_name.strip()
+        release_policy.github_repository_branch = 
form.github_repository_branch.strip()
+        release_policy.github_compose_workflow_path = 
_split_lines(form.github_compose_workflow_path)
+        release_policy.github_vote_workflow_path = 
_split_lines(form.github_vote_workflow_path)
+        release_policy.github_finish_workflow_path = 
_split_lines(form.github_finish_workflow_path)
+
+        await self.__commit_and_log(str(project_key))
+
     async def edit_vote(self, form: shared.projects.VotePolicyForm) -> None:
         project_key = form.project_key
         project, release_policy = await 
self.__get_or_create_policy(project_key)
@@ -149,7 +157,6 @@ class CommitteeMember(CommitteeParticipant):
         release_policy.manual_vote = form.manual_vote
 
         if not release_policy.manual_vote:
-            release_policy.github_vote_workflow_path = 
_split_lines(form.github_vote_workflow_path)
             release_policy.mailto_addresses = [form.mailto_addresses]
             self.__set_min_hours(form.min_hours, project, release_policy)
             release_policy.pause_for_rm = form.pause_for_rm
diff --git a/tests/e2e/policy/conftest.py b/tests/e2e/policy/conftest.py
index 61affc80..e3cc5b67 100644
--- a/tests/e2e/policy/conftest.py
+++ b/tests/e2e/policy/conftest.py
@@ -44,5 +44,8 @@ def _clear_policy_excludes(page: Page) -> None:
     policy_helpers.textarea_source_excludes_rat(page).fill("")
     policy_helpers.input_github_repository_name(page).fill("")
     policy_helpers.input_github_repository_branch(page).fill("")
-    policy_helpers.compose_form_save_button(page).click()
+    policy_helpers.textarea_github_compose_workflow_path(page).fill("")
+    policy_helpers.textarea_github_vote_workflow_path(page).fill("")
+    policy_helpers.textarea_github_finish_workflow_path(page).fill("")
+    policy_helpers.trusted_publishing_form_save_button(page).click()
     page.wait_for_load_state()
diff --git a/tests/e2e/policy/helpers.py b/tests/e2e/policy/helpers.py
index 9ac36f6f..73162174 100644
--- a/tests/e2e/policy/helpers.py
+++ b/tests/e2e/policy/helpers.py
@@ -35,9 +35,29 @@ def input_github_repository_name(page: Page) -> Locator:
     return page.locator('input[name="github_repository_name"]')
 
 
+def textarea_github_compose_workflow_path(page: Page) -> Locator:
+    return page.locator('textarea[name="github_compose_workflow_path"]')
+
+
+def textarea_github_finish_workflow_path(page: Page) -> Locator:
+    return page.locator('textarea[name="github_finish_workflow_path"]')
+
+
+def textarea_github_vote_workflow_path(page: Page) -> Locator:
+    return page.locator('textarea[name="github_vote_workflow_path"]')
+
+
 def textarea_source_excludes_lightweight(page: Page) -> Locator:
     return page.locator('textarea[name="source_excludes_lightweight"]')
 
 
 def textarea_source_excludes_rat(page: Page) -> Locator:
     return page.locator('textarea[name="source_excludes_rat"]')
+
+
+def trusted_publishing_form_save_button(page: Page) -> Locator:
+    return (
+        page.locator('input[name="github_repository_name"]')
+        .locator("xpath=ancestor::form")
+        .locator('button[type="submit"]')
+    )
diff --git a/tests/e2e/policy/test_post.py b/tests/e2e/policy/test_post.py
index 768a0c90..61d942f9 100644
--- a/tests/e2e/policy/test_post.py
+++ b/tests/e2e/policy/test_post.py
@@ -25,13 +25,13 @@ def 
test_github_repository_branch_can_be_cleared(page_project: Page) -> None:
     repo_input.fill("tooling-actions")
     branch_input = helpers.input_github_repository_branch(page_project)
     branch_input.fill("main")
-    helpers.compose_form_save_button(page_project).click()
+    helpers.trusted_publishing_form_save_button(page_project).click()
     page_project.wait_for_load_state()
 
     root_helpers.visit(page_project, helpers.PROJECT_URL)
     branch_input = helpers.input_github_repository_branch(page_project)
     branch_input.fill("")
-    helpers.compose_form_save_button(page_project).click()
+    helpers.trusted_publishing_form_save_button(page_project).click()
     page_project.wait_for_load_state()
 
     root_helpers.visit(page_project, helpers.PROJECT_URL)
@@ -44,7 +44,7 @@ def 
test_github_repository_branch_value_persists(page_project: Page) -> None:
     repo_input.fill("tooling-actions")
     branch_input = helpers.input_github_repository_branch(page_project)
     branch_input.fill("2.5.x")
-    helpers.compose_form_save_button(page_project).click()
+    helpers.trusted_publishing_form_save_button(page_project).click()
     page_project.wait_for_load_state()
 
     root_helpers.visit(page_project, helpers.PROJECT_URL)


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to