Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Receive and save OpenScanHub task finish event #2580

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions alembic/versions/242a0cdb6d23_create_oshscanmodel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""Create OSHScanModel

Revision ID: 242a0cdb6d23
Revises: f376be1907e1
Create Date: 2024-10-18 08:10:35.335510

"""

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = "242a0cdb6d23"
down_revision = "f376be1907e1"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"scans",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("task_id", sa.Integer(), nullable=True),
sa.Column(
"status",
sa.Enum(
"pending",
"running",
"succeeded",
"canceled",
"failed",
name="oshscanstatus",
),
nullable=True,
),
sa.Column("url", sa.String(), nullable=True),
sa.Column("issues_added_url", sa.String(), nullable=True),
sa.Column("issues_fixed_url", sa.String(), nullable=True),
sa.Column("scan_results_url", sa.String(), nullable=True),
sa.Column("copr_build_target_id", sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(
["copr_build_target_id"],
["copr_build_targets.id"],
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("copr_build_target_id"),
sa.UniqueConstraint("task_id"),
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("scans")
# ### end Alembic commands ###
10 changes: 10 additions & 0 deletions packit_service/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,3 +302,13 @@ def from_number(number: int):
USAGE_PAST_YEAR_DATE_STR = (datetime.now() - timedelta(days=365)).strftime("%Y-%m-%d")
USAGE_DATE_IN_THE_PAST = USAGE_CURRENT_DATE.replace(year=USAGE_CURRENT_DATE.year - 100)
USAGE_DATE_IN_THE_PAST_STR = USAGE_DATE_IN_THE_PAST.strftime("%Y-%m-%d")

OPEN_SCAN_HUB_FEATURE_DESCRIPTION = (
"This is an experimental feature. Once the scan finishes, you can see the "
"new findings in the `added.html` in `Logs`. \n\n"
":warning: You can see the list of known issues and also provide your feedback"
" [here](https://github.com/packit/packit/discussions/2371). \n\n"
"You can disable the scanning in your configuration by "
"setting `osh_diff_scan_after_copr_build` to `false`. For more information, "
f"see [docs]({DOCS_URL}/configuration#osh_diff_scan_after_copr_build)."
)
77 changes: 77 additions & 0 deletions packit_service/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1970,6 +1970,8 @@ class CoprBuildTargetModel(GroupAndTargetModelConnector, Base):
"CoprBuildGroupModel", back_populates="copr_build_targets"
)

scan = relationship("OSHScanModel", back_populates="copr_build_target")

def set_built_packages(self, built_packages):
with sa_session_transaction(commit=True) as session:
self.built_packages = built_packages
Expand Down Expand Up @@ -2195,6 +2197,13 @@ def __repr__(self):
f"build_submitted_time={self.build_submitted_time})"
)

def add_scan(self, task_id: int) -> "OSHScanModel":
with sa_session_transaction(commit=True) as session:
scan = OSHScanModel.get_or_create(task_id)
scan.copr_build_target = self
session.add(scan)
return scan


class KojiBuildGroupModel(ProjectAndEventsConnector, GroupModel, Base):
__tablename__ = "koji_build_groups"
Expand Down Expand Up @@ -3824,6 +3833,74 @@ def get_by_koji_name(cls, koji_name: str) -> Optional["SidetagModel"]:
return session.query(SidetagModel).filter_by(koji_name=koji_name).first()


class OSHScanStatus(str, enum.Enum):
"""An enum of all possible build statuses"""

pending = "pending"
running = "running"
succeeded = "succeeded"
failed = "failed"
canceled = "canceled"


class OSHScanModel(Base):
__tablename__ = "scans"
id = Column(Integer, primary_key=True)
majamassarini marked this conversation as resolved.
Show resolved Hide resolved
task_id = Column(Integer, unique=True) # open scan hub id
status = Column(Enum(OSHScanStatus))
url = Column(String)
issues_added_url = Column(String)
issues_fixed_url = Column(String)
scan_results_url = Column(String)
copr_build_target_id = Column(
Integer, ForeignKey("copr_build_targets.id"), unique=True
)
copr_build_target = relationship(
"CoprBuildTargetModel", back_populates="scan", uselist=False
)

@classmethod
def get_or_create(cls, task_id: int) -> "OSHScanModel":
with sa_session_transaction(commit=True) as session:
scan = cls.get_by_task_id(task_id)
if not scan:
scan = cls()
majamassarini marked this conversation as resolved.
Show resolved Hide resolved
scan.task_id = task_id
scan.status = OSHScanStatus.pending
session.add(scan)
return scan

def set_status(self, status: OSHScanStatus) -> None:
with sa_session_transaction(commit=True) as session:
self.status = status
session.add(self)

def set_url(self, url: str) -> None:
with sa_session_transaction(commit=True) as session:
self.url = url
session.add(self)

def set_issues_added_url(self, issues_added_url: str) -> None:
with sa_session_transaction(commit=True) as session:
self.issues_added_url = issues_added_url
session.add(self)

def set_issues_fixed_url(self, issues_fixed_url: str) -> None:
with sa_session_transaction(commit=True) as session:
self.issues_fixed_url = issues_fixed_url
session.add(self)

def set_scan_results_url(self, scan_results_url: str) -> None:
with sa_session_transaction(commit=True) as session:
self.scan_results_url = scan_results_url
session.add(self)

@classmethod
def get_by_task_id(cls, task_id: int) -> Optional["OSHScanModel"]:
with sa_session_transaction() as session:
return session.query(cls).filter_by(task_id=task_id).first()


@cached(cache=TTLCache(maxsize=2048, ttl=(60 * 60 * 24)))
def get_usage_data(datetime_from=None, datetime_to=None, top=10) -> dict:
"""
Expand Down
4 changes: 4 additions & 0 deletions packit_service/worker/allowlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
ReleaseEvent,
TestingFarmResultsEvent,
CheckRerunEvent,
OpenScanHubTaskFinishedEvent,
OpenScanHubTaskStartedEvent,
)
from packit_service.worker.events.gitlab import ReleaseGitlabEvent
from packit_service.worker.events.koji import KojiBuildEvent, KojiBuildTagEvent
Expand Down Expand Up @@ -485,6 +487,8 @@ def check_and_report(
KojiBuildTagEvent,
CheckRerunEvent,
NewHotnessUpdateEvent,
OpenScanHubTaskFinishedEvent,
OpenScanHubTaskStartedEvent,
): self._check_unchecked_event,
(
ReleaseEvent,
Expand Down
26 changes: 26 additions & 0 deletions packit_service/worker/checker/open_scan_hub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright Contributors to the Packit project.
# SPDX-License-Identifier: MIT

import logging

from packit.config import (
aliases,
)
from packit_service.worker.checker.abstract import Checker

logger = logging.getLogger(__name__)


class RawhideX86Target(
Checker,
):
def pre_check(self) -> bool:
branches = aliases.get_build_targets(
*self.job_config.targets,
)
if "fedora-rawhide-x86_64" not in branches:
logger.debug(
"Skipping job configuration with no fedora-rawhide-x86_64 target."
)
return False
return True
7 changes: 7 additions & 0 deletions packit_service/worker/events/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@

from packit_service.worker.events.vm_image import VMImageBuildResultEvent

from packit_service.worker.events.open_scan_hub import (
OpenScanHubTaskFinishedEvent,
OpenScanHubTaskStartedEvent,
)

__all__ = [
Event.__name__,
EventData.__name__,
Expand Down Expand Up @@ -86,4 +91,6 @@
ReleaseGitlabEvent.__name__,
TagPushGitlabEvent.__name__,
PullRequestFlagPagureEvent.__name__,
OpenScanHubTaskFinishedEvent.__name__,
OpenScanHubTaskStartedEvent.__name__,
]
32 changes: 28 additions & 4 deletions packit_service/worker/events/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,24 @@ def from_event_dict(cls, event: dict):
branches_override=branches_override,
)

def to_event(self) -> "Event":
"""
Create an instance of Event class from the data in this class.
"""
mod = __import__("packit_service.worker.events", fromlist=[self.event_type])
kls = getattr(mod, self.event_type)
kwargs = copy.copy(self.event_dict)
# The following data should be reconstructed by the Event instance (when needed)
kwargs.pop("event_type", None)
kwargs.pop("event_id", None)
kwargs.pop("task_accepted_time", None)
kwargs.pop("build_targets_override", None)
kwargs.pop("tests_targets_override", None)
kwargs.pop("branches_override", None)
pr_id = kwargs.pop("_pr_id", None)
kwargs["pr_id"] = pr_id
return kls(**kwargs)

@property
def project(self):
if not self._project:
Expand Down Expand Up @@ -367,12 +385,17 @@ def store_packages_config(self):
self.db_project_event.set_packages_config(package_config_dict)

def get_non_serializable_attributes(self):
"""List here both non serializable attributes and attributes that
we want to skip from the dict because are not needed to re-create
the event.
"""
return [
"_db_project_object",
"_db_project_event",
"_project",
"_base_project",
"_package_config",
"_package_config_searched",
]

def get_dict(self, default_dict: Optional[Dict] = None) -> dict:
Expand Down Expand Up @@ -618,10 +641,11 @@ def get_all_build_targets_by_status(
statuses_to_filter_with=statuses_to_filter_with,
)

def get_dict(self, default_dict: Optional[Dict] = None) -> dict:
result = super().get_dict()
result.pop("_pull_request_object")
return result
def get_non_serializable_attributes(self):
return super().get_non_serializable_attributes() + [
"fail_when_config_file_missing",
"_pull_request_object",
]


class AbstractResultEvent(AbstractForgeIndependentEvent):
Expand Down
91 changes: 91 additions & 0 deletions packit_service/worker/events/open_scan_hub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Copyright Contributors to the Packit project.
# SPDX-License-Identifier: MIT

import enum
from typing import Optional
from logging import getLogger

from ogr.abstract import GitProject
from packit_service.config import ServiceConfig
from packit_service.worker.events.event import AbstractResultEvent
from packit_service.models import (
AbstractProjectObjectDbType,
ProjectEventModel,
OSHScanModel,
)

logger = getLogger(__name__)


class OpenScanHubTaskAbstractEvent(AbstractResultEvent):

def __init__(
self,
task_id: int,
commit_sha: Optional[str] = None,
**kwargs,
):
super().__init__(**kwargs)

self.task_id = task_id
self.commit_sha = commit_sha

self.scan = OSHScanModel.get_by_task_id(task_id)
self.build = None
if not self.scan:
logger.warning(
f"Scan with id {task_id} not found in the database."
" It should have been created when receiving the CoprBuildEndEvent"
" and should have been associated with the copr build."
)
else:
self.build = self.scan.copr_build_target
project_event = self.build.get_project_event_model()
# commit_sha is needed by the StatusReporter
# and have to be serialized to be later found in the
# event metadata
self.commit_sha = (
project_event.commit_sha if not self.commit_sha else self.commit_sha
)

def get_db_project_object(self) -> Optional[AbstractProjectObjectDbType]:
return self.build.get_project_event_object()

def get_db_project_event(self) -> Optional[ProjectEventModel]:
return self.build.get_project_event_model()

def get_project(self) -> GitProject:
return ServiceConfig.get_service_config().get_project(
self.db_project_object.project.project_url
)

def get_non_serializable_attributes(self):
# build and scan are not serializable
return super().get_non_serializable_attributes() + ["build", "scan"]


class OpenScanHubTaskFinishedEvent(OpenScanHubTaskAbstractEvent):

class Status(str, enum.Enum):
success = "success"
cancel = "cancel"
interrupt = "interrupt"
fail = "fail"

def __init__(
self,
status: Status,
issues_added_url: str,
issues_fixed_url: str,
scan_results_url: str,
**kwargs,
):
super().__init__(**kwargs)

self.status = status
self.issues_added_url = issues_added_url
self.issues_fixed_url = issues_fixed_url
self.scan_results_url = scan_results_url


class OpenScanHubTaskStartedEvent(OpenScanHubTaskAbstractEvent): ...
Loading
Loading