Skip to content
Open
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
7 changes: 6 additions & 1 deletion packit_service/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ def __init__(
logdetective_enabled: bool = False,
logdetective_url: str = LOGDETECTIVE_PACKIT_SERVER_URL,
logdetective_secret: str = "",
onboarding_secret: str = "",
**kwargs,
):
if "authentication" in kwargs:
Expand Down Expand Up @@ -215,6 +216,9 @@ def __init__(
# Token to be used with Log Detective interface server
self.logdetective_secret = logdetective_secret

# Secret for dist-git onboarding requests
self.onboarding_secret = onboarding_secret

service_config = None

def __repr__(self):
Expand Down Expand Up @@ -248,7 +252,8 @@ def hide(token: str) -> str:
f"logdetective_url='{self.logdetective_url}')"
f"fedora_ci_run_by_default='{self.fedora_ci_run_by_default}', "
f"enabled_projects_for_fedora_ci='{self.enabled_projects_for_fedora_ci}', "
f"disabled_projects_for_fedora_ci='{self.disabled_projects_for_fedora_ci}')"
f"disabled_projects_for_fedora_ci='{self.disabled_projects_for_fedora_ci}', "
f"onboarding_secret='{hide(self.onboarding_secret)}')"
)

@classmethod
Expand Down
63 changes: 63 additions & 0 deletions packit_service/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,69 @@ def from_number(number: int):
# Default URL of the logdetective-packit interface server for sending the Log Detective requests.
LOGDETECTIVE_PACKIT_SERVER_URL = "https://logdetective01.fedorainfracloud.org"

DG_ONBOARDING_TITLE = "Add initial Packit configuration"
DG_ONBOARDING_DESCRIPTION = """
Hello,

thank you for introducing a new package to Fedora!

Let us present you [Packit](https://packit.dev/), an automation for Fedora releases.
We are sending a configuration file with a basic setup.
The automation will be enabled by merging this pull-request into the `rawhide` branch.

If you have any question or concern, ask here or on `#packit:fedora.im` Matrix channel
and Packit team will help.

If you look at the configuration file, you can see that Packit is configured to do 3 jobs for you:
* [`pull_from_upstream`](https://packit.dev/docs/configuration/downstream/pull_from_upstream):
Create a new set of pull-requests when there is a new upstream release.
(The specfile-changes and sources are taken care of. Packit is notified about new release from
[Release Monitoring](https://release-monitoring.org/) service. Check if your project is there.)
* [`koji_build`](https://packit.dev/docs/configuration/downstream/koji_build):
Submit a Koji build as reaction to a merged pull request.
* [`bodhi_update`](https://packit.dev/docs/configuration/downstream/bodhi_update):
Create a Bodhi update as a reaction to a succesful Koji build.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Create a Bodhi update as a reaction to a succesful Koji build.
Create a Bodhi update as a reaction to a successful Koji build.


These jobs are independent so you can pick just those that are relevant to you.
For each, you can also configure Fedora/EPEL versions other than Rawhide that the jobs
should be run for.

You can also further tweak the process. A few handy options are prepared for you
in the confguration file to uncomment. Rest can be found in
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
in the confguration file to uncomment. Rest can be found in
in the configuration file to uncomment. Rest can be found in

[the documentation](https://packit.dev/docs/fedora-releases-guide/dist-git-onboarding).
In case you have a group of dependent packages, you might want to take a look at
[how to configure multi-package updates]
(https://packit.dev/docs/fedora-releases-guide/releasing-multiple-packages).
Comment on lines +376 to +377
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[how to configure multi-package updates]
(https://packit.dev/docs/fedora-releases-guide/releasing-multiple-packages).
[how to configure multi-package updates](https://packit.dev/docs/fedora-releases-guide/releasing-multiple-packages).



Things you still need to be aware of:

* The package maintenance is still your responsibility -- Packit is just a handy tool that can
save you some time.
* When Packit introduces new releases in form of the pull-request, it's your responsibility
to check the pull-request including the newly-introduced source. This is the place where human
intervention is required.
* Be aware that there are other packages and packagers and that you might break someone else's work
by using Packit in a wrong way. (E.g. be careful about dependent packages since there is
Comment on lines +387 to +388
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Be aware that there are other packages and packagers and that you might break someone else's work
by using Packit in a wrong way. (E.g. be careful about dependent packages since there is
* Be aware that there are other packages and packagers and automated updates by using Packit may affect them.
(E.g. be careful about dependent packages since there is

I would just use a bit milder tone here.

no automatic check for these in place.)
* Check [Fedora updates policy](https://docs.fedoraproject.org/en-US/fesco/Updates_Policy/).
* Check [Fedora Packaging guidelines](https://docs.fedoraproject.org/en-US/packaging-guidelines/)
including the specifics for your package type.
* Consult the approach with other maintainers of this package and care about the Packit results
so you don't introduce spam and extra work for others.
* Speaking of notifications -- you might want to setup a rule on
[Fedora Notifications](https://notifications.fedoraproject.org/) so you won't miss
anything important, since `packit` FAS account will be the actor of the jobs.


In case you don't want to receive these pull-requests in the future, you can use
the `--onboard-packit no` option when running `fedpkg request-repo`.
Comment on lines +400 to +401
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we could also mention that they can easily disable packit by either removing particular jobs or the file itself (emphasis on rawhide branch)



I hope you will be happy with the automation!
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
I hope you will be happy with the automation!
We hope you will be happy with the automation!

*[Packit team](https://packit.dev/#contact)*
"""

# CI Transition comment for Fedora dist-git PRs
# TODO: Remove this after March 2026
# https://github.com/packit/packit-service/issues/3008
Expand Down
25 changes: 25 additions & 0 deletions packit_service/events/onboarding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright Contributors to the Packit project.
# SPDX-License-Identifier: MIT

from typing import Optional

from packit.config import PackageConfig

from .abstract.base import ForgeIndependent


class Request(ForgeIndependent):
@classmethod
def event_type(cls) -> str:
return "onboarding.Request"

def __init__(
self,
package: str,
open_pr: bool = True,
):
super().__init__(project_url=f"https://src.fedoraproject.org/rpms/{package}")
self.open_pr = open_pr

def get_packages_config(self) -> Optional[PackageConfig]:
return None
1 change: 1 addition & 0 deletions packit_service/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class ServiceConfigSchema(UserConfigSchema):
logdetective_enabled = fields.Bool(missing=False, default=False)
logdetective_url = fields.String()
logdetective_secret = fields.String()
onboarding_secret = fields.String()

@post_load
def make_instance(self, data, **kwargs):
Expand Down
2 changes: 2 additions & 0 deletions packit_service/service/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from packit_service.service.api.installations import ns as installations_ns
from packit_service.service.api.koji_builds import koji_builds_ns
from packit_service.service.api.koji_tag_requests import koji_tag_requests_ns
from packit_service.service.api.onboarding import ns as onboarding_ns
from packit_service.service.api.osh_scans import ns as osh_scans_ns
from packit_service.service.api.projects import ns as projects_ns
from packit_service.service.api.propose_downstream import ns as propose_downstream_ns
Expand Down Expand Up @@ -48,3 +49,4 @@
api.add_namespace(system_ns)
api.add_namespace(bodhi_updates_ns)
api.add_namespace(osh_scans_ns)
api.add_namespace(onboarding_ns)
81 changes: 81 additions & 0 deletions packit_service/service/api/onboarding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Copyright Contributors to the Packit project.
# SPDX-License-Identifier: MIT

import logging
from http import HTTPStatus
from os import getenv

from flask import request
from flask_restx import Namespace, Resource, fields

from packit_service.celerizer import celery_app
from packit_service.config import ServiceConfig
from packit_service.constants import CELERY_DEFAULT_MAIN_TASK_NAME
from packit_service.service.api.errors import ValidationFailed

logger = logging.getLogger("packit_service")

config = ServiceConfig.get_service_config()

ns = Namespace("onboarding", description="Packit dist-git onboarding")

payload = ns.model(
"Packit dist-git onboarding request",
{
"package": fields.String(required=True, example="packit"),
"open_pr": fields.Boolean(required=True, default=True),
"token": fields.String(required=True, example="HERE-IS-A-VALID-TOKEN"),
},
)


@ns.route("/request")
class OnboardingRequest(Resource):
@ns.response(HTTPStatus.OK.value, "Request has been accepted")
@ns.response(HTTPStatus.BAD_REQUEST.value, "Bad request data")
@ns.response(HTTPStatus.UNAUTHORIZED.value, "Secret validation failed")
@ns.expect(payload)
def post(self):
msg = request.json

if not msg:
logger.debug("/onboarding/request: we haven't received any JSON data.")
return "We haven't received any JSON data.", HTTPStatus.BAD_REQUEST

try:
self.validate_onboarding_request()
except ValidationFailed as exc:
logger.info(f"/onboarding/request {exc}")
return str(exc), HTTPStatus.UNAUTHORIZED

msg["source"] = "onboarding"

celery_app.send_task(
name=getenv("CELERY_MAIN_TASK_NAME") or CELERY_DEFAULT_MAIN_TASK_NAME,
kwargs={
"event": msg,
"source": "onboarding",
"event_type": "request",
},
)

return "Onboarding request accepted", HTTPStatus.OK

@staticmethod
def validate_onboarding_request():
if not config.onboarding_secret:
msg = "Onboarding secret not specified in config"
logger.error(msg)
raise ValidationFailed(msg)

if not (token := request.json.get("token")):
msg = "The request doesn't contain any token"
logger.info(msg)
raise ValidationFailed(msg)

if token == config.onboarding_secret:
return

msg = "Invalid onboarding secret provided"
logger.warning(msg)
raise ValidationFailed(msg)
18 changes: 18 additions & 0 deletions packit_service/worker/checker/onboarding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright Contributors to the Packit project.
# SPDX-License-Identifier: MIT

import logging

from packit.constants import CONFIG_FILE_NAMES

from packit_service.worker.checker.abstract import Checker

logger = logging.getLogger(__name__)


class ProjectIsNotOnboarded(Checker):
def pre_check(self) -> bool:
if any(f for f in self.project.get_files(ref="rawhide") if f in CONFIG_FILE_NAMES):
logger.info(f"Package {self.project.repo} is already onboarded")
return False
return True
1 change: 1 addition & 0 deletions packit_service/worker/handlers/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ class TaskName(str, enum.Enum):
tag_into_sidetag = "task.tag_into_sidetag"
openscanhub_task_finished = "task.openscanhub_task_finished"
openscanhub_task_started = "task.openscanhub_task_started"
onboarding_request = "task.run_onboarding_request_handler"
downstream_koji_scratch_build = "task.run_downstream_koji_scratch_build_handler"
downstream_koji_scratch_build_report = "task.run_downstream_koji_scratch_build_report_handler"
downstream_koji_eln_scratch_build = "task.run_downstream_koji_eln_scratch_build_handler"
Expand Down
103 changes: 103 additions & 0 deletions packit_service/worker/handlers/onboarding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Copyright Contributors to the Packit project.
# SPDX-License-Identifier: MIT

"""
This file defines classes for job handlers specific to onboarding tasks
"""

import logging

from packit.cli.dist_git_init import (
COMMIT_MESSAGE,
CONFIG_FILE_NAME,
ONBOARD_BRANCH_NAME,
DistGitInitializer,
)
from packit.config.config import Config
from packit.config.package_config import PackageConfig

from packit_service.constants import DG_ONBOARDING_DESCRIPTION, DG_ONBOARDING_TITLE
from packit_service.events import (
onboarding,
)
from packit_service.worker.checker.abstract import Checker
from packit_service.worker.checker.onboarding import ProjectIsNotOnboarded
from packit_service.worker.handlers.abstract import (
JobHandler,
TaskName,
reacts_to,
)
from packit_service.worker.mixin import ConfigFromEventMixin, PackitAPIWithDownstreamMixin
from packit_service.worker.result import TaskResults

logger = logging.getLogger(__name__)


@reacts_to(event=onboarding.Request)
class OnboardingRequestHandler(
JobHandler,
ConfigFromEventMixin,
PackitAPIWithDownstreamMixin,
):
task_name = TaskName.onboarding_request

@staticmethod
def get_checkers() -> tuple[type[Checker], ...]:
return (ProjectIsNotOnboarded,)

def _run(self) -> TaskResults:
package = self.project.repo
logger.debug(f"Running onboarding for {package}")

# generate and load config
initializer = DistGitInitializer(
config=Config(),
path_or_url="",
upstream_git_url=None,
)
self.package_config = self.job_config = PackageConfig.get_from_dict(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we actually need these?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initializer.package_config_dict | {"downstream_package_name": package},
)

self.perform_onboarding(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need some exception handling here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure... it should be retried automatically on common exceptions, but perhaps it would be better to be explicit about it.

config=initializer.package_config_content,
open_pr=self.data.event_dict.get("open_pr", True),
)

return TaskResults(success=True, details={})

def perform_onboarding(self, config: str, open_pr: bool) -> None:
# clone the repo and fetch rawhide
self.packit_api.dg.create_branch(
"rawhide",
base="remotes/origin/rawhide",
setup_tracking=True,
)
self.packit_api.dg.update_branch("rawhide")
self.packit_api.dg.switch_branch("rawhide", force=True)

if open_pr:
self.packit_api.dg.create_branch(ONBOARD_BRANCH_NAME)
self.packit_api.dg.switch_branch(ONBOARD_BRANCH_NAME, force=True)
self.packit_api.dg.reset_workdir()

working_dir = self.packit_api.dg.local_project.working_dir

# create config file
(working_dir / CONFIG_FILE_NAME).write_text(config)

self.packit_api.dg.commit(
title=COMMIT_MESSAGE,
msg="",
prefix="",
)

if open_pr:
self.packit_api.push_and_create_pr(
pr_title=DG_ONBOARDING_TITLE,
pr_description=DG_ONBOARDING_DESCRIPTION,
git_branch="rawhide",
repo=self.packit_api.dg,
)
else:
self.packit_api.dg.push(refspec="HEAD:rawhide")
12 changes: 12 additions & 0 deletions packit_service/worker/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
github,
koji,
logdetective,
onboarding,
pagure,
testing_farm,
)
Expand Down Expand Up @@ -83,6 +84,7 @@
RetriggerDownstreamKojiBuildHandler,
TagIntoSidetagHandler,
)
from packit_service.worker.handlers.onboarding import OnboardingRequestHandler
from packit_service.worker.helpers.build import (
BaseBuildJobHelper,
CoprBuildJobHelper,
Expand Down Expand Up @@ -358,6 +360,16 @@ def process(self) -> list[TaskResults]:
).apply_async()
# should we comment about not processing if the comment is not
# on the issue created by us or not in packit/notifications?
elif isinstance(self.event, onboarding.Request):
if OnboardingRequestHandler.pre_check(
package_config=None,
job_config=None,
event=self.event.get_dict(),
):
OnboardingRequestHandler.get_signature(
event=self.event,
job=None,
).apply_async()
else:
if (
isinstance(
Expand Down
Loading
Loading