#134 port over the development guide from pagure fedora-docs
Merged 2 years ago by ryanlerch. Opened 2 years ago by ryanlerch.
ryanlerch/infra-docs-fpo port-development-guide  into  master

@@ -1,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]

@@ -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.

@@ -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 you’re 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.

@@ -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.

@@ -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

+ ----

@@ -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.

@@ -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.

@@ -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.

@@ -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.

@@ -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.

@@ -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"]

+ ....

@@ -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.

@@ -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.

@@ -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.

@@ -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.

@@ -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`.