Fedora Messaging Notifications¶
fmn
is a family of systems to manage end-user notifications triggered by
fedora-messaging
, it provides a single place for all
applications using fedora-messaging
to notify users of events.
Documentation
Installation¶
User Guide¶
fmn package¶
Subpackages¶
fmn.api package¶
Subpackages¶
fmn.api.handlers package¶
Submodules¶
fmn.api.handlers.admin module¶
- async fmn.api.handlers.admin.get_rules(disabled: bool | None = None, username: str | None = None, identity: Identity = Depends(get_identity_admin), db_session: AsyncSession = Depends(gen_db_session))[source]¶
fmn.api.handlers.misc module¶
- async fmn.api.handlers.misc.get_artifacts(names: list[str] = Query([]), users: list[str] = Query([]), groups: list[str] = Query([]), distgit_proxy: PagureAsyncProxy = Depends(get_distgit_proxy))[source]¶
This handler queries artifacts from Pagure
Proxying Pagure queries lets the API cache results to reduce load on the backend service.
- Parameters:
names – Name patterns of artifacts which should be returned
users – Names of users whose artifacts should be returned
groups – Names of groups whose artifacts should be returned
fmn.api.handlers.users module¶
- async fmn.api.handlers.users.create_user_rule(username, rule: NewRule, identity: Identity = Depends(IdentityFactory), db_session: AsyncSession = Depends(gen_db_session))[source]¶
- async fmn.api.handlers.users.delete_user_rule(username: str, id: int, identity: Identity = Depends(IdentityFactory), db_session: AsyncSession = Depends(gen_db_session))[source]¶
- async fmn.api.handlers.users.edit_user_rule(username: str, id: int, rule: Rule, identity: Identity = Depends(IdentityFactory), db_session: AsyncSession = Depends(gen_db_session))[source]¶
- async fmn.api.handlers.users.get_me(identity: Identity = Depends(IdentityFactory), db_session: AsyncSession = Depends(gen_db_session))[source]¶
- async fmn.api.handlers.users.get_user_destinations(username, fasjson_proxy: FASJSONAsyncProxy = Depends(get_fasjson_proxy))[source]¶
- async fmn.api.handlers.users.get_user_groups(username, fasjson_proxy: FASJSONAsyncProxy = Depends(get_fasjson_proxy))[source]¶
- async fmn.api.handlers.users.get_user_info(username, fasjson_proxy: FASJSONAsyncProxy = Depends(get_fasjson_proxy))[source]¶
- async fmn.api.handlers.users.get_user_rule(username: str, id: int, identity: Identity = Depends(IdentityFactory), db_session: AsyncSession = Depends(gen_db_session))[source]¶
fmn.api.handlers.utils module¶
Submodules¶
fmn.api.api_models module¶
- class fmn.api.api_models.Artifact(*, type: ArtifactType, name: str)[source]¶
Bases:
BaseModel
- type: ArtifactType¶
- class fmn.api.api_models.ArtifactOptionsGroup(*, label: str, options: list[fmn.api.api_models.Option[Artifact]])[source]¶
Bases:
BaseModel
- options: list[fmn.api.api_models.Option[Artifact]]¶
- class fmn.api.api_models.ArtifactsFollowedTrackingRule(*, name: Literal['artifacts-followed'], params: list[dict[Literal['name', 'type'], str]])[source]¶
Bases:
BaseModel
- class fmn.api.api_models.Filters(*, applications: list[str] = [], severities: list[str] = [], topic: str | None = None, my_actions: bool = False)[source]¶
Bases:
BaseModel
- class fmn.api.api_models.GenerationRule(*, id: int | None = None, destinations: list[fmn.api.api_models.Destination], filters: Filters)[source]¶
Bases:
BaseModel
- class Config[source]¶
Bases:
object
- getter_dict¶
alias of
GRGetterDict
- destinations: list[fmn.api.api_models.Destination]¶
- class fmn.api.api_models.ListParamTrackingRule(*, name: Literal['artifacts-owned', 'artifacts-group-owned', 'users-followed'], params: list[str])[source]¶
Bases:
BaseModel
- class fmn.api.api_models.NewRule(*, name: str | None = None, disabled: bool = False, tracking_rule: ListParamTrackingRule | NoParamTrackingRule | ArtifactsFollowedTrackingRule, generation_rules: list[fmn.api.api_models.GenerationRule])[source]¶
Bases:
BaseModel
- generation_rules: list[fmn.api.api_models.GenerationRule]¶
- tracking_rule: ListParamTrackingRule | NoParamTrackingRule | ArtifactsFollowedTrackingRule¶
- class fmn.api.api_models.NoParamTrackingRule(*, name: Literal['related-events'], params: str | None = None)[source]¶
Bases:
BaseModel
- class fmn.api.api_models.Option(*, label: str, value: T = None)[source]¶
Bases:
GenericModel
,Generic
[T
]- value: T¶
- class fmn.api.api_models.Rule(*, name: str | None = None, disabled: bool = False, tracking_rule: ListParamTrackingRule | NoParamTrackingRule | ArtifactsFollowedTrackingRule, generation_rules: list[fmn.api.api_models.GenerationRule], id: int, user: User, generated_last_week: int = 0)[source]¶
Bases:
NewRule
fmn.api.auth module¶
- class fmn.api.auth.Identity(*, name: str, admin: bool, expires_at: float, user_info: dict[str, Any])[source]¶
Bases:
BaseModel
- exception fmn.api.auth.TokenExpired[source]¶
Bases:
ValueError
fmn.api.cli module¶
fmn.api.database module¶
- async fmn.api.database.gen_db_session() Iterator[AsyncSession] [source]¶
Generate database sessions for FastAPI request handlers.
This lets users declare the session as a dependency in request handler functions, e.g.:
@app.get("/path") def process_path(db_session: AsyncSession = Depends(gen_db_session)): query = select(Model).filter_by(...) result = await db_session.execute(query) ...
- Returns:
A
sqlalchemy.ext.asyncio.AsyncSession
object for the current request
fmn.api.main module¶
fmn.api.messaging module¶
fmn.backends package¶
Submodules¶
fmn.backends.base module¶
- class fmn.backends.base.APIClient(base_url: str | None = None, **kwargs)[source]¶
Bases:
ABC
- abstract determine_next_page_params(url: str, params: dict, result: dict) tuple[str, dict] | tuple[None, None] [source]¶
Determine parameters for next page.
- Parameters:
url – API endpoint URL
params – Query parameters (can be modified)
result – Result dictionary of previous query
- Returns:
Tuple of (new URL, new params dict) or (None, None) if last page
- exception fmn.backends.base.PaginationRecursionError[source]¶
Bases:
RuntimeError
fmn.backends.fasjson module¶
- class fmn.backends.fasjson.FASJSONAsyncProxy(base_url: str)[source]¶
Bases:
APIClient
Proxy for the FASJSON API endpoints used in FMN
- API_VERSION = 'v1'¶
- FAS_TOPIC_RE = re.compile('fas\\.(?P<usergroup>user|group)\\.(?P<event>member\\.sponsor|create|update)$')¶
- determine_next_page_params(url: str, params: dict, result: dict) tuple[str, dict] | tuple[None, None] [source]¶
Determine parameters for next page.
- Parameters:
url – API endpoint URL
params – Query parameters (can be modified)
result – Result dictionary of previous query
- Returns:
Tuple of (new URL, new params dict) or (None, None) if last page
- fmn.backends.fasjson.get_fasjson_proxy() FASJSONAsyncProxy [source]¶
fmn.backends.pagure module¶
- class fmn.backends.pagure.PagureAsyncProxy(base_url: str | None = None, **kwargs)[source]¶
Bases:
APIClient
Proxy for the FASJSON API endpoints used in FMN
- API_VERSION = '0'¶
- PROJECT_TOPIC_RE = re.compile('pagure\\.project\\.(?P<usergroup>user|group)\\.(?P<action>access\\.updated|added|removed)$')¶
- determine_next_page_params(url: str, params: dict, result: dict) tuple[str, dict] | tuple[None, None] [source]¶
Determine parameters for next page.
- Parameters:
url – API endpoint URL
params – Query parameters (can be modified)
result – Result dictionary of previous query
- Returns:
Tuple of (new URL, new params dict) or (None, None) if last page
- async get_group_projects(*, name: str, acl: PagureRole | None = None) list[dict[str, Any]] [source]¶
- async get_project_groups(*, project_path: str, roles: PagureRole = PagureRole.GROUP_ROLES_MAINTAINER) list[str] [source]¶
- async get_project_users(*, project_path: str, roles: PagureRole = PagureRole.USER_ROLES_MAINTAINER) list[str] [source]¶
- class fmn.backends.pagure.PagureRole(value)[source]¶
Bases:
IntFlag
An enumeration.
- ADMIN = 2¶
- COLLABORATOR = 8¶
- COMMIT = 4¶
- GROUP_ROLES = 30¶
- GROUP_ROLES_MAINTAINER = 14¶
- GROUP_ROLES_MAINTAINER_SET = {PagureRole.ADMIN, PagureRole.COMMIT, PagureRole.COLLABORATOR}¶
- GROUP_ROLES_SET = {PagureRole.ADMIN, PagureRole.COMMIT, PagureRole.COLLABORATOR, PagureRole.TICKET}¶
- OWNER = 1¶
- TICKET = 16¶
- USER_ROLES = 31¶
- USER_ROLES_MAINTAINER = 15¶
- USER_ROLES_MAINTAINER_SET = {PagureRole.OWNER, PagureRole.ADMIN, PagureRole.COMMIT, PagureRole.COLLABORATOR}¶
- USER_ROLES_SET = {PagureRole.OWNER, PagureRole.ADMIN, PagureRole.COMMIT, PagureRole.COLLABORATOR, PagureRole.TICKET}¶
- fmn.backends.pagure.get_distgit_proxy() PagureAsyncProxy [source]¶
fmn.cache package¶
Submodules¶
fmn.cache.base module¶
fmn.cache.cli module¶
fmn.cache.rules module¶
fmn.cache.tracked module¶
- class fmn.cache.tracked.Tracked(packages: set = <factory>, containers: set = <factory>, modules: set = <factory>, flatpaks: set = <factory>, usernames: set = <factory>, agent_name: set = <factory>)[source]¶
Bases:
object
- class fmn.cache.tracked.TrackedCache(requester: Requester, rules_cache: RulesCache)[source]¶
Bases:
CachedValue
Used to quickly know whether we want to process an incoming message.
It can be called outside of the message-processing loop to refresh the cache.
Cases when the cache should be refreshed: - a rule is changed - a user is added or removed to/from a group - an artifact has their owners (users or groups) changed
The Consumer listens to those events as messages on the bus.
If this happens too frequently, we can just refresh after X minutes have passed and tell users that their changes will take X minutes to be active.
- name = 'tracked'¶
fmn.cache.util module¶
- fmn.cache.util.cache_arg(arg: str, scope: str | None = None) Callable[[str, str | None], Any] [source]¶
Generate a cached function for cashews decorator arguments.
The purpose of this is to evaluate the settings late (i.e. the first time a decorated callable is called), so customized settings can be applied effectively.
- fmn.cache.util.cache_ttl(scope: str | None = None) Callable[[str, str | None], Any] ¶
Generate a cached function for cashews decorator arguments.
The purpose of this is to evaluate the settings late (i.e. the first time a decorated callable is called), so customized settings can be applied effectively.
- fmn.cache.util.get_pattern_for_cached_calls(func: Callable, **kwargs: dict[str, Any]) list[str] [source]¶
- fmn.cache.util.lock_ttl(scope: str | None = None) Callable[[str, str | None], Any] ¶
Generate a cached function for cashews decorator arguments.
The purpose of this is to evaluate the settings late (i.e. the first time a decorated callable is called), so customized settings can be applied effectively.
fmn.consumer package¶
Submodules¶
fmn.core package¶
Submodules¶
fmn.core.amqp module¶
fmn.core.cli module¶
fmn.core.config module¶
- class fmn.core.config.CacheArgsModel(*, ttl: int | float | str | timedelta | None = None, lock_ttl: int | float | str | timedelta | None = None, early_ttl: int | float | str | timedelta | None = None)[source]¶
Bases:
BaseModel
- class fmn.core.config.CacheModel(*, url: UrlValue = 'mem://', setup_args: dict[str, Any] | None = None, default_args: CacheArgsModel = CacheArgsModel(ttl='1h', lock_ttl=None, early_ttl=None), scoped_args: CacheScopedArgsModel = CacheScopedArgsModel(tracked=CacheArgsModel(ttl='1d', lock_ttl='1h', early_ttl='20h'), rules=CacheArgsModel(ttl='1d', lock_ttl='5m', early_ttl='20h'), pagure=None, fasjson=None))[source]¶
Bases:
BaseModel
- default_args: CacheArgsModel¶
- scoped_args: CacheScopedArgsModel¶
- url: UrlValue¶
- class fmn.core.config.CacheScopedArgsModel(*, tracked: CacheArgsModel = CacheArgsModel(ttl='1d', lock_ttl='1h', early_ttl='20h'), rules: CacheArgsModel = CacheArgsModel(ttl='1d', lock_ttl='5m', early_ttl='20h'), pagure: CacheArgsModel | None = None, fasjson: CacheArgsModel | None = None)[source]¶
Bases:
BaseModel
- fasjson: CacheArgsModel | None¶
- pagure: CacheArgsModel | None¶
- rules: CacheArgsModel¶
- tracked: CacheArgsModel¶
- class fmn.core.config.DBModel(*, sqlalchemy: SQLAlchemyModel = SQLAlchemyModel(url='sqlite:///:memory:', echo=False))[source]¶
Bases:
BaseModel
- sqlalchemy: SQLAlchemyModel¶
- class fmn.core.config.SQLAlchemyModel(*, url: UrlValue = 'sqlite:///:memory:', echo: bool = False, **extra_data: Any)[source]¶
Bases:
BaseModel
- url: UrlValue¶
- class fmn.core.config.ServicesModel(*, fasjson_url: UrlValue = 'https://fasjson.fedoraproject.org', distgit_url: UrlValue = 'https://src.fedoraproject.org')[source]¶
Bases:
BaseModel
- distgit_url: UrlValue¶
- fasjson_url: UrlValue¶
- class fmn.core.config.Settings(_env_file: str | PathLike | List[str | PathLike] | Tuple[str | PathLike, ...] | None = '<object object>', _env_file_encoding: str | None = None, _env_nested_delimiter: str | None = None, _secrets_dir: str | PathLike | None = None, *, cors_origins: str = 'https://notifications.fedoraproject.org', oidc_provider_url: str = 'https://id.fedoraproject.org/openidc', oidc_conf_endpoint: str = '/.well-known/openid-configuration', oidc_token_info_endpoint: str = '/TokenInfo', oidc_user_info_endpoint: str = '/UserInfo', oidc_client_id: str = '0123456789abcdef0123456789abcdef', oidc_client_secret: str = '0123456789abcdef0123456789abcdef', admin_groups: list[str] = ['sysadmin-main'], oidc_conf_url: str | None = None, oidc_token_info_url: str | None = None, id_cache_gc_interval: int = 300, database: DBModel = DBModel(sqlalchemy=SQLAlchemyModel(url='sqlite:///:memory:', echo=False)), cache: CacheModel = CacheModel(url='mem://', setup_args=None, default_args=CacheArgsModel(ttl='1h', lock_ttl=None, early_ttl=None), scoped_args=CacheScopedArgsModel(tracked=CacheArgsModel(ttl='1d', lock_ttl='1h', early_ttl='20h'), rules=CacheArgsModel(ttl='1d', lock_ttl='5m', early_ttl='20h'), pagure=None, fasjson=None)), services: ServicesModel = ServicesModel(fasjson_url='https://fasjson.fedoraproject.org', distgit_url='https://src.fedoraproject.org'))[source]¶
Bases:
BaseSettings
- cache: CacheModel¶
- services: ServicesModel¶
fmn.core.constants module¶
fmn.core.version module¶
fmn.database package¶
Subpackages¶
fmn.database.model package¶
Submodules¶
fmn.database.model.destination module¶
fmn.database.model.filter module¶
fmn.database.model.generated module¶
fmn.database.model.generation_rule module¶
fmn.database.model.rule module¶
- class fmn.database.model.rule.Rule(**kwargs)[source]¶
Bases:
Base
- disabled¶
- generated¶
- generation_rules¶
- id¶
- name¶
Convenience method to query rules and related property objects.
This tells SQLAlchemy to query ORM objects related to a Rule right in the query which is necessary when accessing their respective properties in an async context.
- tracking_rule¶
- user¶
- user_id¶
fmn.database.model.tracking_rule module¶
fmn.database.model.user module¶
Submodules¶
fmn.database.cli module¶
fmn.database.main module¶
- class fmn.database.main.CustomBase[source]¶
Bases:
object
- async classmethod async_get(db_session: AsyncSession, **attrs) Base [source]¶
Get an object from the datbase.
- Parameters:
db_session – The SQLAlchemy session to use
- Returns:
the object
- async classmethod async_get_or_create(db_session: AsyncSession, **attrs) Base [source]¶
Get an object from the database or create if missing.
- Parameters:
db_session – The SQLAlchemy session to use
- Returns:
the object
The returned object will have an (ephemeral) boolean attribute _was_created which allows finding out if it existed previously or not.
fmn.database.setup module¶
fmn.messages package¶
Submodules¶
fmn.messages.base module¶
- class fmn.messages.base.BaseMessage(body=None, headers=None, topic=None, properties=None, severity=None)[source]¶
Bases:
Message
fmn.messages.rule module¶
- class fmn.messages.rule.RuleCreateV1(body=None, headers=None, topic=None, properties=None, severity=None)[source]¶
Bases:
BaseMessage
- body_schema = {'$schema': 'http://json-schema.org/draft-04/schema#', 'description': 'A rule was created', 'id': 'http://fedoraproject.org/message-schema/fmn', 'properties': {'rule': {'properties': {'id': {'description': 'The ID of the rule', 'type': 'integer'}, 'name': {'description': 'The name of the rule', 'type': 'string'}}, 'type': 'object'}, 'user': {'properties': {'name': {'description': 'The FAS username', 'type': 'string'}}, 'type': 'object'}}, 'required': ['rule', 'user'], 'type': 'object'}¶
- topic = 'fmn.rule.update.v1'¶
- class fmn.messages.rule.RuleDeleteV1(body=None, headers=None, topic=None, properties=None, severity=None)[source]¶
Bases:
BaseMessage
- body_schema = {'$schema': 'http://json-schema.org/draft-04/schema#', 'description': 'A rule was deleted', 'id': 'http://fedoraproject.org/message-schema/fmn', 'properties': {'rule': {'properties': {'id': {'description': 'The ID of the rule', 'type': 'integer'}, 'name': {'description': 'The name of the rule', 'type': 'string'}}, 'type': 'object'}, 'user': {'properties': {'name': {'description': 'The FAS username', 'type': 'string'}}, 'type': 'object'}}, 'required': ['rule', 'user'], 'type': 'object'}¶
- topic = 'fmn.rule.delete.v1'¶
- class fmn.messages.rule.RuleUpdateV1(body=None, headers=None, topic=None, properties=None, severity=None)[source]¶
Bases:
BaseMessage
- body_schema = {'$schema': 'http://json-schema.org/draft-04/schema#', 'description': 'A rule was updated', 'id': 'http://fedoraproject.org/message-schema/fmn', 'properties': {'rule': {'properties': {'id': {'description': 'The ID of the rule', 'type': 'integer'}, 'name': {'description': 'The name of the rule', 'type': 'string'}}, 'type': 'object'}, 'user': {'properties': {'name': {'description': 'The FAS username', 'type': 'string'}}, 'type': 'object'}}, 'required': ['rule', 'user'], 'type': 'object'}¶
- topic = 'fmn.rule.update.v1'¶
fmn.rules package¶
Submodules¶
fmn.rules.filter module¶
fmn.rules.notification module¶
- class fmn.rules.notification.EmailNotification(*, protocol: Literal['email'], content: EmailNotificationContent)[source]¶
Bases:
FrozenModel
- content: EmailNotificationContent¶
- class fmn.rules.notification.EmailNotificationContent(*, headers: EmailNotificationHeaders, body: str)[source]¶
Bases:
FrozenModel
- headers: EmailNotificationHeaders¶
- class fmn.rules.notification.EmailNotificationHeaders(*, To: str, Subject: str)[source]¶
Bases:
FrozenModel
- class fmn.rules.notification.IRCNotification(*, protocol: Literal['irc'], content: IRCNotificationContent)[source]¶
Bases:
FrozenModel
- content: IRCNotificationContent¶
- class fmn.rules.notification.IRCNotificationContent(*, to: str, message: str)[source]¶
Bases:
FrozenModel
- class fmn.rules.notification.MatrixNotification(*, protocol: Literal['matrix'], content: MatrixNotificationContent)[source]¶
Bases:
FrozenModel
- content: MatrixNotificationContent¶
- class fmn.rules.notification.MatrixNotificationContent(*, to: str, message: str)[source]¶
Bases:
FrozenModel
- class fmn.rules.notification.Notification(*, __root__: EmailNotification | IRCNotification | MatrixNotification)[source]¶
Bases:
FrozenModel
fmn.rules.requester module¶
fmn.rules.tracking_rules module¶
- class fmn.rules.tracking_rules.ArtifactsFollowed(*args, **kwargs)[source]¶
Bases:
TrackingRule
- class fmn.rules.tracking_rules.ArtifactsGroupOwned(*args, **kwargs)[source]¶
Bases:
TrackingRule
- class fmn.rules.tracking_rules.ArtifactsOwned(*args, **kwargs)[source]¶
Bases:
TrackingRule
- class fmn.rules.tracking_rules.RelatedEvents(*args, **kwargs)[source]¶
Bases:
TrackingRule
fmn.sender package¶
Submodules¶
fmn.sender.cli module¶
fmn.sender.config module¶
fmn.sender.consumer module¶
fmn.sender.email module¶
fmn.sender.handler module¶
fmn.sender.irc module¶
fmn.sender.matrix module¶
Contributor Guide¶
You need to be legally allowed to submit any contribution to this project. What this means in detail
is laid out at the Developer Certificate of Origin website.
The mechanism by which you certify this is adding a Signed-off-by
trailer to git commit log
messages, you can do this by using the --signoff/-s
option to git commit
.
Changelog¶
Significant changes should appear in the ChangeLog. To that end, contributors must create a changelog entry using Towncrier and the appropriate category.
The format is based on Keep a Changelog.
The syntax to create a changelog entry is the following:
poetry run towncrier create -c "Added a cool feature" issuenumber.category.md
Where issuenumber
is the issue number in Github, and category
is one of:
security
in case of vulnerabilitiesremoved
for now removed featuresdeprecated
for soon-to-be removed featuresadded
for new featureschanged
for changes to existing functionalityfixed
for any bug fixes
For example:
poetry run towncrier create -c "Added a cool feature!" 42.added.md
If the change does not fit into any category, prefix the filename with a “plus”, for example:
poetry run towncrier create -c "A fix without an issue number!" +something-unique.fixed.md
Components¶
FMN consists of several components, most of which run in Fedora Infrastructure.
Message Consumer¶
The Message Consumer reads messages carried on the Message Bus. If a message is matched by a rule, it triggers the appropriate Notification Sender to send an email or chat message to the user who set up the rule.
It queries FASJSON and dist-git (Pagure) for information about users, groups and projects and stores the information in the Shared Service Cache (which is also used by the Configuration Backend) and is responsible for invalidating cached entries when receiving messages that affected objects have changed.
There can be multiple instances of the Message consumer, each will process incoming messages in a round-robin fashion.
Notification Sender¶
A Notification Sender receives messages over a private message queue from the Message Consumer, triggering it to send notifications to users over various communication channels, such as IRC, email or Matrix. These messages contain all information a sender needs to perform the work, no additional lookups in other services are necessary.
Because of how IRC and Matrix work, there can only be one instance of their respective senders.
Configuration Frontend¶
The Configuration Frontend is an application running in a web browser and is implemented using
Vue.js
. It lets users configure rules specifying which bus messages they want to be notified about,
e.g.:
Regarding certain artifacts they’re interested in,
regarding themselves or groups they are a member of,
regarding someone else, e.g. a mentee or someone they sponsored.
It communicates with the Configuration Backend over a REST web API which also acts as an intermediary cache to services like FASJSON and dist-git (Pagure).
The Configuration Frontend lets users authenticate themselves with Ipsilon (our OpenID Connect identity provider) and uses the token it receives to establish a user’s identity with the Configuration Backend.
Configuration Backend¶
The Configuration Backend is a service implementing a REST web API using FastAPI and Python. It lets users manage their notification rules which are stored in the Database. Additionally, it works as a proxy cache to FASJSON and dist-git (Pagure) using the Shared Service Cache which is also used by the Message Consumer.
The Configuration Frontend which uses the web API on behalf of a user establishes their identity to the Configuration Backend using the token it got from the OpenID Connect Identity Provider (Ipsilon).
Database¶
The Database is used to store the rules describing which messages users want to be notified about, as well as the number of notifications generated by a rule, to produce statistics for users and administrators.
It is implemented as a PostgreSQL
RDBMS and accessed by the Message
Consumer and the Configuration Backend using the
SQLAlchemy
object relational mapper.
Rules¶
Rules in FMN consist of several components: One Tracking Rule, and potentially many Filters and Destinations (grouped into Generation Rules). The Message Consumer looks at all messages transported over the bus and if any match a Rule, Notifications will be sent to the respective Notification Sender, one instance of each is responsible for actually sending out emails and chat messages over IRC or Matrix, respectively.
Tracking Rules¶
Each Rule has exactly one Tracking Rule which specifies what messages should be tracked, e.g. messages that concern:
Artifacts owned by the user or a group the user is member of,
specific named artifacts,
the user themselves, or
followed users.
If a Tracking Rule matches, its Generation Rules will be consulted for further processing.
Generation Rules¶
Each Rule contains one or more Generation Rules which group together zero or more Filters and one or more Destinations. If no Filters exist, or all of them match, Notifications will be created for each Destination which are processed by the respective Sender.
This lets users e.g. specify that messages of a lower severity should be sent via email, while higher severities should let FMN ping the user via IRC or Matrix.
Filters¶
Filters further restrict if a message should be matched by a Rule and notifications should be sent. Users can configure filters for these criteria:
The name of the application sending a message.
The severity(*) of the message.
If a message was caused by an action of the user.
If the message topic matches a certain glob pattern.
(*): At this point, we don’t know that any app in Fedora infrastructure tags its messages with a
severity, which makes them default to INFO
.
Destinations¶
A Destination contains information about how a user should be notified if a Rule matches, e.g. via email, IRC or Matrix. The destinations available to a user are retrieved from their account and can be configured in Noggin.
Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
This project uses towncrier and the changes for the upcoming release can be found in https://github.com/fedora-infra/fmn/tree/main/changelog.d/.