port over the development guide from pagure fedora-docs

Signed-off-by: Ryan Lerch <rlerch@redhat.com>
This commit is contained in:
Ryan Lercho 2021-11-09 19:26:14 +10:00
parent 47156deb6f
commit 1d7790ce3c
16 changed files with 1479 additions and 1 deletions

View file

@ -1 +1,15 @@
* link:https://fedora-infra-docs.readthedocs.io/en/latest/dev-guide/index.html[Developer's Guide] * xref:index.adoc[Developer Guide]
** xref:getting-started.adoc[Getting Started]
** xref:dev-environment.adoc[Development Environment]
** xref:documentation.adoc[Documentation]
** xref:code-style.adoc[Code Style]
** xref:frameworks.adoc[Frameworks and Tools]
** xref:db.adoc[Databases]
** xref:writing-tests.adoc[Tests]
** xref:auth.adoc[Authentication]
** xref:fedmsg.adoc[fedmsg]
** xref:messaging.adoc[Messaging]
** xref:sops.adoc[Developing Standard Operating Procedures]
** xref:source_control.adoc[Source Control]
** xref:openshift.adoc[Openshift]
** xref:security_policy.adoc[Fedora Infrastructure Application Security Policy]

View file

@ -0,0 +1,90 @@
== Authentication
Fedora applications that require authentication should support, at a
minimum, authentication against https://ipsilon-project.org/[Ipsilon].
Ipsilon is an Identity Provider that uses a separate Identity Management
system to perform authentication. In Fedora, Ipsilon is currently backed
by the https://admin.fedoraproject.org/accounts/[Fedora Account System].
In the future, it will be backed by http://www.freeipa.org/[FreeIPA].
Ipsilon supports
https://openid.net/specs/openid-authentication-2_0.html[OpenID 2.0],
https://openid.net/connect/[OpenID Connect],
https://tools.ietf.org/html/rfc6749[OAuth 2.0], and more.
=== Authentication
All new applications should use OpenID Connect for user authentication.
[NOTE]
.Note
====
Many existing applications use OpenID 2.0 and should eventually migrate
to OpenID Connect.
====OpenID Connect is an authentication layer built on top of OAuth 2.0
so to understand OpenID Connect you should first be familiar with OAuth
2.0 and its various flows prior to learning about OpenID Connect.
When requesting an access token in OAuth 2.0, clients are allowed to
specify the https://tools.ietf.org/html/rfc6749#section-3.3[scope] of
the access token. This scope indicates what the token is allowed to be
used for. In most cases, your application should require a scope or
scopes of its own so users can issue access tokens that can only be used
with a particular application. To do so, consult the
https://fedoraproject.org/wiki/Infrastructure/Authentication[Authentication
Wiki page].
[WARNING]
.Warning
====
OpenID Connect
https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest[requires
that the "openid" scope is requested]. Failing to do so will result in
undefined behavior. In the case of Ipsilon, you won't have access to the
UserInfo or recieve an ID token.
======= Libraries
==== OAuthLib
https://oauthlib.readthedocs.io/[OAuthLib] is a low-level implementation
of OAuth 2.0 with OpenID Connect support. It does not tie itself to a
HTTP request framework. Typically, you will only use this library
indirectly. If you are investigating this library, note that it is a
library for both OAuth clients and OAuth providers. You will be most
interested in the
https://oauthlib.readthedocs.io/en/latest/oauth2/clients/client.html[OAuth
client] sub-package.
==== Requests-OAuthlib
https://requests-oauthlib.readthedocs.io/[Requests-OAuthlib] uses the
http://docs.python-requests.org/[Requests] library with OAuthLib to
provide an easy-to-use interface for OAuth 2.0 clients. If you need to
add support to an application that doesn't have an extension for
OAuthLib, you should use this library.
==== Flask-OAuthlib
https://flask-oauthlib.readthedocs.io/en/latest/[Flask-OAuthlib] is a
Flask extension that builds on top of Requests-OAuthlib. It comes with
plenty of examples in the
https://github.com/lepture/flask-oauthlib/tree/master/example[examples]
directory of the repository. Flask applications within Fedora
Infrastructure should use this extension unless there is a good reason
not to (and that reason is documented here).
==== Pyramid-OAuthLib
https://github.com/tilgovi/pyramid-oauthlib[Pyramid-OAuthLib] is a
Pyramid extension that uses OAuthlib. It does not appear to be actively
maintained, but it is a reasonable starting point for our few Pyramid
applications.
==== Flask-OIDC
link:#flask-oidc[Flask-OIDC] is a Flask extension.
==== Mozilla-Django-OIDC
https://github.com/mozilla/mozilla-django-oidc[Mozilla-Django-OIDC] is a
Django extension for OpenID Connect.

View file

@ -0,0 +1,126 @@
== Code Style
We attempt to maintain a consistent coding style across projects so
contributors do not have to keep dozens of different styles in their
head as they move from project to project.
=== Python
We follow the https://www.python.org/dev/peps/pep-0008/[PEP8] style
guide for Python. Projects should make it easy to check new code for
PEP8 violations preferably by
https://pypi.python.org/pypi/flake8[flake8]. It is up to the individual
project to choose an enforcement method, but it should be clearly
documented and continuous integration tests should ensure code is
correctly styled before merging pull requests.
[NOTE]
.Note
====
There are a few PEP8 rules which will vary from project to project. For
example, the maximum line length might vary. The test suite should
enforce this.
======== Enforcement
Projects should automatically enforce code style. How a project does so
is up to the maintainers, but several good options are documented here.
===== Tox
`tox-config` is an excellent way to test style. `flake8` looks in, among
other places, `tox.ini` for its configuration so if you're already using
tox this is a good place to place your configuration. For example,
adding the following snippet to your `tox.ini` file will run `flake8` as
part of your test suite:
....
[testenv:lint]
deps =
flake8 > 3.0
commands =
python -m flake8 {posargs}
[flake8]
show-source = True
max-line-length = 100
exclude = .git,.tox,dist,*egg
....
===== Unit Test
If you're not using `tox`, you can add a unit test to your test suite:
....
"""This module runs flake8 on a subset of the code base"""
import os
import subprocess
import unittest
# Adjust this as necessary to ensure REPO_PATH resolves to the root of your
# repository.
REPO_PATH = os.path.abspath(os.path.dirname(os.path.join(os.path.dirname(__file__), '../')))
class TestStyle(unittest.TestCase):
"""Run flake8 on the repository directory as part of the unit tests."""
def test_code_with_flake8(self):
"""Assert the code is PEP8-compliant"""
# enforced_paths = [
# 'mypackage/pep8subpackage/',
# 'mypackage/a_module.py',
# ]
# enforced_paths = [os.path.join(REPO_PATH, p) for p in enforced_paths]
# If your entire codebase is not already PEP8 compliant, you can enforce
# the style incrementally using ``enforced_paths``.
#
# flake8_command = ['flake8', '--max-line-length', '100'] + enforced_paths
flake8_command = ['flake8', '--max-line-length', '100', REPO_PATH]
self.assertEqual(subprocess.call(flake8_command), 0)
if __name__ == '__main__':
unittest.main()
....
==== Auto formatting
The https://black.readthedocs.io/[Black] tool is an automatic code
formatter for Python. From the website:
____
By using Black, you agree to cede control over minutiae of
hand-formatting. In return, Black gives you speed, determinism, and
freedom from pycodestyle nagging about formatting. You will save time
and mental energy for more important matters.
Black makes code review faster by producing the smallest diffs possible.
Blackened code looks the same regardless of the project youre reading.
Formatting becomes transparent after a while and you can focus on the
content instead.
____
Your text editor is very likely to have a plugin to run Black on file
saving. The documentation has instructions to set it up in Vim and in VS
Code (and in Emacs).
You can check that your code is properly formatted according to Black's
settings by adding the following snippet to your `tox.ini` file:
....
[testenv:format]
deps =
black
commands =
python -m black --check {posargs:.}
....
Remember to add `format` to your Tox `envlist`.
=== Javascript
Javascript files should be formatted using the
https://prettier.io/[prettier] code formatter. It has support for many
editors and can integrate with ESLint to check the code automatically.

View file

@ -0,0 +1,109 @@
== Databases
We use PostgreSQL throughout Fedora Infrastructure.
=== Bi-directional Replication
http://bdr-project.org/docs/stable/index.html[Bi-directional
replication] (BDR) is a project that adds asynchronous multi-master
logical replication to PostgreSQL. Fedora has a PostgreSQL deployment
with BDR enabled. In Fedora, only one master is written to at any time.
Applications are not required to use the BDR-enabled database, but it is
encouraged since it provides redundancy and more flexibility for the
system administrators.
Applications need to take several things into account when considering
whether or not to use BDR.
==== Primary Keys
All tables need to have primary keys.
==== Conflicts
BDR does not use any consensus algorithm or locking between nodes so
writing to multiple masters can result in
http://bdr-project.org/docs/stable/conflicts.html[conflicts]. There are
several types of conflicts that can occur, and applications should
carefully consider each one and be prepared to handle them. Some
conflicts are handled automatically, while others can result in a
deadlock that requires manual intervention.
==== Global DDL Lock
BDR uses a
link:bdr-project.org/docs/stable/ddl-replication-advice.html[global DDL
lock] (across all PostgreSQL nodes) for DDL changes, which applications
must explicitly acquire prior to emitting DDL statements.
This can be done in Alembic by modifying the `run_migrations_offline`
and `run_migrations_online` functions in `env.py` to emit the SQL when
connecting to the database. An example of the `run_migrations_offline`:
....
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This requires a configuration options since it's not known whether the
target database is a BDR cluster or not. Alternatively, you can simply
add the SQL to the script manually and not bother with a setting.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(url=url)
with context.begin_transaction():
# If the configuration indicates this script is for a Postgres-BDR database,
# then we need to acquire the global DDL lock before migrating.
postgres_bdr = config.get_main_option('offline_postgres_bdr')
if postgres_bdr is not None and postgres_bdr.strip().lower() == 'true':
_log.info('Emitting SQL to allow for global DDL locking with BDR')
context.execute('SET LOCAL bdr.permit_ddl_locking = true')
context.run_migrations()
....
An example of the `run_migrations_online` function:
....
def run_migrations_online():
"""Run migrations in 'online' mode.
This auto-detects when it's run against a Postgres-BDR system.
"""
engine = engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool)
connection = engine.connect()
context.configure(
connection=connection,
target_metadata=target_metadata)
try:
try:
connection.execute('SHOW bdr.permit_ddl_locking')
postgres_bdr = True
except exc.ProgrammingError:
# bdr.permit_ddl_locking is an unknown option, so this isn't a BDR database
postgres_bdr = False
with context.begin_transaction():
if postgres_bdr:
_log.info('Emitting SQL to allow for global DDL locking with BDR')
connection.execute('SET LOCAL bdr.permit_ddl_locking = true')
context.run_migrations()
finally:
connection.close()
....
Be aware that long-running migrations will hold the global lock for the
entire migration and while the global lock is held by a node, no other
nodes may perform any DDL or make any changes to rows.
==== DDL Restrictions
BDR has a set of
http://bdr-project.org/docs/stable/ddl-replication-statements.html#DDL-REPLICATION-PROHIBITED-COMMANDS[DDL
Restrictions]. Some of the restrictions are easily worked around by
performing the task in several steps, while others are simply not
available.

View file

@ -0,0 +1,70 @@
== Development Environment
In order to make contributing easy, all projects should have an
automated way to create a development environment. This might be as
simple as a Python virtual environment, or it could be a virtual machine
or container. This document provides guidelines for setting up
development environments.
=== Ansible
link:#ansible[Ansible] is used throughout Fedora Infrastructure to
automate tasks. If the project requires anything more than a Python
virtual environment to be set up, you should use Ansible to automate the
setup.
=== Vagrant
link:#vagrant[Vagrant] is a tool to provision virtual machines. It
allows you to define a base image (called a "box"), virtual machine
resources, network configuration, directories to share between the host
and guest machine, and much more. It can be configured to use
link:[libvirt] to provision the virtual machines.
You can install link:#vagrant[Vagrant] on a Fedora host with:
....
$ sudo dnf install libvirt vagrant vagrant-libvirt vagrant-sshfs
....
You can combine your link:[Ansible playbook] with link:#vagrant[Vagrant]
very easily. Simply point Vagrant to your Ansible playbook and it will
run it. Users who would prefer to provision their virtual machines in
some other way are free to do so and only need to run the Ansible
playbook on their host.
[NOTE]
====
How a project lays out its development-related content is up to the
individual project, but a good approach is to create a `devel`
directory. Within that directory you can create an `ansible` directory
and use the layout suggested in the link:[Ansible roles] documentation.
====
Below is a Vagrantfile that provisions a Fedora 25 virtual machine,
updates it, and runs an Ansible playbook on it. You can place it in the
root of your repository as `Vagrantfile.example` and instruct users to
copy it to `Vagrantfile` and customize as they wish.
[source,ruby]
----
# -*- mode: ruby -*-
# vi: set ft=ruby :
#
# Copy this file to ``Vagrantfile`` and customize it as you see fit.
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# If you'd prefer to pull your boxes from Hashicorp's repository, you can
# replace the config.vm.box and config.vm.box_url declarations with the line below.
#
# config.vm.box = "fedora/25-cloud-base"
config.vm.box = "f25-cloud-libvirt"
config.vm.box_url = "https://download.fedoraproject.org/pub/fedora/linux/releases"\
"/25/CloudImages/x86_64/images/Fedora-Cloud-Base-Vagrant-25-1"\
".3.x86_64.vagrant-libvirt.box"
# Forward traffic on the host to the development server on the guest.
# You can change the host port that is fo
----

View file

@ -0,0 +1,157 @@
== Documentation
Since Fedora contributors live around the world and don't often have the
opportunity to meet in person, it's important to maintain up-to-date
high quality documentation for all our projects. Our preferred
documentation tool is http://www.sphinx-doc.org/[Sphinx]. In fact, this
documentation is written using http://www.sphinx-doc.org/[Sphinx]!
A project's documentation should at a minimum contain:
* An introduction to the project
* A user guide
* A contributor guide
* API documentation.
The easiest way to maintain up-to-date documentation is to include the
majority of the documentation in the code itself.
http://www.sphinx-doc.org/[Sphinx] includes several extensions to turn
Python documentation into HTML pages.
[NOTE]
.Note
====
Improving documentation is a great way to get involved in a project.
When adding new documentation or cleaning up existing documentation,
please follow the guidelines below.
======= Style
Sphinx supports three different documentation styles. By default, Sphinx
expects ReStructuredText. However, it has included an extension to
support the
http://www.sphinx-doc.org/en/1.5.2/ext/example_google.html[Google style]
and the http://www.sphinx-doc.org/en/1.5.2/ext/example_numpy.html[NumPy
style] since version 1.3. The style of the documentation blocks is left
up to the individual project, but it should document the choice and be
consistent.
=== Introduction
The project introduction should be easy to find - preferably it should
be the documentation's index page. It should provide an overview of the
project and should be easy for a complete new-comer to understand.
=== User Guide
Have a clear user guide that covers most, if not all, features of the
project as well as potential use cases. Keep in mind that your users may
be non-technical as well as technical. Some users will want to use the
project's web interface, while others are interested in the API and the
documentation should make it easy for both types of users to find the
documentation for them.
=== Contributor Guide
Documenting how to start contributing makes it much easier for new
contributors to get involved. This is a good place to cover the
expectations about code style, documentation, tests, etc.
=== API Documentation
All APIs should be documented. Users should never have to consult the
source code to use the project's API.
==== Python
Python API documentation is easily generated by using the
http://www.sphinx-doc.org/en/stable/tutorial.html#autodoc[autodoc]
extension. Following these steps will create rich HTML, PDF, EPUB, or
man format documentation:
[arabic]
. All modules should contain a documentation block at the top of the
file that describes the module's purpose and documents module-level
attributes it provides as part of its public interface. In the case of a
package's `__init__.py`, this should document the package's purpose.
. All classes should have documentation blocks that describe their
purpose, any attributes they have, and example usage if appropriate.
. All methods and functions should have documentation blocks that
describe their purpose, the arguments they accept, the types of those
arguments, and example usage if appropriate.
. Make use of Sphinx's
http://www.sphinx-doc.org/en/stable/domains.html#cross-referencing-python-objects[cross-referencing]
feature. This will generate links between objects in the documentation.
==== HTTP APIs
Many projects provide an HTTP-based API. Use
http://pythonhosted.org/sphinxcontrib-httpdomain/[sphinxcontrib-httpdomain]
to produce the HTTP interface documentation. This task is made
significantly easier if the project using a web framework that
http://pythonhosted.org/sphinxcontrib-httpdomain/[sphinxcontrib-httpdomain]
supports, like Flask. In that case, all you need to do is add the
http://pythonhosted.org/sphinxcontrib-httpdomain/[sphinxcontrib-httpdomain]
ReStructuredText directives to the functions or classes that provide the
Flask endpoints.
After that, all you need to do is use the `autoflask` ReStructuredText
directive.
=== Release Notes and ChangeLog
The release notes (or the changelog) can be managed using
https://pypi.org/project/towncrier/[towncrier]. It can build a release
notes files by assembling items that would be written in separate files
by each pull request (or commit). This way, the different commits will
not conflict by writing in the same changelog file, and a link to the
issue, the pull request or the commit is automatically inserted.
In your project root, add a `pyproject.toml` file with a
`tool.towncrier` section similar to the one in
https://raw.githubusercontent.com/fedora-infra/fedora-messaging/master/pyproject.toml[fedora-messaging].
Create a `news` directory where the news items will be written, and in
there create a `_template.rst` file with a content similar to the one in
https://raw.githubusercontent.com/fedora-infra/fedora-messaging/master/news/_template.rst[fedora-messaging].
Of course, replace `fedora_messaging` and the project URL by yours,
where applicable.
Then create a `docs/changelog.rst` file (location configured in the
`pyproject.toml` file) with the follwing content:
....
=============
Release Notes
=============
.. towncrier release notes start
....
Then each commit can add a file in the `news` folder to document the
change. The file has the `source.type` name format, where `type` is one
of:
* `feature`: for new features
* `bug`: for bug fixes
* `api`: for API changes
* `dev`: for development-related changes
* `author`: for contributor names
* `other`: for other changes
And where the `source` part of the filename is:
* `42` when the change is described in issue `42`
* `PR42` when the change has been implemented in pull request `42`, and
there is no associated issue
* `Cabcdef` when the change has been implemented in changeset `abcdef`,
and there is no associated issue or pull request.
* `username` for contributors (`author` extention). It should be the
username part of their commits' email address.
A preview of the release notes can be generated with
`towncrier --draft`.
When running `towncrier`, the tool will write the changelog file and
remove the individual news fragments. These changes can then be
committed as part of the release commit.

View file

@ -0,0 +1,73 @@
== fedmsg
https://fedmsg.readthedocs.io/[fedmsg] is a ZeroMQ-based messaging
library used throughout Fedora Infrastructure applications. It uses a
publish/subscribe design so applications can decide what messages
they're interested in receiving.
[WARNING]
.Warning
====
fedmsg does not guarantee message delivery. Messages will be lost and
your application should never depend on the reliable delivery of fedmsgs
to function.
======= Topics
==== Existing Topics
There are many existing https://fedora-fedmsg.readthedocs.org/[topics]
in Fedora Infrastructure.
==== New Topics
When creating new message topics, please use the following format:
....
org.fedoraproject.ENV.CATEGORY.OBJECT[.SUBOBJECT].EVENT
....
Where:
____
* `ENV` is one of [.title-ref]#dev#, [.title-ref]#stg#, or
[.title-ref]#production#.
* `CATEGORY` is the name of the service emitting the message --
something like [.title-ref]#koji#, [.title-ref]#bodhi#, or
[.title-ref]#fedoratagger#
* `OBJECT` is something like [.title-ref]#package#, [.title-ref]#user#,
or [.title-ref]#tag#
* `SUBOBJECT` is something like [.title-ref]#owner# or
[.title-ref]#build# (in the case where `OBJECT` is
[.title-ref]#package#, for instance)
* `EVENT` is a verb like [.title-ref]#update#, [.title-ref]#create#, or
[.title-ref]#complete#.
____
All 'fields' in a topic *should*:
____
* Be [.title-ref]#singular# (Use [.title-ref]#package#, not
[.title-ref]#packages#)
* Use existing fields as much as possible (since [.title-ref]#complete#
is already used by other topics, use that instead of using
[.title-ref]#finished#).
____
*Furthermore*, the _body_ of messages will contain the following
envelope:
* A `topic` field indicating the topic of the message.
* A `timestamp` indicating the seconds since the epoch when the message
was published.
* A `msg_id` bearing a unique value distinguishing the message. It is
typically of the form <YEAR>-<UUID>. These can be used to uniquely query
for messages in the datagrepper web services.
* A `crypto` field indicating if the message is signed with the `X509`
method or the `gpg` method.
* An `i` field indicating the sequence of the message if it comes from a
permanent service.
* A `username` field indicating the username of the process that
published the message (sometimes, `apache` or `fedmsg` or something
else).
* Lastly, the application-specific body of the message will be contained
in a nested `msg` dictionary.

View file

@ -0,0 +1,48 @@
== Frameworks and Tools
We attempt to use the same set of frameworks and tools across projects
to minimize the number of frameworks developers must keep in their
heads.
=== Python
==== Flask
http://flask.pocoo.org/[Flask] is a web microframework for Python based
on http://werkzeug.pocoo.org/[Werkzeug] and
http://jinja.pocoo.org/[Jinja 2]. It is our preferred framework and all
new applications should use it unless there is a very good reason not to
do so.
[NOTE]
.Note
====
For historical reasons, you may find applications that don't use Flask.
Other frameworks currently in use include
https://www.djangoproject.com/[Django] and
http://docs.pylonsproject.org/projects/pyramid/en/latest/[Pyramid].
====Flask is designed to be extensible, so it's common to use extensions
with the core flask library. A few common extensions are documented
below.
===== Flask-SQLAlchemy
http://flask-sqlalchemy.pocoo.org/[Flask-SQLAlchemy] integrates Flask
with SQLAlchemy. It will configure a scoped session for you, set up a
declarative base class, and provide a convenient
`flask_sqlalchemy.BaseQuery` sub-class for you.
==== SQLAlchemy
http://www.sqlalchemy.org/[SQLAlchemy] is an SQL toolkit and Object
Relational Mapper. It provides a core set of tools (surprisingly called
SQLAlchemy Core) for working with SQL databases, as well as an Object
Relational Mapper (SQLAlchemy ORM) which is built using SQLAlchemy Core.
SQLAlchemy is quite flexible and provides a myriad of options. We use
SQLAlchemy with its
http://docs.sqlalchemy.org/en/latest/orm/extensions/declarative/index.html[Declarative]
extension to map SQL tables to Python classes. Once mapped, instances of
those Python classes are created from database rows using the
http://docs.sqlalchemy.org/en/latest/orm/session.html[Session]
interfaces.

View file

@ -0,0 +1,46 @@
== Getting Started
:toc: right
This document is intended to guide you through your first contribution
to a Fedora Infrastructure project. It assumes you are already familiar
with the https://git-scm.com/[git] version control system and the
https://www.python.org/[Python] programming language.
=== Development Environment
The Fedora Infrastructure team uses https://www.ansible.com/[Ansible]
and https://vagrantup.com/[Vagrant] to set up development environments
for the majority of our projects. It's recommended that you develop on a
Fedora host, but that is not strictly required.
To install https://www.ansible.com/[Ansible] and
https://vagrantup.com/[Vagrant] on Fedora, run:
....
$ sudo dnf install vagrant libvirt vagrant-libvirt vagrant-sshfs ansible
....
Projects will provide a `Vagrantfile.example` file in the root of their
repository if they support using https://vagrantup.com/[Vagrant]. Copy
this to `Vagrantfile`, adjust it as you see fit, and then run:
....
$ vagrant up
$ vagrant reload
$ vagrant ssh
....
This will create a new virtual machine, configure it with
https://www.ansible.com/[Ansible], restart it to ensure you're running
the latest updates, and then SSH into the virtual machine.
Individual projects will provide detailed instructions for their
particular setup.
=== Finding a Project
Fedora Infrastructure applications are either on
https://github.com/[GitHub] in the
https://github.com/fedora-infra[fedora-infra] organization, or on
https://pagure.io/[Pagure]. Check out the issues tagged with
https://fedoraproject.org/easyfix/[easyfix] for an issue to fix.

View file

@ -0,0 +1,3 @@
= Developer Guide
This is a complete guide to contributing to Fedora Infrastructure applications. It targets both new and experienced contributors, and is maintained by those contributors. If the documentation is in need of improvement, please https://pagure.io/infra-docs-fpo/new_issue[file an issue] or submit a pull request.

View file

@ -0,0 +1,128 @@
== Messaging
Fedora uses many event-driven services triggered by messages. In the
past, this was done with ZeroMQ and
https://fedmsg.readthedocs.io/en/stable/[fedmsg]. This has been replaced
by an AMQP message broker and the
https://fedora-messaging.readthedocs.io/en/latest/[fedora-messaging] for
Python applications. This documentation outlines the policies for
sending and receiving messages. To learn how to send and receive
messages, see the fedora-messaging documentation.
=== Broker URLs
The broker consists of multiple RabbitMQ nodes. They are available
through the proxies at `amqps://rabbitmq.fedoraproject.org` for
production and `amqps://rabbitmq.stg.fedoraproject.org` for staging.
Clients can connect using these URLs both inside and outside the Fedora
VPN, but users outside need to use a separate virtual host. Consult the
https://fedora-messaging.readthedocs.io/en/stable/fedora-broker.html[fedora-messaging
documentation] for details on how to connect externally.
=== Identity
In order to help with debugging, clients must configure the
https://fedora-messaging.readthedocs.io/en/latest/configuration.html#client-properties[client_properties]
option to include their application name under the `app` key. Clients
should include the application version, if possible, in the
`app_version` key.
=== Authentication
When applications are deployed, clients must authenticate with the
message broker over a TLS connection using x509 certificates. There are
https://fedora-messaging.readthedocs.io/en/stable/configuration.html#tls[configuration
options] for this in fedora-messaging.
Clients require certificates issued by Fedora Infrastructure. If you're
not using the external, read-only user, file a
https://pagure.io/fedora-infrastructure/issues[ticket] requesting a
certificate for the AMQP broker and be sure to provide the username you
plan to use. This is placed in the Common Name of the client certificate
and must match the name of the user you create in AMQP. Consult the
Authorization section below for details on creating users, queues, and
bindings.
=== Authorization
The message broker can use https://www.rabbitmq.com/vhosts.html[virtual
hosts] to allow multiple applications to use the broker. The general
purpose publish-subscribe virtual host is called `/pubsub` and has its
authorization policy is outlined below. If your application is using a
different virtual host for private messaging (for example, your
application uses Celery), different authorization rules apply.
==== pubsub Virtual Host
AMQP clients do not have permission to create exchanges, queues, or
bindings. However, they can and should declare the exchanges, queues,
and bindings they expect to exist in their fedora-messaging
configuration so that if they do not exist, the application will fail
with a helpful error message about which resource is not available.
[WARNING]
.Warning
====
Because AMQP clients don't have permission to create objects, you need
to set
https://fedora-messaging.readthedocs.io/en/stable/configuration.html#passive-declares[passive_declares
= true] or you will get 403 Permission Denied errors.
====Users, exchanges, queues, bindings, and
https://www.rabbitmq.com/vhosts.html[virtual hosts] other objects are
managed in the broker using the Fedora Infrastructure Ansible project
and must be declared there.
To do so, you can use the `rabbit/queue` role in the Ansible repository.
An example usage in your deployment playbook would be:
....
roles:
- role: rabbit/queue
username: bodhi
queue_name: bodhi_masher
routing_keys:
- "routing_key1"
- "routing_key2"
....
Note that users only have permissions to read from queues prefixed with
their name so if your username is "bodhi", all queues must start with
"bodhi". The username must also match the common name in the x509
certificate used for authentication.
If you want to create a user that will only need to publish messages,
and not consume them, you can use the `rabbit/user` role in the Ansible
repository. An example usage in your deployment playbook would be:
....
roles:
- role: rabbit/user
username: bodhi
....
Please note that the username must match the TLS certificate's Common
Name, and they were created with the environment suffix. As a result, if
you want the username to match in staging too, you should use:
....
username: "bodhi{{ env_suffix}}"
....
===== Bindings
Messages from AMQP publishers are sent to the `amq.topic` exchange.
Messages from ZeroMQ publishers are sent to the `zmq.topic` exchange. In
order to receive all messages during the transition period from ZeroMQ
to AMQP, be sure to bind your consumers to both exchanges:
....
[[bindings]]
queue = "your queue"
exchange = "amq.topic"
routing_keys = ["key1", "key2"]
[[bindings]]
queue = "your queue"
exchange = "zmq.topic"
routing_keys = ["key1", "key2"]
....

View file

@ -0,0 +1,101 @@
== OpenShift
OpenShift is a Kubernetes-based platform for running containers. The
upstream project, https://www.openshift.org/[OpenShift Origin], is what
Red Hat bases the https://www.openshift.com/[OpenShift Container
Platform] product on. Fedora runs OpenShift Container Platform rather
than OpenShift Origin.
=== Getting Started
If you've never used OpenShift before a good place to start is with
https://www.openshift.org/minishift/[MiniShift], which deploys OpenShift
Origin in a virtual machine.
=== OpenShift in Fedora Infrastructure
Fedora has two OpenShift deployments:
https://os.stg.fedoraproject.org/[Staging OpenShift] and
https://os.fedoraproject.org/[Production OpenShift]. In addition to
being the staging deployment of OpenShift itself, the staging deployment
is intended to be a place for developers to deploy the staging version
of their applications.
Some features of OpenShift are not functional in Fedora's deployment,
mainly due to the lack of HTTP/2 support (at the time of this writing).
Additionally, users are not allowed to alter configuration, roll out new
deployments, run builds, etc. in the web UI or CLI.
==== Web User Interface
Some of the web user interface is currently non-functional since it
requires HTTP/2. The rest is locked down to be read-only, making it of
limited usefulness.
==== Command-line Interface
Although the CLI is also locked down to be read only, it is possible to
view logs and request debugging containers, but only from batcave01. For
example, to view the logs of a deployment in staging:
....
$ ssh batcave01.phx2.fedoraproject.org
$ oc login os-master01.stg.phx2.fedoraproject.org
You must obtain an API token by visiting https://os.stg.fedoraproject.org/oauth/token/request
$ oc login os-master01.stg.phx2.fedoraproject.org --token=<Your token here>
$ oc get pods
librariesio2fedmsg-28-bfj52 1/1 Running 522 28d
$ oc logs librariesio2fedmsg-28-bfj52
....
==== Deploying Your Application
Applications are deployed to OpenShift using
https://pagure.io/fedora-infra/ansible/blob/main/f/playbooks/openshift-apps/[Ansible
playbooks]. You will need to create an
https://pagure.io/fedora-infra/ansible/blob/main/f/roles/openshift-apps/[Ansible
Role] for your application. A role is made up of several YAML files that
define OpenShift
https://docs.openshift.com/container-platform/latest/architecture/core_concepts/index.html[objects].
To create these YAML objects you have two options:
[arabic]
. Copy and paste an existing role and do your best to rewrite all the
files to work for your application. You will likely make mistakes which
you won't find until you run the playbook and when you do learn that
your configuration is invalid, it won't be clear where you messed up.
. Set up your own deployment of OpenShift where you can click through
the web UI to create your application (and occasionally use the built-in
text editor when the UI doesn't have buttons for a feature you need).
Once you've done that, you can export all the configuration files and
drop them into the infra ansible repository. They will be "messy" with
lots of additional data OpenShift adds for you (including old revisions
of the configuration).
Both approaches have their downsides. #1 has a very long feedback cycle
as you edit the file, commit it to the infra repository, and then run
the playbook. #2 generates most of the configuration, but will produce
crufty files. Additionally, you will likely not have your OpenShift
deployment set up the same way Fedora does so you still may produce
configurations that won't work.
You will likely need (at a minimum) the following objects:
* A
https://docs.openshift.com/container-platform/latest/architecture/core_concepts/builds_and_image_streams.html#builds[BuildConfig]
- This defines how your container is built.
* An
https://docs.openshift.com/container-platform/latest/architecture/core_concepts/builds_and_image_streams.html#image-streams[ImageStream]
- This references a "stream" of container images and lets you trigger
deployments or image builds based on changes in a stream.
* A
https://docs.openshift.com/container-platform/latest/architecture/core_concepts/deployments.html[DeploymentConfig]
- This defines how your container is deployed (how many replicas, what
ports are available, etc)
* A
https://docs.openshift.com/container-platform/latest/architecture/core_concepts/pods_and_services.html#services[Service]
- An internal load balancer that routes traffic to your pods.
* A
https://docs.openshift.com/container-platform/latest/architecture/networking/routes.html[Route]
- This exposes a Service as a host name.

View file

@ -0,0 +1,116 @@
== Fedora Infrastructure Application Security Policy
This document sets out the security requirements applications must meet
at a minimum to pass the security audit, and as such run in Fedora
Infrastructure.
This is by no means a comprehensive list, but it is a minimum set.
=== General
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
document are to be interpreted as described in
https://tools.ietf.org/html/rfc2119[RFC 2119].
The key words "MUST (BUT WE KNOW YOU WON'T)", "SHOULD CONSIDER", "REALLY
SHOULD NOT", "OUGHT TO", "WOULD PROBABLY", "MAY WISH TO", "COULD",
"POSSIBLE", and "MIGHT" in this document are to be interpreted as
described in https://tools.ietf.org/html/rfc6919[RFC 6919].
=== Static security checking
If written in Python, the application MUST pass
https://github.com/PyCQA/bandit[Bandit] on level medium with default
configuration. Any exclusion lines that appear in the codebase MUST be
sufficiently explained. Code that is only executed during test suite
runs MAY be exempted from this check.
=== Authentication
The application MUST use OpenID Connect to authenticate users. The
application MUST use an
https://fedora-infra-docs.readthedocs.io/en/latest/dev-guide/auth.html#libraries[approved
authentication library]. If the application supports an API, it SHOULD
accept OpenID Connect Bearer tokens, which MUST be verified by the
approved authentication library. The application MUST NOT accept any
user credentials, but MUST forward the user to the OpenID Connect
Provider in their browser. If the application supports API tokens that
are not OpenID Connect Bearer tokens, they MUST be generated by the
application via a Cryptographically Secure Psuedo-Random Number
Generator. The application REALLY SHOULD NOT return error code 418 at
any moment unless it is applicable.
=== Authorization
API tokens, whether OpenID Connect Bearer or custom, SHOULD allow the
user to limit the scope of the token in a useful and clear way. The
application SHOULD use authorization if provided by the authentication
library, if it does not, this MUST be pointed out during the audit
request so that specific review is performed.
=== Data exchange formats
The application MUST NOT use the Python pickle library for data. If the
application uses the PyYAML library, it MUST NOT use yaml.load, but MUST
use yaml.safe_load. If the application uses XML data exchange, it MUST
use the https://pypi.org/project/defusedxml/[defusedxml] library to
process this data.
=== User input sanitization
Special care must be taken when processing user generated content. The
application SHOULD use a common database abstraction layer (e.g.
SQLAlchemy or Django ORM) that has protections against crafted input,
and these protections MUST be used. Requests that are not part of an API
call MUST be protected against cross-site request forgery.
=== Cookies
The application MUST set the Secure flag on any cookies it sets if it is
not in a development mode. The application MUST set the httpOnly flag on
any cookiees it sets. The application SHOULD NOT set a Domain parameter
in any cookies it sets, if it does set the Domain, its value MUST be
identical to the exact Host requested.
=== Security headers
The application MUST set the `X-Frame-Options` header, and its value
SHOULD be `DENY`, unless there are specific reasons it should be
inserted into a frame. Setting anything else than `DENY` is a flag for
review. The application MUST set the `X-Xss-Protection` header, and the
value MUST be `1; mode=block`. The application MUST set the
`X-Content-Type-Options` header, and the value MUST be `nosniff`. The
application MUST set the `\`Referrer-Policy`[.title-ref]##_ header, and
the value MUST be ##[.title-ref]##no-referrer##[.title-ref]## or
##[.title-ref]##same-origin##`.
The application MUST set the `\`Content-Security-Policy`[.title-ref]##_
header and MUST set at least
##[.title-ref]##default-src##[.title-ref]##. The content security MUST
NOT allow any origins other than ##[.title-ref]##'none'##[.title-ref]##,
##[.title-ref]##'self'##[.title-ref]##, any of the explicitly approved
origins (listed below) or ##[.title-ref]##nonce-$nonce##`. Any nonces
used for the content security policy MUST be generated via a
Cryptographically Secure PRNG.
The allowed origin at this moment is: `https://apps.fedoraproject.org`.
=== Dependencies
The application MUST use up-to-date, maintained dependencies. The
application MAY set minimum versions on dependencies, but MUST NOT set
maximum versions.
=== Resources
The application MUST only use include any resources in produced HTML
that are served via TLS.
== Audit
The list of requirements in this document are a set of minimum
requirements. Any deviation from them MUST be mentioned when requesting
a security audit and MAY be reason for rejecting the security audit.
Even if all these requirements are met, the auditor MAY reject the
application on well-explained grounds.

View file

@ -0,0 +1,120 @@
[[develop-sops]]
== Developing Standard Operating Procedures
When a new application is deployed in Fedora, it is critical that you
add a standard operating procedure (SOP) for it. This documents how the
application is deployed in Fedora. Consult the current `sops` and if one
is missing, please add it.
You can modify this documentation or any of the current `sops` by making
a https://docs.pagure.org/pagure/usage/pull_requests.html[pull request]
to the https://pagure.io/infra-docs/[Pagure project].
=== Adding a Standard Operating Procedure
To add a standard operating procedure, create a new
http://www.sphinx-doc.org/en/stable/rest.html[reStructedText] file in
the
https://pagure.io/infra-docs/blob/master/f/docs/sysadmin-guide/sops[sop
directory] and then add it to the
https://pagure.io/infra-docs/blob/master/f/docs/sops/index.rst[index
file].
SOP text file names should use lowercase with dashes. Describe the
service and end the page name with ".rst".
=== Stuff every SOP should have
Here's the template for adding a new SOP:
....
=========
SOP Title
=========
Provide a brief description of the SOP here.
Contact Information
===================
Owner
<usually, Fedora Infrastructure Team>
Contact
<stakeholder fas groups, individuals, IRC channels to find the action>
Location
<Relevant URIs, etc>
Servers
<affected machines>
Purpose
<a brief description of the SOPs purpose>
Sections Describing Things
==========================
Put detailed information in these sections
A Helpful Sub-section
---------------------
You can even have sub-sections.
A Sub-section of a sub-section
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
....
If a current SOP does not follow this general template, it should be
updated to do so.
=== SOP Formatting
SOPs are written in ReStructuredText. To learn about the spec, read:
* http://docutils.sourceforge.net/docs/user/rst/quickstart.html[Quickstart]
* http://docutils.sourceforge.net/docs/user/rst/quickref.html[Quick
references]
* http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html[Full
Specification]
* http://www.sphinx-doc.org/en/stable/rest.html[Sphinx reStructuredText]
The format is somewhat simple if you remember a few key points:
* Sections are deliniated by underlined texts. The convention is:
** Title has "=" above and below the title text, at least as many
columns as the title itself.
** Top level sections are underlined by "===" - at least as many columns
as the section title in the line above.
** Second level sections are underlined by "---"
** Any of
`! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ \` { | } ~` are
valid section deliniators. If you need more than two section levels,
choose between them but be sure to be consistent.
* Indents are significant. Only indent for things like block quotes,
nested lists etc. Match the tabstop of the document you are editing.
Make note of the indentation level as you nest lists.
* Use literal blocks for code and command sections. An indented section
found after `::` and a newline will be processed as a literal block.
Like this:
+
....
Literal blocks can be nested into lists (think a numbered sequence of steps)
1. Log into the thing
2. run a command::
this indented relative to the first content column of the list
so it is a block quote
This line begins at the first content column of the list,
so it is considered a continuation of the list content.
3. Log out of the thing.
....
* For inline literals (commands, filenames, anything that wouldn't make
sense if translated, use your judgement) use double backticks, like
this:
+
....
You should specify your Fedora username and ssh key in ``~/.ssh/config`` to make connecting
better.
....
* If nesting and mixing content types, use newlines liberally. A bullet
list doesn't need newlines, but if a list item's content spans more than
one line, a newline _is_ required. If a list is nested, the top level
list should have newlines between list members.

View file

@ -0,0 +1,8 @@
== Source Control
=== Pagure
If your project is hosted on Pagure, you should go to the project
settings and set "Project tags" to have `fedora-infra` in it. This way
your pull requests will appear on
http://ambre.pingoured.fr/fedora-infra/ automatically.

View file

@ -0,0 +1,269 @@
== Tests
Tests make development easier for both veteran project contributors and
newcomers alike. Most projects use the
https://docs.python.org/3.6/library/unittest.html[unittest] framework
for tests so you should familiarize yourself with this framework.
[NOTE]
.Note
====
Writing tests can be a great way to get involved with a project. It's an
opportunity to get familiar with the codebase and the code submission
and review process. Check the project's code coverage and write a test
for a piece of code missing coverage!
====Patches should be accompanied by one or more tests to demonstrate
the feature or bugfix works. This makes the review process much easier
since it allows the reviewer to run your code with very little effort,
and it lets developers know when they break your code.
=== Test Organization
Having a standard test layout makes it easy to find tests. When adding
new tests, follow the following guidelines:
[arabic]
. Each module in the application should have a corresponding test
module. These modules should be organized in the test package to mirror
the package they test. That is, if the package contains the
`<package>/server/push.py` module, the test module should be in a module
called `<test_root>/server/test_push.py`.
. Within each test module, follow the
https://docs.python.org/3.6/library/unittest.html#organizing-test-code[unittest
code organization guidelines].
. Include documentation blocks for each test case that explain the goal
of the test.
. Avoid using mock unless absolutely necessary. It's easy to write tests
using mock that only assert that mock works as expected. When testing
code that makes HTTP requests, consider using
https://pypi.python.org/pypi/vcrpy[vcrpy].
[NOTE]
.Note
====
You may find projects that do not follow this test layout. In those
cases, consider re-organizing the tests to follow the layout described
here and follow the established conventions for that project until that
happens.
======= Test Runners
Projects should include a way to run the tests with ease locally and the
steps to run the tests should be documented. This should be the same way
the continuous integration (Jenkins, TravisCI, etc.) tool runs the
tests.
There are many test runners available that can discover
https://docs.python.org/3.6/library/unittest.html[unittest] based tests.
These include:
* https://docs.python.org/3.6/library/unittest.html[unittest] itself via
`python -m unittest discover`
* http://docs.pytest.org/en/latest/contents.html[pytest]
* http://nose2.readthedocs.io/en/latest/[nose2]
Projects should choose whichever runner best suits them.
[NOTE]
.Note
====
You may find projects using the
https://nose.readthedocs.io/en/latest/[nose] test runner. nose is in
maintenance mode and, according to their documentation, will likely
cease without a new maintainer. They recommend using
https://docs.python.org/3.6/library/unittest.html[unittest],
http://docs.pytest.org/en/latest/contents.html[pytest], or
http://nose2.readthedocs.io/en/latest/[nose2].
====[[tox-config]]
=== Tox
https://pypi.python.org/pypi/tox[Tox] is an easy way to run your
project's tests (using a Python test runner) using multiple Python
interpreters. It also allows you to define arbitrary test environments,
so it's an excellent place to run the code style tests and to ensure the
project's documentation builds without errors or warnings.
Here's an example `tox.ini` file that runs a project's unit tests in
Python 2.7, Python 3.4, Python 3.5, and Python 3.6. It also runs
https://pypi.python.org/pypi/flake8[flake8] on the entire codebase and
builds the documentation with the "warnings treated as errors" Sphinx
flag enabled. Finally, it enforces 100% coverage on lines edited by new
patches using https://pypi.org/project/diff-cover/[diff-cover]:
....
[tox]
envlist = py27,py34,py35,py36,lint,diff-cover,docs
# If the user is missing an interpreter, don't fail
skip_missing_interpreters = True
[testenv]
deps =
-rtest-requirements.txt
# Substitute your test runner of choice
commands =
py.test
# When running in OpenShift you don't have a username, so expanduser
# won't work. If you are running your tests in CentOS CI, this line is
# important so the tests can pass there, otherwise tox will fail to find
# a home directory when looking for configuration files.
passenv = HOME
[testenv:diff-cover]
deps =
diff-cover
commands =
diff-cover coverage.xml --compare-branch=origin/master --fail-under=100
[testenv:docs]
changedir = docs
deps =
sphinx
sphinxcontrib-httpdomain
-rrequirements.txt
whitelist_externals =
mkdir
sphinx-build
commands=
mkdir -p _static
sphinx-build -W -b html -d {envtmpdir}/doctrees . _build/html
[testenv:lint]
deps =
flake8 > 3.0
commands =
python -m flake8 {posargs}
[flake8]
show-source = True
max-line-length = 100
exclude = .git,.tox,dist,*egg
....
=== Coverage
https://pypi.python.org/pypi/coverage/[coverage] is a good way to
collect test coverage statistics.
http://docs.pytest.org/en/latest/contents.html[pytest] has a
https://pypi.python.org/pypi/pytest-cov[pytest-cov] plugin that
integrates with https://pypi.python.org/pypi/coverage/[coverage] and
https://pypi.python.org/pypi/nose-cov[nose-cov] provides integration for
the https://nose.readthedocs.io/en/latest/[nose] test runner.
https://pypi.org/project/diff-cover/[diff-cover] can be used to ensure
that all lines edited in a patch have coverage.
It's possible (and recommended) to have the test suite fail if the
coverage percentage goes down. This example `.coveragerc`:
....
[run]
# Track what conditional branches are covered.
branch = True
include =
my_python_package/*
[report]
# Fail if the coverage is not 100%
fail_under = 100
# Display results with up 1/100th of a percent accuracy.
precision = 2
exclude_lines =
pragma: no cover
# Don't complain if tests don't hit defensive assertion code
raise AssertionError
raise NotImplementedError
if __name__ == .__main__.:
omit =
my_python_package/tests/*
....
To configure `pytest` to collect coverage data on your project, edit
`setup.cfg` and add this block, substituting `yourpackage` with the name
of the Python package you are measuring coverage on:
....
[tool:pytest]
addopts = --cov-config .coveragerc --cov=yourpackage --cov-report term --cov-report xml --cov-report html
....
causes coverage (and any test running plugins using coverage) to fail if
the coverage level is not 100%. New projects should enforce 100% test
coverage. Existing projects should ensure test coverage does not drop to
accept a pull request and should increase the minimum test coverage
until it is 100%.
[NOTE]
.Note
====
https://pypi.python.org/pypi/coverage/[coverage] has great
https://coverage.readthedocs.io/en/coverage-4.3.4/excluding.html[exclusion]
support, so you can exclude individual lines, conditional branches,
functions, classes, and whole source files from your coverage report. If
you have code that doesn't make sense to have tests for, you can exclude
it from your coverage report. Remember to leave a comment explaining why
it's excluded!
======= Licenses
The https://pypi.org/project/liccheck/[liccheck] checker can verify that
every dependency in your project has an acceptable license. The
dependencies are checked recursively.
The licenses are validated against a set of acceptable licenses that you
define in a file called `.license_strategy.ini` in your project
directory. Here is an example of such a file, that would accept Free
licenses:
....
[Licenses]
authorized_licenses:
bsd
new bsd
simplified bsd
apache
apache 2.0
apache software
gnu lgpl
gpl v2
gpl v3
lgpl with exceptions or zpl
isc
isc license (iscl)
mit
python software foundation
zpl 2.1
....
The verification is case-insensitive, and is done on both the `license`
and the `classifiers` metadata fields. See
https://pypi.org/project/liccheck/[liccheck]'s documentation for more
details.
You can automate the license check with the following snippet in your
`tox.ini` file:
....
[testenv:licenses]
deps =
liccheck
commands =
liccheck -s .license_strategy.ini
....
Remember to add `licenses` to your Tox `envlist`.
=== Security
The https://pypi.org/project/bandit/[bandit] checker is designed to find
common security issues in Python code.
You can add it to the tests run by Tox by adding the following snippet
to your `tox.ini` file:
....
[testenv:bandit]
deps = bandit
commands =
bandit -r your_project/ -x your_project/tests/ -ll
....
Remember to add `bandit` to your Tox `envlist`.