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-trusted-releases.git
commit 790ca41aa2f07ff8dada3da289e59ea9094acd2b Author: Sean B. Palmer <[email protected]> AuthorDate: Fri Jan 30 18:36:39 2026 +0000 Add a release policy setting for the upstream branch in compose --- atr/api/__init__.py | 45 +++++++++++++------------ atr/get/projects.py | 1 + atr/models/api.py | 1 + atr/models/sql.py | 8 +++++ atr/shared/projects.py | 8 +++++ atr/storage/writers/policy.py | 1 + migrations/versions/0046_2026.01.30_72330898.py | 27 +++++++++++++++ tests/e2e/policy/conftest.py | 2 ++ tests/e2e/policy/helpers.py | 8 +++++ tests/e2e/policy/test_post.py | 32 ++++++++++++++++++ 10 files changed, 111 insertions(+), 22 deletions(-) diff --git a/atr/api/__init__.py b/atr/api/__init__.py index b5b28a0..b1041e6 100644 --- a/atr/api/__init__.py +++ b/atr/api/__init__.py @@ -283,28 +283,6 @@ async def distribute_ssh_register(data: models.api.DistributeSshRegisterArgs) -> ).model_dump(), 200 [email protected]("/distribute/task/status", methods=["POST"]) -@quart_schema.validate_request(models.api.DistributeStatusUpdateArgs) -async def update_distribution_task_status(data: models.api.DistributeStatusUpdateArgs) -> DictResponse: - """ - Update the status of a distribution task - """ - _payload, _asf_uid = await interaction.validate_trusted_jwt(data.publisher, data.jwt) - async with db.session() as db_data: - status = await db_data.workflow_status( - workflow_id=data.workflow, - project_name=data.project_name, - run_id=int(data.run_id), - ).demand(exceptions.NotFound(f"Workflow {data.workflow} not found")) - status.status = data.status - status.message = data.message - await db_data.commit() - return models.api.DistributeStatusUpdateResults( - endpoint="/distribute/task/status", - success=True, - ).model_dump(), 200 - - @api.route("/distribution/record", methods=["POST"]) @jwtoken.require @quart_schema.security_scheme([{"BearerAuth": []}]) @@ -673,6 +651,7 @@ async def project_policy(name: str) -> DictResponse: policy_binary_artifact_paths=project.policy_binary_artifact_paths, policy_github_compose_workflow_path=project.policy_github_compose_workflow_path, policy_github_finish_workflow_path=project.policy_github_finish_workflow_path, + policy_github_repository_branch=project.policy_github_repository_branch, policy_github_repository_name=project.policy_github_repository_name, policy_github_vote_workflow_path=project.policy_github_vote_workflow_path, policy_license_check_mode=project.policy_license_check_mode, @@ -1237,6 +1216,28 @@ async def tasks_list(query_args: models.api.TasksListQuery) -> DictResponse: ).model_dump(), 200 [email protected]("/distribute/task/status", methods=["POST"]) +@quart_schema.validate_request(models.api.DistributeStatusUpdateArgs) +async def update_distribution_task_status(data: models.api.DistributeStatusUpdateArgs) -> DictResponse: + """ + Update the status of a distribution task + """ + _payload, _asf_uid = await interaction.validate_trusted_jwt(data.publisher, data.jwt) + async with db.session() as db_data: + status = await db_data.workflow_status( + workflow_id=data.workflow, + project_name=data.project_name, + run_id=int(data.run_id), + ).demand(exceptions.NotFound(f"Workflow {data.workflow} not found")) + status.status = data.status + status.message = data.message + await db_data.commit() + return models.api.DistributeStatusUpdateResults( + endpoint="/distribute/task/status", + success=True, + ).model_dump(), 200 + + @api.route("/user/info") @rate_limiter.rate_limit(10, datetime.timedelta(hours=1)) @jwtoken.require diff --git a/atr/get/projects.py b/atr/get/projects.py index 8703c57..3075375 100644 --- a/atr/get/projects.py +++ b/atr/get/projects.py @@ -340,6 +340,7 @@ def _render_compose_form(project: sql.Project) -> htm.Element: "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, diff --git a/atr/models/api.py b/atr/models/api.py index 8a242f6..9464a63 100644 --- a/atr/models/api.py +++ b/atr/models/api.py @@ -295,6 +295,7 @@ class ProjectPolicyResults(schema.Strict): policy_binary_artifact_paths: list[str] policy_github_compose_workflow_path: list[str] policy_github_finish_workflow_path: list[str] + policy_github_repository_branch: str policy_github_repository_name: str policy_github_vote_workflow_path: list[str] policy_license_check_mode: sql.LicenseCheckMode diff --git a/atr/models/sql.py b/atr/models/sql.py index cdbc51f..0dc7780 100644 --- a/atr/models/sql.py +++ b/atr/models/sql.py @@ -738,6 +738,12 @@ Thanks, return "" return policy.github_repository_name + @property + def policy_github_repository_branch(self) -> str: + if (policy := self.release_policy) is None: + return "" + return policy.github_repository_branch + @property def policy_github_compose_workflow_path(self) -> list[str]: if (policy := self.release_policy) is None: @@ -1090,6 +1096,7 @@ class ReleasePolicy(sqlmodel.SQLModel, table=True): ) strict_checking: bool = sqlmodel.Field(default=False) github_repository_name: str = sqlmodel.Field(default="") + github_repository_branch: str = sqlmodel.Field(default="") github_compose_workflow_path: list[str] = sqlmodel.Field( default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON, nullable=False) ) @@ -1128,6 +1135,7 @@ class ReleasePolicy(sqlmodel.SQLModel, table=True): source_excludes_rat=list(self.source_excludes_rat), strict_checking=self.strict_checking, github_repository_name=self.github_repository_name, + github_repository_branch=self.github_repository_branch, github_compose_workflow_path=list(self.github_compose_workflow_path), github_vote_workflow_path=list(self.github_vote_workflow_path), github_finish_workflow_path=list(self.github_finish_workflow_path), diff --git a/atr/shared/projects.py b/atr/shared/projects.py index 23360ca..88162e6 100644 --- a/atr/shared/projects.py +++ b/atr/shared/projects.py @@ -136,6 +136,10 @@ class ComposePolicyForm(form.Form): "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.", @@ -154,12 +158,16 @@ class ComposePolicyForm(form.Form): @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.") diff --git a/atr/storage/writers/policy.py b/atr/storage/writers/policy.py index fc264b1..8c37638 100644 --- a/atr/storage/writers/policy.py +++ b/atr/storage/writers/policy.py @@ -124,6 +124,7 @@ class CommitteeMember(CommitteeParticipant): 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 diff --git a/migrations/versions/0046_2026.01.30_72330898.py b/migrations/versions/0046_2026.01.30_72330898.py new file mode 100644 index 0000000..43bacd2 --- /dev/null +++ b/migrations/versions/0046_2026.01.30_72330898.py @@ -0,0 +1,27 @@ +"""Add a GitHub repository branch property to release policies + +Revision ID: 0046_2026.01.30_72330898 +Revises: 0045_2026.01.30_9664bcb9 +Create Date: 2026-01-30 17:27:25.246498+00:00 +""" + +from collections.abc import Sequence + +import sqlalchemy as sa +from alembic import op + +# Revision identifiers, used by Alembic +revision: str = "0046_2026.01.30_72330898" +down_revision: str | None = "0045_2026.01.30_9664bcb9" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + with op.batch_alter_table("releasepolicy", schema=None) as batch_op: + batch_op.add_column(sa.Column("github_repository_branch", sa.String(), nullable=False, server_default="")) + + +def downgrade() -> None: + with op.batch_alter_table("releasepolicy", schema=None) as batch_op: + batch_op.drop_column("github_repository_branch") diff --git a/tests/e2e/policy/conftest.py b/tests/e2e/policy/conftest.py index 2918137..61affc8 100644 --- a/tests/e2e/policy/conftest.py +++ b/tests/e2e/policy/conftest.py @@ -42,5 +42,7 @@ def _clear_policy_excludes(page: Page) -> None: helpers.visit(page, policy_helpers.PROJECT_URL) policy_helpers.textarea_source_excludes_lightweight(page).fill("") 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() page.wait_for_load_state() diff --git a/tests/e2e/policy/helpers.py b/tests/e2e/policy/helpers.py index c9bcc09..f2e2474 100644 --- a/tests/e2e/policy/helpers.py +++ b/tests/e2e/policy/helpers.py @@ -27,6 +27,14 @@ def compose_form_save_button(page: Page) -> Locator: return page.locator('form.atr-canary button[type="submit"]').first +def input_github_repository_branch(page: Page) -> Locator: + return page.locator('input[name="github_repository_branch"]') + + +def input_github_repository_name(page: Page) -> Locator: + return page.locator('input[name="github_repository_name"]') + + def textarea_source_excludes_lightweight(page: Page) -> Locator: return page.locator('textarea[name="source_excludes_lightweight"]') diff --git a/tests/e2e/policy/test_post.py b/tests/e2e/policy/test_post.py index d3f6218..768a0c9 100644 --- a/tests/e2e/policy/test_post.py +++ b/tests/e2e/policy/test_post.py @@ -20,6 +20,38 @@ import e2e.policy.helpers as helpers from playwright.sync_api import Page, expect +def test_github_repository_branch_can_be_cleared(page_project: Page) -> None: + repo_input = helpers.input_github_repository_name(page_project) + 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() + 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() + page_project.wait_for_load_state() + + root_helpers.visit(page_project, helpers.PROJECT_URL) + branch_input = helpers.input_github_repository_branch(page_project) + expect(branch_input).to_have_value("") + + +def test_github_repository_branch_value_persists(page_project: Page) -> None: + repo_input = helpers.input_github_repository_name(page_project) + 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() + page_project.wait_for_load_state() + + root_helpers.visit(page_project, helpers.PROJECT_URL) + branch_input = helpers.input_github_repository_branch(page_project) + expect(branch_input).to_have_value("2.5.x") + + def test_source_excludes_lightweight_can_be_cleared(page_project: Page) -> None: textarea = helpers.textarea_source_excludes_lightweight(page_project) textarea.fill("*.min.js") --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
