Skip to content

Commit 3bf5387

Browse files
Store info about Koji tagging requests (#2648)
Store info about Koji tagging requests Fixes #2576. Reviewed-by: Laura Barcziová
2 parents b89f972 + 62bd9d9 commit 3bf5387

File tree

10 files changed

+489
-12
lines changed

10 files changed

+489
-12
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""Add Koji tag request groups and targets
2+
3+
Revision ID: 6f936840164c
4+
Revises: 4387d6ab90e9
5+
Create Date: 2024-11-14 13:20:07.918410
6+
7+
"""
8+
9+
import sqlalchemy as sa
10+
11+
from alembic import op
12+
13+
# revision identifiers, used by Alembic.
14+
revision = "6f936840164c"
15+
down_revision = "f69687c314c5"
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# ### commands auto generated by Alembic - please adjust! ###
22+
op.create_table(
23+
"koji_tag_request_groups",
24+
sa.Column("id", sa.Integer(), nullable=False),
25+
sa.Column("submitted_time", sa.DateTime(), nullable=True),
26+
sa.PrimaryKeyConstraint("id"),
27+
)
28+
op.create_table(
29+
"koji_tag_request_targets",
30+
sa.Column("id", sa.Integer(), nullable=False),
31+
sa.Column("task_id", sa.String(), nullable=True),
32+
sa.Column("target", sa.String(), nullable=True),
33+
sa.Column("web_url", sa.String(), nullable=True),
34+
sa.Column("tag_request_submitted_time", sa.DateTime(), nullable=True),
35+
sa.Column("sidetag", sa.String(), nullable=True),
36+
sa.Column("nvr", sa.String(), nullable=True),
37+
sa.Column("koji_tag_request_group_id", sa.Integer(), nullable=True),
38+
sa.ForeignKeyConstraint(["koji_tag_request_group_id"], ["koji_tag_request_groups.id"]),
39+
sa.PrimaryKeyConstraint("id"),
40+
)
41+
op.create_index(
42+
op.f("ix_koji_tag_request_targets_task_id"),
43+
"koji_tag_request_targets",
44+
["task_id"],
45+
unique=False,
46+
)
47+
op.add_column("pipelines", sa.Column("koji_tag_request_group_id", sa.Integer(), nullable=True))
48+
op.create_index(
49+
op.f("ix_pipelines_koji_tag_request_group_id"),
50+
"pipelines",
51+
["koji_tag_request_group_id"],
52+
unique=False,
53+
)
54+
op.create_foreign_key(
55+
None, "pipelines", "koji_tag_request_groups", ["koji_tag_request_group_id"], ["id"]
56+
)
57+
# ### end Alembic commands ###
58+
59+
60+
def downgrade():
61+
# ### commands auto generated by Alembic - please adjust! ###
62+
op.drop_constraint(None, "pipelines", type_="foreignkey")
63+
op.drop_index(op.f("ix_pipelines_koji_tag_request_group_id"), table_name="pipelines")
64+
op.drop_column("pipelines", "koji_tag_request_group_id")
65+
op.drop_index(
66+
op.f("ix_koji_tag_request_targets_task_id"), table_name="koji_tag_request_targets"
67+
)
68+
op.drop_table("koji_tag_request_targets")
69+
op.drop_table("koji_tag_request_groups")
70+
# ### end Alembic commands ###

packit_service/models.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1865,6 +1865,12 @@ class PipelineModel(Base):
18651865
index=True,
18661866
)
18671867
koji_build_group = relationship("KojiBuildGroupModel", back_populates="runs")
1868+
koji_tag_request_group_id = Column(
1869+
Integer,
1870+
ForeignKey("koji_tag_request_groups.id"),
1871+
index=True,
1872+
)
1873+
koji_tag_request_group = relationship("KojiTagRequestGroupModel", back_populates="runs")
18681874
vm_image_build_id = Column(
18691875
Integer,
18701876
ForeignKey("vm_image_build_targets.id"),
@@ -2822,6 +2828,143 @@ def get_all_projects(cls) -> set["GitProjectModel"]:
28222828
return set(projects)
28232829

28242830

2831+
class KojiTagRequestGroupModel(ProjectAndEventsConnector, GroupModel, Base):
2832+
__tablename__ = "koji_tag_request_groups"
2833+
id = Column(Integer, primary_key=True)
2834+
submitted_time = Column(DateTime, default=datetime.utcnow)
2835+
2836+
runs = relationship("PipelineModel", back_populates="koji_tag_request_group")
2837+
koji_tag_request_targets = relationship(
2838+
"KojiTagRequestTargetModel",
2839+
back_populates="group_of_targets",
2840+
)
2841+
2842+
@property
2843+
def grouped_targets(self):
2844+
return self.koji_tag_request_targets
2845+
2846+
def __repr__(self) -> str:
2847+
return f"KojiTagRequestGroupModel(id={self.id}, submitted_time={self.submitted_time})"
2848+
2849+
@classmethod
2850+
def get_by_id(cls, id_: int) -> Optional["KojiTagRequestGroupModel"]:
2851+
with sa_session_transaction() as session:
2852+
return session.query(KojiTagRequestGroupModel).filter_by(id=id_).first()
2853+
2854+
@classmethod
2855+
def create(cls, run_model: "PipelineModel") -> "KojiTagRequestGroupModel":
2856+
with sa_session_transaction(commit=True) as session:
2857+
tag_request_group = cls()
2858+
session.add(tag_request_group)
2859+
if run_model.koji_tag_request_group:
2860+
# Clone run model
2861+
new_run_model = PipelineModel.create(
2862+
project_event=run_model.project_event,
2863+
package_name=run_model.package_name,
2864+
)
2865+
new_run_model.srpm_build = run_model.srpm_build
2866+
new_run_model.koji_tag_request_group = tag_request_group
2867+
session.add(new_run_model)
2868+
else:
2869+
run_model.koji_tag_request_group = tag_request_group
2870+
session.add(run_model)
2871+
return tag_request_group
2872+
2873+
2874+
class KojiTagRequestTargetModel(GroupAndTargetModelConnector, Base):
2875+
"""we create an entry for every target"""
2876+
2877+
__tablename__ = "koji_tag_request_targets"
2878+
id = Column(Integer, primary_key=True)
2879+
task_id = Column(String, index=True) # ID of the Koji tag task
2880+
2881+
# chroot, but we use the word target in our docs
2882+
target = Column(String)
2883+
# URL to koji web ui for the particular build
2884+
web_url = Column(String)
2885+
# datetime.utcnow instead of datetime.utcnow() because its an argument to the function
2886+
# so it will run when the koji build is initiated, not when the table is made
2887+
tag_request_submitted_time = Column(DateTime, default=datetime.utcnow)
2888+
2889+
sidetag = Column(String)
2890+
nvr = Column(String)
2891+
2892+
koji_tag_request_group_id = Column(Integer, ForeignKey("koji_tag_request_groups.id"))
2893+
2894+
group_of_targets = relationship(
2895+
"KojiTagRequestGroupModel",
2896+
back_populates="koji_tag_request_targets",
2897+
)
2898+
2899+
def set_web_url(self, web_url: str):
2900+
with sa_session_transaction(commit=True) as session:
2901+
self.web_url = web_url
2902+
session.add(self)
2903+
2904+
def set_task_id(self, task_id: str):
2905+
with sa_session_transaction(commit=True) as session:
2906+
self.task_id = task_id
2907+
session.add(self)
2908+
2909+
def set_tag_request_submitted_time(self, tag_request_submitted_time: Optional[DateTime]):
2910+
with sa_session_transaction(commit=True) as session:
2911+
self.tag_request_submitted_time = tag_request_submitted_time
2912+
session.add(self)
2913+
2914+
@classmethod
2915+
def get_by_id(cls, id_: int) -> Optional["KojiTagRequestTargetModel"]:
2916+
with sa_session_transaction() as session:
2917+
return session.query(KojiTagRequestTargetModel).filter_by(id=id_).first()
2918+
2919+
@classmethod
2920+
def get_all(cls) -> Iterable["KojiTagRequestTargetModel"]:
2921+
with sa_session_transaction() as session:
2922+
return session.query(KojiTagRequestTargetModel)
2923+
2924+
@classmethod
2925+
def get_range(
2926+
cls,
2927+
first: int,
2928+
last: int,
2929+
) -> Iterable["KojiTagRequestTargetModel"]:
2930+
with sa_session_transaction() as session:
2931+
query = session.query(KojiTagRequestTargetModel).order_by(
2932+
desc(KojiTagRequestTargetModel.id),
2933+
)
2934+
2935+
return query.slice(first, last)
2936+
2937+
@classmethod
2938+
def create(
2939+
cls,
2940+
task_id: Optional[str],
2941+
web_url: Optional[str],
2942+
target: str,
2943+
koji_tag_request_group: "KojiTagRequestGroupModel",
2944+
sidetag: Optional[str] = None,
2945+
nvr: Optional[str] = None,
2946+
) -> "KojiTagRequestTargetModel":
2947+
with sa_session_transaction(commit=True) as session:
2948+
tag_request = cls()
2949+
tag_request.task_id = task_id
2950+
tag_request.web_url = web_url
2951+
tag_request.target = target
2952+
tag_request.sidetag = sidetag
2953+
tag_request.nvr = nvr
2954+
session.add(tag_request)
2955+
2956+
koji_tag_request_group.koji_tag_request_targets.append(tag_request)
2957+
session.add(koji_tag_request_group)
2958+
2959+
return tag_request
2960+
2961+
def __repr__(self):
2962+
return (
2963+
f"KojiTagRequestTargetModel(id={self.id}, "
2964+
f"tag_submitted_time={self.tag_request_submitted_time})"
2965+
)
2966+
2967+
28252968
class SRPMBuildModel(ProjectAndEventsConnector, Base):
28262969
__tablename__ = "srpm_builds"
28272970
id = Column(Integer, primary_key=True)

packit_service/service/api/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from packit_service.service.api.healthz import ns as healthz_ns
1111
from packit_service.service.api.installations import ns as installations_ns
1212
from packit_service.service.api.koji_builds import koji_builds_ns
13+
from packit_service.service.api.koji_tag_requests import koji_tag_requests_ns
1314
from packit_service.service.api.osh_scans import ns as osh_scans_ns
1415
from packit_service.service.api.projects import ns as projects_ns
1516
from packit_service.service.api.propose_downstream import ns as propose_downstream_ns
@@ -38,6 +39,7 @@
3839
api.add_namespace(webhooks_ns)
3940
api.add_namespace(allowlist_ns)
4041
api.add_namespace(koji_builds_ns)
42+
api.add_namespace(koji_tag_requests_ns)
4143
api.add_namespace(srpm_builds_ns)
4244
api.add_namespace(runs_ns)
4345
api.add_namespace(propose_downstream_ns)
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Copyright Contributors to the Packit project.
2+
# SPDX-License-Identifier: MIT
3+
4+
from http import HTTPStatus
5+
from logging import getLogger
6+
7+
from flask_restx import Namespace, Resource
8+
9+
from packit_service.models import (
10+
KojiTagRequestGroupModel,
11+
KojiTagRequestTargetModel,
12+
optional_timestamp,
13+
)
14+
from packit_service.service.api.parsers import indices, pagination_arguments
15+
from packit_service.service.api.utils import get_project_info_from_build, response_maker
16+
17+
logger = getLogger("packit_service")
18+
19+
koji_tag_requests_ns = Namespace("koji-tag-requests", description="Koji tagging requests")
20+
21+
22+
@koji_tag_requests_ns.route("")
23+
class KojiTagRequestsList(Resource):
24+
@koji_tag_requests_ns.expect(pagination_arguments)
25+
@koji_tag_requests_ns.response(HTTPStatus.PARTIAL_CONTENT, "Koji tagging requests list follows")
26+
def get(self):
27+
"""List all Koji tagging requests."""
28+
first, last = indices()
29+
result = []
30+
31+
for tag_request in KojiTagRequestTargetModel.get_range(first, last):
32+
tag_request_dict = {
33+
"packit_id": tag_request.id,
34+
"task_id": tag_request.task_id,
35+
"tag_request_submitted_time": optional_timestamp(
36+
tag_request.tag_request_submitted_time
37+
),
38+
"chroot": tag_request.target,
39+
"sidetag": tag_request.sidetag,
40+
"nvr": tag_request.nvr,
41+
"web_url": tag_request.web_url,
42+
"pr_id": tag_request.get_pr_id(),
43+
"branch_name": tag_request.get_branch_name(),
44+
"release": tag_request.get_release_tag(),
45+
}
46+
47+
if project := tag_request.get_project():
48+
tag_request_dict["project_url"] = project.project_url
49+
tag_request_dict["repo_namespace"] = project.namespace
50+
tag_request_dict["repo_name"] = project.repo_name
51+
52+
result.append(tag_request_dict)
53+
54+
resp = response_maker(
55+
result,
56+
status=HTTPStatus.PARTIAL_CONTENT,
57+
)
58+
resp.headers["Content-Range"] = f"koji-tag-requests {first + 1}-{last}/*"
59+
return resp
60+
61+
62+
@koji_tag_requests_ns.route("/<int:id>")
63+
@koji_tag_requests_ns.param("id", "Packit id of the tagging request")
64+
class KojiTagRequestItem(Resource):
65+
@koji_tag_requests_ns.response(HTTPStatus.OK, "OK, koji tagging request details follow")
66+
@koji_tag_requests_ns.response(
67+
HTTPStatus.NOT_FOUND.value,
68+
"No info about tagging request stored in DB",
69+
)
70+
def get(self, id):
71+
"""A specific koji tagging request details."""
72+
tag_request = KojiTagRequestTargetModel.get_by_id(int(id))
73+
74+
if not tag_request:
75+
return response_maker(
76+
{"error": "No info about tagging request stored in DB"},
77+
status=HTTPStatus.NOT_FOUND,
78+
)
79+
80+
tag_request_dict = {
81+
"task_id": tag_request.task_id,
82+
"chroot": tag_request.target,
83+
"sidetag": tag_request.sidetag,
84+
"nvr": tag_request.nvr,
85+
"tag_request_submitted_time": optional_timestamp(
86+
tag_request.tag_request_submitted_time
87+
),
88+
"commit_sha": tag_request.commit_sha,
89+
"web_url": tag_request.web_url,
90+
"run_ids": sorted(run.id for run in tag_request.group_of_targets.runs),
91+
}
92+
93+
tag_request_dict.update(get_project_info_from_build(tag_request))
94+
return response_maker(tag_request_dict)
95+
96+
97+
@koji_tag_requests_ns.route("/groups/<int:id>")
98+
@koji_tag_requests_ns.param("id", "Packit id of the koji tagging request group")
99+
class KojiTagRequestGroup(Resource):
100+
@koji_tag_requests_ns.response(HTTPStatus.OK, "OK, koji tagging request group details follow")
101+
@koji_tag_requests_ns.response(
102+
HTTPStatus.NOT_FOUND.value,
103+
"No info about koji tagging request group stored in DB",
104+
)
105+
def get(self, id):
106+
"""A specific test run details."""
107+
group_model = KojiTagRequestGroupModel.get_by_id(int(id))
108+
109+
if not group_model:
110+
return response_maker(
111+
{"error": "No info about group stored in DB"},
112+
status=HTTPStatus.NOT_FOUND,
113+
)
114+
115+
group_dict = {
116+
"submitted_time": optional_timestamp(group_model.submitted_time),
117+
"run_ids": sorted(run.id for run in group_model.runs),
118+
"tag_request_target_ids": sorted(
119+
tag_request.id for tag_request in group_model.grouped_targets
120+
),
121+
}
122+
123+
group_dict.update(get_project_info_from_build(group_model))
124+
return response_maker(group_dict)

0 commit comments

Comments
 (0)