├── .pep8 ├── doc ├── requirements.txt ├── pkgdb.dia ├── pkgdb2.png ├── _templates │ └── pkgdb-logo.html ├── pkgdb1_shortcomings ├── api.rst ├── _static │ └── site.css ├── contributing.rst ├── index.rst ├── deployment.rst ├── contributors.rst ├── pkgdb_api_design ├── groups.rst ├── new_branch_request_graph ├── Makefile └── development.rst ├── pkgdb2 ├── static │ ├── EOL.png │ ├── Active.png │ ├── Denied.png │ ├── arrow.png │ ├── link.png │ ├── small.gif │ ├── wheel.png │ ├── Approved.png │ ├── Obsolete.png │ ├── Removed.png │ ├── watch_20.png │ ├── small_asc.gif │ ├── small_desc.gif │ ├── icon_koschei.png │ ├── Awaiting Review.png │ ├── images │ │ ├── header-bg.png │ │ ├── animated-overlay.gif │ │ ├── ui-icons_217bc0_256x240.png │ │ ├── ui-icons_2e83ff_256x240.png │ │ ├── ui-icons_3c6eb4_256x240.png │ │ ├── ui-icons_454545_256x240.png │ │ ├── ui-icons_469bdd_256x240.png │ │ ├── ui-icons_666666_256x240.png │ │ ├── ui-icons_6da8d5_256x240.png │ │ ├── ui-icons_cd0a0a_256x240.png │ │ ├── ui-icons_d8e7f3_256x240.png │ │ ├── ui-icons_db3279_256x240.png │ │ ├── ui-icons_f9bd01_256x240.png │ │ ├── ui-icons_ffffff_256x240.png │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ ├── ui-bg_flat_0_eeeeee_40x100.png │ │ ├── ui-bg_flat_55_fbec88_40x100.png │ │ ├── ui-bg_flat_55_ffffff_40x100.png │ │ ├── ui-bg_flat_75_ffffff_40x100.png │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ ├── ui-bg_glass_75_d0e5f5_1x400.png │ │ ├── ui-bg_glass_85_dfeffc_1x400.png │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ ├── ui-bg_gloss-wave_55_5c9ccc_500x100.png │ │ ├── ui-bg_inset-hard_100_f5f8f9_1x100.png │ │ ├── ui-bg_inset-hard_100_fcfdfd_1x100.png │ │ ├── ui-bg_highlight-soft_25_3c6eb4_1x100.png │ │ ├── ui-bg_highlight-soft_50_dddddd_1x100.png │ │ ├── ui-bg_highlight-soft_75_dddddd_1x100.png │ │ └── ui-bg_highlight-soft_100_f6f6f6_1x100.png │ ├── packagedb-logo.png │ ├── Under Development.png │ ├── fedora-infra-icon_review.png │ ├── toggle.css │ └── jquery.flot.categories.js ├── templates │ ├── msg.html │ ├── acl_request.html │ ├── admin.html │ ├── acl_give.html │ ├── collection_new.html │ ├── collection_edit.html │ ├── package_give.html │ ├── admin_namespaces.html │ ├── request_branch.html │ ├── branch_selection.html │ ├── request_unretire.html │ ├── package_new.html │ ├── opensearch.html │ ├── collection.html │ ├── index.html │ ├── list_packagers.html │ ├── actions_update_ro.html │ ├── _formhelpers.html │ ├── package_anitya.html │ ├── list_collections.html │ ├── acl_pending.html │ ├── list_logs.html │ ├── packager.html │ ├── actions_update.html │ ├── package_request.html │ ├── package_timeline.html │ ├── stats.html │ ├── list_packages.html │ ├── acl_update.html │ ├── api.html │ └── list_actions.html ├── lib │ ├── exceptions.py │ └── notifications.py ├── proxy.py ├── doc_utils.py ├── mail_logging.py ├── ui │ └── packagers.py └── api │ └── __init__.py ├── devel └── ansible │ ├── playbook.yml │ └── roles │ ├── core │ └── tasks │ │ └── main.yml │ ├── db │ └── tasks │ │ └── main.yml │ └── dev │ └── tasks │ └── main.yml ├── runtests.sh ├── MANIFEST.in ├── .gitignore ├── test_requirements.txt ├── nosetests ├── requirements.txt ├── createdb.py ├── alembic ├── script.py.mako ├── versions │ ├── 353d208dd699_drop_package_shouldo.py │ ├── 2947b3065e9a_add_the_monitor_field.py │ ├── 1f179f37f12b_add_a_koschei_settings_to_the_package_.py │ ├── 1adacdcd3910_refactor_the_monitoring_flag.py │ ├── 187e9f9ec178_expand_fas_name_to_255_chars.py │ ├── 7fa622e911d_update_log_on_delete.py │ ├── 33d905fb1f55_date_updated_for_collections.py │ ├── 2eef3c8402d0_lower_case_table_name.py │ ├── 80939061434_update_unique_constraints_on_admin_.py │ ├── 27924040e3ad_artifacts.py │ └── 563dd24698a7_add_the_allow_retire_field_to_the_.py └── env.py ├── fedmsg.d └── pkgdb.py ├── utility ├── pkgdb2.conf ├── pkgdb2.wsgi ├── alembic.ini └── pkgdb2.cfg.sample ├── Vagrantfile ├── setup.py ├── runserver.py ├── tests ├── test_flask_api.py ├── test_pkgdb.py ├── test_collection.py ├── test_orphan_group.py └── test_flask_ui_packagers.py └── README.rst /.pep8: -------------------------------------------------------------------------------- 1 | [pep8] 2 | ignore = E265 3 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | cloud_sptheme 3 | -------------------------------------------------------------------------------- /doc/pkgdb.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/doc/pkgdb.dia -------------------------------------------------------------------------------- /doc/pkgdb2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/doc/pkgdb2.png -------------------------------------------------------------------------------- /doc/_templates/pkgdb-logo.html: -------------------------------------------------------------------------------- 1 |

PackageDB

2 | -------------------------------------------------------------------------------- /pkgdb2/static/EOL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/EOL.png -------------------------------------------------------------------------------- /pkgdb2/static/Active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/Active.png -------------------------------------------------------------------------------- /pkgdb2/static/Denied.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/Denied.png -------------------------------------------------------------------------------- /pkgdb2/static/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/arrow.png -------------------------------------------------------------------------------- /pkgdb2/static/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/link.png -------------------------------------------------------------------------------- /pkgdb2/static/small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/small.gif -------------------------------------------------------------------------------- /pkgdb2/static/wheel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/wheel.png -------------------------------------------------------------------------------- /pkgdb2/static/Approved.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/Approved.png -------------------------------------------------------------------------------- /pkgdb2/static/Obsolete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/Obsolete.png -------------------------------------------------------------------------------- /pkgdb2/static/Removed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/Removed.png -------------------------------------------------------------------------------- /pkgdb2/static/watch_20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/watch_20.png -------------------------------------------------------------------------------- /pkgdb2/static/small_asc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/small_asc.gif -------------------------------------------------------------------------------- /pkgdb2/static/small_desc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/small_desc.gif -------------------------------------------------------------------------------- /pkgdb2/static/icon_koschei.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/icon_koschei.png -------------------------------------------------------------------------------- /pkgdb2/static/Awaiting Review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/Awaiting Review.png -------------------------------------------------------------------------------- /pkgdb2/static/images/header-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/header-bg.png -------------------------------------------------------------------------------- /pkgdb2/static/packagedb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/packagedb-logo.png -------------------------------------------------------------------------------- /pkgdb2/static/Under Development.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/Under Development.png -------------------------------------------------------------------------------- /pkgdb2/static/images/animated-overlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/animated-overlay.gif -------------------------------------------------------------------------------- /pkgdb2/static/fedora-infra-icon_review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/fedora-infra-icon_review.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-icons_217bc0_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-icons_217bc0_256x240.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-icons_3c6eb4_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-icons_3c6eb4_256x240.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-icons_469bdd_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-icons_469bdd_256x240.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-icons_666666_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-icons_666666_256x240.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-icons_6da8d5_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-icons_6da8d5_256x240.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-icons_d8e7f3_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-icons_d8e7f3_256x240.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-icons_db3279_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-icons_db3279_256x240.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-icons_f9bd01_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-icons_f9bd01_256x240.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-icons_ffffff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-icons_ffffff_256x240.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-bg_flat_0_eeeeee_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-bg_flat_0_eeeeee_40x100.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-bg_flat_55_fbec88_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-bg_flat_55_fbec88_40x100.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-bg_flat_55_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-bg_flat_55_ffffff_40x100.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-bg_flat_75_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-bg_flat_75_ffffff_40x100.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-bg_glass_75_d0e5f5_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-bg_glass_75_d0e5f5_1x400.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-bg_glass_85_dfeffc_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-bg_glass_85_dfeffc_1x400.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /devel/ansible/playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | become: true 4 | become_method: sudo 5 | vars: 6 | roles: 7 | - core 8 | - db 9 | - dev 10 | -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-bg_inset-hard_100_f5f8f9_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-bg_inset-hard_100_f5f8f9_1x100.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-bg_inset-hard_100_fcfdfd_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-bg_inset-hard_100_fcfdfd_1x100.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-bg_highlight-soft_25_3c6eb4_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-bg_highlight-soft_25_3c6eb4_1x100.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-bg_highlight-soft_50_dddddd_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-bg_highlight-soft_50_dddddd_1x100.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-bg_highlight-soft_75_dddddd_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-bg_highlight-soft_75_dddddd_1x100.png -------------------------------------------------------------------------------- /pkgdb2/static/images/ui-bg_highlight-soft_100_f6f6f6_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-infra/pkgdb2/HEAD/pkgdb2/static/images/ui-bg_highlight-soft_100_f6f6f6_1x100.png -------------------------------------------------------------------------------- /runtests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #PKGDB_CONFIG=../tests/pkgdb_test.cfg PYTHONPATH=pkgdb nosetests \ 3 | 4 | PYTHONPATH=pkgdb ./nosetests \ 5 | --with-coverage --cover-erase --cover-package=pkgdb2 $* 6 | -------------------------------------------------------------------------------- /doc/pkgdb1_shortcomings: -------------------------------------------------------------------------------- 1 | - Changing ACLs requires javascript 2 | -> don't use ajax to change ACLs 3 | - Slow 4 | - Spammy 5 | -> group ACLs changes to reduce spam level 6 | - Group package ownership 7 | 8 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include COPYING README.rst requirements.txt 2 | include runtests.sh createdb.py nosetests 3 | recursive-include pkgdb2 * 4 | recursive-include tests * 5 | recursive-include utility * 6 | recursive-include alembic * 7 | include doc/* 8 | 9 | -------------------------------------------------------------------------------- /pkgdb2/templates/msg.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | 3 | {% block title %} {{ super() }} {% endblock %} 4 | 5 | {% block content %} 6 | 7 | 8 | 9 | 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | build/ 3 | dist/ 4 | doc/_build/ 5 | *egg-info/ 6 | *.cfg 7 | *.wsgi 8 | *.sqlite 9 | *~ 10 | start-pkgdb 11 | .coverage 12 | alembic.ini 13 | .vagrant/ 14 | devel/ansible/playbook.retry 15 | pkgdb2/vagrant_default_config.py 16 | -------------------------------------------------------------------------------- /devel/ansible/roles/core/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install basic packages 3 | dnf: 4 | name: "{{ item }}" 5 | state: present 6 | with_items: 7 | - bash-completion 8 | - dstat 9 | - htop 10 | - screen 11 | - tmux 12 | - tree 13 | -------------------------------------------------------------------------------- /test_requirements.txt: -------------------------------------------------------------------------------- 1 | # This file lists the libraries needed to run the unit-tests 2 | # Used for when working from a virtualenv. 3 | # Use this file by running "$ pip install -r test_requirements.txt" 4 | 5 | # Requests is used to make the tests running on faitout 6 | requests 7 | nose 8 | coverage 9 | psycopg2 10 | pygments 11 | -------------------------------------------------------------------------------- /nosetests: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # EASY-INSTALL-ENTRY-SCRIPT: 'nose==1.3.0','console_scripts','nosetests' 3 | __requires__ = ['nose', 'flask>=0.10'] 4 | import sys 5 | from pkg_resources import load_entry_point 6 | 7 | if __name__ == '__main__': 8 | sys.exit( 9 | load_entry_point('nose', 'console_scripts', 'nosetests')() 10 | ) 11 | -------------------------------------------------------------------------------- /doc/api.rst: -------------------------------------------------------------------------------- 1 | The PackageDB API 2 | ================= 3 | 4 | .. automethod:: pkgdb.api.api_version 5 | 6 | 7 | .. automodule:: pkgdb.api.collections 8 | :members: 9 | 10 | .. automodule:: pkgdb.api.packagers 11 | :members: 12 | 13 | .. automodule:: pkgdb.api.packages 14 | :members: 15 | 16 | .. automodule:: pkgdb.api.acls 17 | :members: 18 | -------------------------------------------------------------------------------- /doc/_static/site.css: -------------------------------------------------------------------------------- 1 | @import url("cloud.css"); 2 | @import url("http://fonts.googleapis.com/css?family=Comfortaa"); 3 | 4 | .pkgdb-logo span { 5 | background: url("pkgdb.png") no-repeat scroll 50% 0 transparent; 6 | display: block; 7 | width: 134px; 8 | height: 64px; 9 | margin-bottom: 10px; 10 | } 11 | 12 | h1.pkgdb-logo { 13 | font-family: 'Comfortaa', sans-serif; 14 | margin-top: -10px; 15 | margin-bottom: 12px; 16 | } 17 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Used for when working from a virtualenv. 2 | # Use this file by running "$ pip install -r requirements.txt" 3 | flask>=0.10 4 | flask-wtf 5 | kitchen 6 | python-fedora>=0.3.33 7 | sqlalchemy>=0.8 8 | python-openid-teams 9 | python-openid-cla 10 | docutils 11 | python-dateutil 12 | dogpile.cache 13 | mock==1.1.2 14 | python-bugzilla>=1.1.0 15 | python-memcached 16 | psutil 17 | blinker # required by Flask for the unit-tests (for signaling) 18 | -------------------------------------------------------------------------------- /createdb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | ## These two lines are needed to run on EL6 4 | __requires__ = ['SQLAlchemy >= 0.8', 'jinja2 >= 2.4'] 5 | import pkg_resources 6 | 7 | from pkgdb2 import APP 8 | from pkgdb2.lib import model 9 | 10 | path_alembic = None 11 | if 'PATH_ALEMBIC_INI' in APP.config \ 12 | and APP.config['PATH_ALEMBIC_INI']: 13 | path_alembic = APP.config['PATH_ALEMBIC_INI'] 14 | model.create_tables(APP.config['DB_URL'], path_alembic, True) 15 | -------------------------------------------------------------------------------- /alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = ${repr(up_revision)} 11 | down_revision = ${repr(down_revision)} 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | ${imports if imports else ""} 16 | 17 | def upgrade(): 18 | ${upgrades if upgrades else "pass"} 19 | 20 | 21 | def downgrade(): 22 | ${downgrades if downgrades else "pass"} 23 | -------------------------------------------------------------------------------- /alembic/versions/353d208dd699_drop_package_shouldo.py: -------------------------------------------------------------------------------- 1 | """Drop Package.shouldopen 2 | 3 | Revision ID: 353d208dd699 4 | Revises: None 5 | Create Date: 2014-05-16 10:08:22.174142 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '353d208dd699' 11 | down_revision = None 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | op.drop_column('Package', 'shouldopen') 19 | 20 | 21 | def downgrade(): 22 | op.add_column( 23 | 'Package', 24 | sa.Column('shouldopen', sa.boolean, default=True, nullable=False) 25 | ) 26 | -------------------------------------------------------------------------------- /fedmsg.d/pkgdb.py: -------------------------------------------------------------------------------- 1 | """ This is an example pkgdb configuration for fedmsg. 2 | By convention, it is normally installed as ``/etc/fedmsg.d/pkgdb.py`` 3 | 4 | For Fedora Infrastructure this file is not needed as we use dynamic 5 | fedmsg endpoints. 6 | 7 | It needs to be globally available so remote consumers know how to find the 8 | pkgdb producer (wsgi process). 9 | """ 10 | 11 | import socket 12 | hostname = socket.gethostname().split('.')[0] 13 | 14 | config = dict( 15 | endpoints={ 16 | "pkgdb2.%s" % hostname: [ 17 | "tcp://127.0.0.1:3005", 18 | ], 19 | }, 20 | ) 21 | -------------------------------------------------------------------------------- /utility/pkgdb2.conf: -------------------------------------------------------------------------------- 1 | # Apache configuration file for pkgdb2 2 | 3 | #WSGIDaemonProcess pkgdb user=apache maximum-requests=1000 display-name=pkgdb processes=2 threads=1 4 | #WSGISocketPrefix run/wsgi 5 | #WSGIRestrictStdout On 6 | #WSGIRestrictSignal Off 7 | #WSGIPythonOptimize 1 8 | 9 | #WSGIScriptAlias /pkgdb /usr/share/pkgdb2/pkgdb2.wsgi 10 | 11 | # 12 | # WSGIProcessGroup pkgdb 13 | # 14 | # # Apache 2.4 15 | # Require all granted 16 | # 17 | # 18 | # # Apache 2.2 19 | # Order deny,allow 20 | # Allow from all 21 | # 22 | # 23 | 24 | -------------------------------------------------------------------------------- /alembic/versions/2947b3065e9a_add_the_monitor_field.py: -------------------------------------------------------------------------------- 1 | """add the monitor field in the Package table 2 | 3 | Revision ID: 2947b3065e9a 4 | Revises: 7fa622e911d 5 | Create Date: 2014-10-10 17:16:29.131691 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '2947b3065e9a' 11 | down_revision = '7fa622e911d' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | ''' Add the `monitor` column on the Package table. ''' 19 | op.add_column( 20 | 'Package', 21 | sa.Column( 22 | 'monitor', 23 | sa.Boolean, 24 | default=False, 25 | server_default='False', 26 | nullable=False) 27 | ) 28 | 29 | 30 | def downgrade(): 31 | ''' Drop the `monitor` column of the Package table. ''' 32 | op.drop_column('Package', 'monitor') 33 | -------------------------------------------------------------------------------- /alembic/versions/1f179f37f12b_add_a_koschei_settings_to_the_package_.py: -------------------------------------------------------------------------------- 1 | """Add a koschei settings to the package table 2 | 3 | Revision ID: 1f179f37f12b 4 | Revises: 1adacdcd3910 5 | Create Date: 2015-06-26 10:38:16.996889 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '1f179f37f12b' 11 | down_revision = '1adacdcd3910' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | ''' Add the `koschei` column on the Package table. ''' 19 | op.add_column( 20 | 'Package', 21 | sa.Column( 22 | 'koschei', 23 | sa.Boolean, 24 | default=False, 25 | server_default='False', 26 | nullable=False) 27 | ) 28 | 29 | 30 | def downgrade(): 31 | ''' Drop the `koschei` column of the Package table. ''' 32 | op.drop_column('Package', 'koschei') 33 | -------------------------------------------------------------------------------- /alembic/versions/1adacdcd3910_refactor_the_monitoring_flag.py: -------------------------------------------------------------------------------- 1 | """Refactor the monitoring flag 2 | 3 | Revision ID: 1adacdcd3910 4 | Revises: 2947b3065e9a 5 | Create Date: 2015-05-18 18:38:11.773711 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '1adacdcd3910' 11 | down_revision = '2947b3065e9a' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | """ Update the monitoring column from boolean to text """ 19 | op.alter_column('Package', 'monitor', 20 | type_=sa.String(10), 21 | existing_type=sa.Boolean(), 22 | ) 23 | 24 | 25 | def downgrade(): 26 | """ Update the monitoring column from text to boolean """ 27 | op.alter_column('Package', 'monitor', 28 | type_=sa.Boolean(), 29 | existing_type=sa.String(10), 30 | ) 31 | -------------------------------------------------------------------------------- /pkgdb2/templates/acl_request.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | {% from "_formhelpers.html" import render_field_in_row %} 3 | 4 | {% block title %} {{ package }} ACL | PkgDB {% endblock %} 5 | 6 | {%block tag %}packages{% endblock %} 7 | 8 | {% block content %} 9 | 10 |

Request ACLs on package: {{ package }}

11 | 12 | 13 |
14 | 15 | {{ render_field_in_row(form.branches) }} 16 | {{ render_field_in_row(form.acl) }} 17 |
18 |

19 | 20 | 21 | {{ form.csrf_token }} 22 |

23 |

Use your mouse or the control key to select multiple 24 | ACLs or branch at once

25 |
26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /pkgdb2/templates/admin.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | 3 | {% block title %} Admin | PkgDB {% endblock %} 4 | 5 | {%block tag %}admin{% endblock %} 6 | 7 | {% block content %} 8 | 9 |

Admin interface

10 | 11 |

12 | This page summarize all the actions an admin can do 13 |

14 | 15 | 37 | 38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /alembic/versions/187e9f9ec178_expand_fas_name_to_255_chars.py: -------------------------------------------------------------------------------- 1 | """Expand fas_name to 255 chars 2 | 3 | Revision ID: 187e9f9ec178 4 | Revises: 27924040e3ad 5 | Create Date: 2016-07-07 19:54:21.331838 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '187e9f9ec178' 11 | down_revision = '27924040e3ad' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | """ Update the fas_name column of PackageListingAcl from 32 chars to 255 19 | """ 20 | op.alter_column( 21 | table_name='PackageListingAcl', 22 | column_name='fas_name', 23 | type_=sa.String(255), 24 | existing_type=sa.String(32) 25 | ) 26 | 27 | 28 | def downgrade(): 29 | """ Update the fas_name column of PackageListingAcl from 255 chars to 32 30 | """ 31 | op.alter_column( 32 | table_name='PackageListingAcl', 33 | column_name='fas_name', 34 | type_=sa.String(32), 35 | existing_type=sa.String(255) 36 | ) 37 | -------------------------------------------------------------------------------- /utility/pkgdb2.wsgi: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | 3 | # Optional: The three lines below are required to run on EL6 as EL6 4 | # has two possible version of python-sqlalchemy and python-jinja2 5 | # These lines make sure the application uses the correct version. 6 | #import __main__ 7 | #__main__.__requires__ = ['SQLAlchemy >= 0.7', 'jinja2 >= 2.4'] 8 | #import pkg_resources 9 | 10 | ## Optional: The following is only needed if you did not install pkgdb 11 | ## as a python module (for example if you run it from a git clone). 12 | #import sys 13 | #sys.path.insert(0, '/path/to/packagedb2/') 14 | 15 | import os 16 | ## Set the environment variable pointing to the configuration file 17 | # Might required to be adjusted to your setup 18 | os.environ['PKGDB2_CONFIG'] = '/etc/pkgdb2/pkgdb2.cfg' 19 | 20 | ## The most import line to make the wsgi working 21 | from pkgdb2 import APP as application 22 | 23 | ## Optional: Turn on the debug mode to get more information in the 24 | ## logs about internal errors 25 | #application.debug = True 26 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure(2) do |config| 5 | config.vm.box_url = "https://download.fedoraproject.org/pub/fedora/linux/releases/23/Cloud/x86_64/Images/Fedora-Cloud-Base-Vagrant-23-20151030.x86_64.vagrant-libvirt.box" 6 | config.vm.box = "f23-cloud-libvirt" 7 | config.vm.network "forwarded_port", guest: 5000, host: 5001 8 | config.vm.synced_folder ".", "/vagrant", type: "sshfs" 9 | 10 | # Ansible needs the guest to have these 11 | config.vm.provision "shell", inline: "sudo dnf install -y libselinux-python python2-dnf" 12 | 13 | config.vm.provision "ansible" do |ansible| 14 | ansible.playbook = "devel/ansible/playbook.yml" 15 | end 16 | 17 | config.vm.post_up_message = "Provisioning Complete. Connect to your new vagrant box with\nvagrant ssh\nThen start the pkdb2 server with\npushd /vagrant/; ./runserver.py -c pkgdb2/vagrant_default_config.py --host \"0.0.0.0\";\nYour fresh pkgdb2 instance will now be accessible at\nhttp://localhost:5001/" 18 | 19 | end 20 | -------------------------------------------------------------------------------- /pkgdb2/templates/acl_give.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | {% from "_formhelpers.html" import render_field_in_row %} 3 | 4 | {% block title %} {{ package }} ACL | PkgDB {% endblock %} 5 | 6 | {%block tag %}packages{% endblock %} 7 | 8 | {% block content %} 9 | 10 |

Give ACLs on package: {{ package }}

11 | 12 | 13 |
15 | 16 | {{ render_field_in_row(form.user) }} 17 | {{ render_field_in_row(form.branches) }} 18 | {{ render_field_in_row(form.acl) }} 19 | {{ render_field_in_row(form.acl_status) }} 20 |
21 |

22 | 23 | 24 | {{ form.csrf_token }} 25 |

26 |

Use your mouse or the control key to select multiple 27 | ACLs or branch at once

28 |
29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /pkgdb2/templates/collection_new.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | {% from "_formhelpers.html" import render_field_in_row %} 3 | 4 | 5 | {% block title %} New collection | PkgDB {% endblock %} 6 | 7 | {%block tag %}collections{% endblock %} 8 | 9 | {% block content %} 10 | 11 |

Create a new collection

12 | 13 |
14 | 15 | {{ render_field_in_row(form.clt_name) }} 16 | {{ render_field_in_row(form.version) }} 17 | {{ render_field_in_row(form.clt_status) }} 18 | {{ render_field_in_row(form.branchname) }} 19 | {{ render_field_in_row(form.kojiname) }} 20 | {{ render_field_in_row(form.dist_tag) }} 21 | {{ render_field_in_row(form.allow_retire) }} 22 |
23 |

24 | 25 | 26 | {{ form.csrf_token }} 27 |

28 | 29 |
30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /pkgdb2/templates/collection_edit.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | {% from "_formhelpers.html" import render_field_in_row %} 3 | 4 | 5 | {% block title %} Edit collection | PkgDB {% endblock %} 6 | 7 | {%block tag %}collections{% endblock %} 8 | 9 | {% block content %} 10 | 11 |

Edit collection

12 | 13 |
14 | 15 | {{ render_field_in_row(form.clt_name) }} 16 | {{ render_field_in_row(form.version) }} 17 | {{ render_field_in_row(form.clt_status) }} 18 | {{ render_field_in_row(form.branchname) }} 19 | {{ render_field_in_row(form.kojiname) }} 20 | {{ render_field_in_row(form.dist_tag) }} 21 | {{ render_field_in_row(form.allow_retire) }} 22 |
23 |

24 | 25 | 26 | {{ form.csrf_token }} 27 |

28 | 29 |
30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /devel/ansible/roles/db/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install database packages 3 | dnf: 4 | name: "{{ item }}" 5 | state: present 6 | with_items: 7 | - postgresql-server 8 | 9 | - name: Initialize PostgreSQL 10 | command: postgresql-setup initdb 11 | args: 12 | creates: /var/lib/pgsql/data/pg_hba.conf 13 | 14 | - replace: 15 | dest: /var/lib/pgsql/data/pg_hba.conf 16 | regexp: "host all all 127.0.0.1/32 ident" 17 | replace: "host all all 127.0.0.1/32 trust" 18 | 19 | - replace: 20 | dest: /var/lib/pgsql/data/pg_hba.conf 21 | regexp: "host all all ::1/128 ident" 22 | replace: "host all all ::1/128 trust" 23 | 24 | - service: 25 | name: postgresql 26 | state: started 27 | enabled: yes 28 | 29 | - name: Create a database for Pkgdb2 30 | shell: runuser -l postgres -c 'createdb pkgdb2' && touch /home/vagrant/.db-created 31 | args: 32 | creates: /home/vagrant/.db-created 33 | -------------------------------------------------------------------------------- /alembic/versions/7fa622e911d_update_log_on_delete.py: -------------------------------------------------------------------------------- 1 | """Update Log on delete 2 | 3 | Revision ID: 7fa622e911d 4 | Revises: 353d208dd699 5 | Create Date: 2014-06-02 12:37:09.917179 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '7fa622e911d' 11 | down_revision = '353d208dd699' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | """ Set the foreign key package_id of Log to SET NULL on delete. """ 19 | op.execute(""" 20 | ALTER TABLE "Log" 21 | DROP CONSTRAINT "Log_package_id_fkey", 22 | ADD CONSTRAINT "Log_package_id_fkey" 23 | FOREIGN KEY (package_id) 24 | REFERENCES "Package"(id) 25 | ON DELETE SET NULL 26 | ON UPDATE CASCADE; 27 | """) 28 | 29 | 30 | def downgrade(): 31 | """ Set back the foreign key package_id of Log to RESTRICT on delete. 32 | """ 33 | op.execute(""" 34 | ALTER TABLE "Log" 35 | DROP CONSTRAINT "Log_package_id_fkey", 36 | ADD CONSTRAINT "Log_package_id_fkey" 37 | FOREIGN KEY (package_id) 38 | REFERENCES "Package"(id) 39 | ON DELETE RESTRICT 40 | ON UPDATE CASCADE; 41 | """) 42 | -------------------------------------------------------------------------------- /pkgdb2/templates/package_give.html: -------------------------------------------------------------------------------- 1 | {% if full %} 2 | {% extends "master.html" %} 3 | {% block title %} Give package | PkgDB {% endblock %} 4 | {%block tag %}packages{% endblock %} 5 | {% endif %} 6 | 7 | {% from "_formhelpers.html" import render_field_in_row %} 8 | 9 | 10 | {% block content %} 11 | {% if not full %} 12 |
13 | {% endif %} 14 | 15 | 16 |

Give Point of Contact of package: {{packagename}}

17 | 18 | 19 |
22 | 23 | {{ render_field_in_row(form.branches) }} 24 | {{ render_field_in_row(form.poc) }} 25 |
26 |

27 | 28 | {% if full %} 29 | 30 | {% endif %} 31 | {{ form.csrf_token }} 32 |

33 |

Use your mouse or the control key to select multiple ACLs at once

34 |
35 | {% if not full %} 36 |
37 | {% endif %} 38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /alembic/versions/33d905fb1f55_date_updated_for_collections.py: -------------------------------------------------------------------------------- 1 | """date_updated for collections 2 | 3 | Revision ID: 33d905fb1f55 4 | Revises: 563dd24698a7 5 | Create Date: 2015-11-03 09:47:01.276762 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '33d905fb1f55' 11 | down_revision = '563dd24698a7' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | ''' Add the column closed_at to the table pull_requests. 19 | ''' 20 | op.add_column( 21 | 'Collection', 22 | sa.Column( 23 | 'date_updated', 24 | sa.DateTime, 25 | nullable=True, 26 | default=sa.func.now(), 27 | onupdate=sa.func.now() 28 | ) 29 | ) 30 | 31 | op.execute('''UPDATE "Collection" SET date_updated=date_created;''') 32 | 33 | op.alter_column( 34 | 'Collection', 35 | column_name='date_updated', 36 | nullable=False, 37 | existing_nullable=True) 38 | 39 | 40 | def downgrade(): 41 | ''' Drop status from the column date_updated of the Collection table 42 | ''' 43 | op.drop_column('Collection', 'date_updated') 44 | -------------------------------------------------------------------------------- /pkgdb2/templates/admin_namespaces.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | 3 | {% block title %} Admin | PkgDB {% endblock %} 4 | 5 | {%block tag %}admin{% endblock %} 6 | 7 | {% block content %} 8 | 9 |

Namespaces

10 | 11 |

12 | This page lists all the namespaces currently in the DB. 13 |

14 | 15 | 16 | {% for namespace in namespaces %} 17 | 18 | 19 | 27 | 28 | {% endfor %} 29 |
{{ namespace }} 20 |
21 | 22 | {{ form.csrf_token }} 23 | 25 |
26 |
30 | 31 |

Add a new namespace

32 |
33 | 34 | {{ form.csrf_token }} 35 | 36 |
37 | 38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /utility/alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # path to migration scripts 5 | 6 | # In production 7 | # script_location = /usr/share/pkgdb2/alembic 8 | # For development 9 | script_location = alembic 10 | 11 | # template used to generate migration files 12 | # file_template = %%(rev)s_%%(slug)s 13 | 14 | # set to 'true' to run the environment during 15 | # the 'revision' command, regardless of autogenerate 16 | # revision_environment = false 17 | 18 | sqlalchemy.url = driver://user:pass@localhost/dbname 19 | 20 | 21 | # Logging configuration 22 | [loggers] 23 | keys = root,sqlalchemy,alembic 24 | 25 | [handlers] 26 | keys = console 27 | 28 | [formatters] 29 | keys = generic 30 | 31 | [logger_root] 32 | level = WARN 33 | handlers = console 34 | qualname = 35 | 36 | [logger_sqlalchemy] 37 | level = WARN 38 | handlers = 39 | qualname = sqlalchemy.engine 40 | 41 | [logger_alembic] 42 | level = INFO 43 | handlers = 44 | qualname = alembic 45 | 46 | [handler_console] 47 | class = StreamHandler 48 | args = (sys.stderr,) 49 | level = NOTSET 50 | formatter = generic 51 | 52 | [formatter_generic] 53 | format = %(levelname)-5.5s [%(name)s] %(message)s 54 | datefmt = %H:%M:%S 55 | -------------------------------------------------------------------------------- /doc/contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | If you're submitting patches to pkgdb2, please observe the following: 5 | 6 | - Check that your python code is `PEP8-compliant 7 | `_. There is a `pep8 tool 8 | `_ that can automatically check 9 | your source. 10 | 11 | - Check that your code doesn't break the test suite. The test suite can be 12 | run using the ``runtests.sh`` shell script at the top of the sources. 13 | See :doc:`development` for more information about the test suite. 14 | 15 | - If you are adding new code, please write tests for them in ``tests/``, 16 | the ``runtests.sh`` script will help you to see the coverage of your code 17 | in unit-tests. 18 | 19 | - If your change warrants a modification to the docs in ``doc/`` or any 20 | docstrings in ``pkgdb2/`` please make that modification. 21 | 22 | .. note:: You have a doubt, you don't know how to do something, you have an 23 | idea but don't know how to implement it, you just have something bugging 24 | you? 25 | 26 | Come to see us on IRC: ``#fedora-apps`` on irc.freenode.net or via the 27 | `trac of the project `_. 28 | -------------------------------------------------------------------------------- /alembic/versions/2eef3c8402d0_lower_case_table_name.py: -------------------------------------------------------------------------------- 1 | """lower case table name 2 | 3 | Revision ID: 2eef3c8402d0 4 | Revises: 187e9f9ec178 5 | Create Date: 2016-09-01 10:22:12.915860 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '2eef3c8402d0' 11 | down_revision = '187e9f9ec178' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | TABLES = { 17 | 'PkgAcls': 'pkg_acls', 18 | 'PkgStatus': 'pkg_status', 19 | 'AclStatus': 'acl_status', 20 | 'CollecStatus': 'collection_status', 21 | 'PackageListingAcl': 'package_listing_acl', 22 | 'Collection': 'collection', 23 | 'PackageListing': 'package_listing', 24 | 'Package': 'package', 25 | 'Log': 'log', 26 | } 27 | 28 | 29 | def upgrade(): 30 | """ Set all the table names to lower case to make it easier to write 31 | sql query manually on postgresql. """ 32 | for table in TABLES: 33 | op.rename_table(table, TABLES[table]) 34 | 35 | 36 | def downgrade(): 37 | """ Set all the table names back to be camel case. """ 38 | # Invert the dict 39 | old_tables = {TABLES[table]: table for table in TABLES} 40 | 41 | for table in TABLES: 42 | op.rename_table(table, TABLES[table]) 43 | -------------------------------------------------------------------------------- /pkgdb2/templates/request_branch.html: -------------------------------------------------------------------------------- 1 | {% if full %} 2 | {% extends "master.html" %} 3 | {% block title %} Select branches | PkgDB {% endblock %} 4 | {%block tag %}packages{% endblock %} 5 | {% endif %} 6 | 7 | {% from "_formhelpers.html" import render_field_in_row %} 8 | 9 | 10 | {% block content %} 11 | {% if not full %} 12 |
13 | {% endif %} 14 | 15 |

16 | Request new branch for package: 17 | 19 | {{ package.name }} 20 | 21 |

22 | 23 |
26 | 27 | {{ render_field_in_row(form.branches) }} 28 |
29 |

30 | 31 | {% if full %} 32 | 33 | {% endif %} 34 | {{ form.csrf_token }} 35 |

36 |

Use your mouse or the control key to select multiple 37 | branch at once

38 |
39 | {% if not full %} 40 |
41 | {% endif %} 42 | {% endblock %} 43 | -------------------------------------------------------------------------------- /pkgdb2/templates/branch_selection.html: -------------------------------------------------------------------------------- 1 | {% if full %} 2 | {% extends "master.html" %} 3 | {% block title %} Select branches | PkgDB {% endblock %} 4 | {%block tag %}packages{% endblock %} 5 | {% endif %} 6 | 7 | {% from "_formhelpers.html" import render_field_in_row %} 8 | 9 | 10 | {% block content %} 11 | {% if not full %} 12 |
13 | {% endif %} 14 | 15 |

16 | Select branches of package: 17 | 19 | {{ package.name }} 20 | 21 | to {{ action }} 22 |

23 | 24 |
27 | 28 | {{ render_field_in_row(form.branches) }} 29 |
30 |

31 | 32 | {% if full %} 33 | 34 | {% endif %} 35 | {{ form.csrf_token }} 36 |

37 |

Use your mouse or the control key to select multiple 38 | ACLs or branch at once

39 |
40 | {% if not full %} 41 |
42 | {% endif %} 43 | {% endblock %} 44 | -------------------------------------------------------------------------------- /alembic/versions/80939061434_update_unique_constraints_on_admin_.py: -------------------------------------------------------------------------------- 1 | """Update unique constraints on admin_actions 2 | 3 | Revision ID: 80939061434 4 | Revises: 1f179f37f12b 5 | Create Date: 2015-10-05 11:16:07.256121 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '80939061434' 11 | down_revision = '1f179f37f12b' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | """ Drop status from the unique constraing of the admin_actions table. 19 | """ 20 | op.drop_constraint( 21 | "admin_actions_user_action_status_package_id_collection_id_key", 22 | "admin_actions", 23 | type_='unique', 24 | ) 25 | op.create_unique_constraint( 26 | "admin_actions_user_action_package_id_collection_id_key", 27 | "admin_actions", 28 | ['user', 'action', 'package_id', 'collection_id'], 29 | ) 30 | 31 | 32 | def downgrade(): 33 | """ Add status to the unique constraing of the admin_actions table. """ 34 | op.drop_constraint( 35 | "admin_actions_user_action_package_id_collection_id_key", 36 | "admin_actions", 37 | type_='unique', 38 | ) 39 | op.create_unique_constraint( 40 | "admin_actions_user_action_status_package_id_collection_id_key", 41 | "admin_actions", 42 | ['user', 'action', 'status', 'package_id', 'collection_id'], 43 | ) 44 | -------------------------------------------------------------------------------- /pkgdb2/lib/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2014 Red Hat, Inc. 4 | # 5 | # This copyrighted material is made available to anyone wishing to use, 6 | # modify, copy, or redistribute it subject to the terms and conditions 7 | # of the GNU General Public License v.2, or (at your option) any later 8 | # version. This program is distributed in the hope that it will be 9 | # useful, but WITHOUT ANY WARRANTY expressed or implied, including the 10 | # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR 11 | # PURPOSE. See the GNU General Public License for more details. You 12 | # should have received a copy of the GNU General Public License along 13 | # with this program; if not, write to the Free Software Foundation, 14 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | # 16 | # Any Red Hat trademarks that are incorporated in the source 17 | # code or documentation are not subject to the GNU General Public 18 | # License and may only be used or replicated with the express permission 19 | # of Red Hat, Inc. 20 | # 21 | 22 | ''' 23 | PkgDB exceptions classes. 24 | ''' 25 | 26 | 27 | class PkgdbException(Exception): 28 | """ Generic Exception object used to throw pkgdb2 specific error. 29 | """ 30 | pass 31 | 32 | 33 | class PkgdbBugzillaException(PkgdbException): 34 | """ Generic Exception object used to throw pkgdb2 error specifically 35 | related to its interaction with bugzilla. 36 | """ 37 | pass 38 | -------------------------------------------------------------------------------- /pkgdb2/templates/request_unretire.html: -------------------------------------------------------------------------------- 1 | {% if full %} 2 | {% extends "master.html" %} 3 | {% block title %} Select branches | PkgDB {% endblock %} 4 | {%block tag %}packages{% endblock %} 5 | {% endif %} 6 | 7 | {% from "_formhelpers.html" import render_field_in_row %} 8 | 9 | 10 | {% block content %} 11 | {% if not full %} 12 |
13 | {% endif %} 14 | 15 |

16 | Select branches of package: 17 | 19 | {{ package.name }} 20 | 21 | to {{ action }} 22 |

23 | 24 |
27 | 28 | {{ render_field_in_row(form.branches) }} 29 | {{ render_field_in_row(form.review_url, after="Required if you're un-retiring the master branch or an EPEL branch while keeping the master branch retired") }} 30 |
31 |

32 | 33 | {% if full %} 34 | 35 | {% endif %} 36 | {{ form.csrf_token }} 37 |

38 |

Use your mouse or the control key to select multiple 39 | ACLs or branch at once

40 |
41 | {% if not full %} 42 |
43 | {% endif %} 44 | {% endblock %} 45 | -------------------------------------------------------------------------------- /pkgdb2/templates/package_new.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | {% from "_formhelpers.html" import render_field_in_row %} 3 | 4 | {% block title %} New package | PkgDB {% endblock %} 5 | 6 | {%block tag %}packages{% endblock %} 7 | 8 | {% block content %} 9 | 10 |

Create a new package

11 | 12 | 13 |
14 | 15 | {{ render_field_in_row(form.pkgname) }} 16 | {{ render_field_in_row(form.summary) }} 17 | {{ render_field_in_row(form.description, rows=5) }} 18 | {{ render_field_in_row(form.status) }} 19 | {{ render_field_in_row(form.branches) }} 20 | {{ render_field_in_row(form.poc) }} 21 | {{ render_field_in_row(form.critpath) }} 22 | {{ render_field_in_row(form.review_url, title="URL pointing directly to the 23 | location where the package review happened", 24 | placeholder="https://bugzilla.redhat.com/1234") }} 25 | {{ render_field_in_row(form.upstream_url, title="URL of the upstream project (if any)") }} 26 | {{ render_field_in_row(form.monitoring_status, title="New release monitoring") }} 27 | {{ render_field_in_row(form.koschei, title="Koschei integration/monitoring") }} 28 | {{ render_field_in_row(form.namespace) }} 29 |
30 |

31 | 32 | 33 | {{ form.csrf_token }} 34 |

35 | 36 |
37 | {% endblock %} 38 | -------------------------------------------------------------------------------- /pkgdb2/templates/opensearch.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | pkgdb {{ shortname }} 6 | Search {{ config['PROJECT_NAME'] }} Package Database 7 | UTF-8 8 | 9 | https://fedoraproject.org/static/images/icons/fedora-infra-icon_pkgdb.png 10 | pingou@pingoured.fr 11 | 13 | 14 | 15 | 16 | 18 | 21 | {{ config.get('SITE_ROOT') }}{{ url_for('.index') }} 22 | pkgdb Web OpenSearch 23 | 24 | Adam Miller 25 | Pierre-Yves Chibon 26 | pkgdb 27 | 28 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Setup script 5 | """ 6 | 7 | # Required to build on EL6 8 | __requires__ = ['SQLAlchemy >= 0.7', 'jinja2 >= 2.4'] 9 | import pkg_resources 10 | 11 | from setuptools import setup 12 | from pkgdb2 import __version__ 13 | 14 | 15 | def get_requirements(requirements_file='requirements.txt'): 16 | """Get the contents of a file listing the requirements. 17 | 18 | :arg requirements_file: path to a requirements file 19 | :type requirements_file: string 20 | :returns: the list of requirements, or an empty list if 21 | `requirements_file` could not be opened or read 22 | :return type: list 23 | """ 24 | 25 | lines = open(requirements_file).readlines() 26 | return [ 27 | line.rstrip().split('#')[0] 28 | for line in lines 29 | if not line.startswith('#') 30 | ] 31 | 32 | 33 | setup( 34 | name='pkgdb2', 35 | description='Pkgdb2 is the newest Package database for Fedora.', 36 | version=__version__, 37 | author='Pierre-Yves Chibon', 38 | author_email='pingou@pingoured.fr', 39 | maintainer='Pierre-Yves Chibon', 40 | maintainer_email='pingou@pingoured.fr', 41 | license='GPLv3+', 42 | download_url='https://fedorahosted.org/releases/p/k/pkgdb2/', 43 | url='https://fedorahosted.org/pkgdb2/', 44 | packages=['pkgdb2'], 45 | include_package_data=True, 46 | install_requires=get_requirements(), 47 | scripts=[ 48 | 'utility/pkgdb2_branch.py', 49 | 'utility/pkgdb-sync-bugzilla', 50 | 'utility/update_package_info.py', 51 | ], 52 | ) 53 | -------------------------------------------------------------------------------- /alembic/versions/27924040e3ad_artifacts.py: -------------------------------------------------------------------------------- 1 | """namespaces 2 | 3 | 4 | Revision ID: 27924040e3ad 5 | Revises: 33d905fb1f55 6 | Create Date: 2015-11-21 11:59:21.906604 7 | 8 | """ 9 | 10 | # revision identifiers, used by Alembic. 11 | revision = '27924040e3ad' 12 | down_revision = '33d905fb1f55' 13 | 14 | from alembic import op 15 | import sqlalchemy as sa 16 | 17 | 18 | def upgrade(): 19 | ''' Add the artifact field to the Package table, fill it and adjust 20 | the unique key constraints. 21 | ''' 22 | 23 | op.add_column( 24 | 'Package', 25 | sa.Column( 26 | 'namespace', 27 | sa.String(50), 28 | sa.ForeignKey( 29 | 'namespaces.namespace', 30 | onupdate='CASCADE', 31 | ondelete='CASCADE', 32 | ), 33 | default='rpms', 34 | ) 35 | ) 36 | 37 | op.execute('''UPDATE "Package" SET namespace='rpms';''') 38 | 39 | op.alter_column( 40 | 'Package', 41 | column_name='namespace', 42 | nullable=False, 43 | existing_nullable=True) 44 | 45 | op.execute(""" 46 | DROP INDEX IF EXISTS "ix_Package_name"; 47 | ALTER TABLE "Package" 48 | ADD CONSTRAINT "ix_package_name_namespace" UNIQUE (name, namespace); 49 | """) 50 | 51 | 52 | def downgrade(): 53 | ''' Drop the artifact field to the Package table and adjust the unique 54 | key constraints. 55 | ''' 56 | op.drop_column('Package', 'namespace') 57 | op.execute(""" 58 | DROP INDEX IF EXISTS "ix_package_name_namespace"; 59 | ALTER TABLE "Package" 60 | ADD CONSTRAINT "ix_Package_name" UNIQUE (name); 61 | """) 62 | -------------------------------------------------------------------------------- /pkgdb2/templates/collection.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | 3 | {% block title %} {{ collection.name }} {{ collection.version }} | PkgDB {% endblock %} 4 | 5 | {%block tag %}collections{% endblock %} 6 | 7 | {% block content %} 8 | 9 |

{{ collection.name }} {{ collection.version }}

10 | 11 |

12 | Status : {{ collection.status }} 14 | {{ collection.status }} 15 | ( 16 | edit 17 | ) 18 |

19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
Name:{{ collection.name }}
Version:{{ collection.version }}
Branch-name:{{ collection.branchname }}
Description:{{ collection.description }}
Dist-tag:{{ collection.dist_tag }}
Allows package retirement{{ collection.allow_retire }}
Created on{{ collection.date_created.strftime('%Y-%m-%d %H:%M:%S') }}
Last update on{{ collection.date_updated.strftime('%Y-%m-%d %H:%M:%S') }}
54 | {% endblock %} 55 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | Pkgdb2 2 | ======= 3 | 4 | The Pkgdb project is the application handling who is allowed to work on 5 | which package present in the Fedora repositories. 6 | 7 | 8 | 9 | Resources: 10 | 11 | - `Home page `_ 12 | - `Documentation `_ 13 | - `Git repository `_ 14 | - `Github mirror `_ 15 | - `Discussion mailing-list 16 | `_ 17 | 18 | 19 | Contents: 20 | 21 | .. toctree:: 22 | :maxdepth: 2 23 | 24 | deployment 25 | configuration 26 | groups 27 | development 28 | contributing 29 | contributors 30 | 31 | 32 | 33 | Indices and tables 34 | ================== 35 | 36 | * :ref:`genindex` 37 | * :ref:`modindex` 38 | * :ref:`search` 39 | 40 | 41 | 42 | FAQ 43 | === 44 | 45 | Here are some answers to frequently-asked questions from IRC and elsewhere. 46 | Got a question that isn't answered here? Try `IRC `_, 47 | the `mailing list `_. 48 | 49 | How do I... 50 | =========== 51 | 52 | ...specify comaintainers on a new package request? 53 | -------------------------------------------------- 54 | 55 | Once the package is created you can add other packagers, pseudo-users or groups: 56 | 57 | 1. Go to one of your package: https://admin.fedoraproject.org/pkgdb/package// 58 | 2. Click on: Manage the committers/watchers/package administrators/main contacts (any of these) 59 | 3. There is then an ``Add`` someone button you can use. 60 | 61 | -------------------------------------------------------------------------------- /runserver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | # These two lines are needed to run on EL6 4 | __requires__ = ['SQLAlchemy >= 0.8', 'jinja2 >= 2.4'] 5 | import pkg_resources 6 | 7 | import argparse 8 | import sys 9 | import os 10 | 11 | 12 | parser = argparse.ArgumentParser( 13 | description='Run the packages2 app') 14 | parser.add_argument( 15 | '--config', '-c', dest='config', 16 | help='Configuration file to use for packages.') 17 | parser.add_argument( 18 | '--debug', dest='debug', action='store_true', 19 | default=False, 20 | help='Expand the level of data returned.') 21 | parser.add_argument( 22 | '--profile', dest='profile', action='store_true', 23 | default=False, 24 | help='Profile the packages2 application.') 25 | parser.add_argument( 26 | '--port', '-p', default=5000, 27 | help='Port for the flask application.') 28 | parser.add_argument( 29 | '--host', default="127.0.0.1", 30 | help='Hostname to listen on. When set to 0.0.0.0 the server is available \ 31 | externally. Defaults to 127.0.0.1 making the it only visable on localhost') 32 | 33 | args = parser.parse_args() 34 | 35 | if args.config: 36 | config = args.config 37 | if not config.startswith('/'): 38 | here = os.path.join(os.path.dirname(os.path.abspath(__file__))) 39 | config = os.path.join(here, config) 40 | os.environ['PKGDB2_CONFIG'] = config 41 | 42 | from pkgdb2 import APP 43 | 44 | if args.profile: 45 | from werkzeug.contrib.profiler import ProfilerMiddleware 46 | APP.config['PROFILE'] = True 47 | APP.wsgi_app = ProfilerMiddleware(APP.wsgi_app, restrictions=[30]) 48 | 49 | APP.debug = True 50 | APP.run(port=int(args.port), host=args.host) 51 | -------------------------------------------------------------------------------- /pkgdb2/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | 3 | {% block title %} {{ super() }} {% endblock %} 4 | 5 | {%block tag %}index{% endblock %} 6 | 7 | {% block content %} 8 | 9 |

{{ config['PROJECT_NAME'] }} Package Database

10 | 11 |

Overview

12 | 13 |

14 | The Package Database is a central repository of package information in 15 | {{ config['PROJECT_NAME'] }}. You will eventually be able to find and change all the 16 | metainformation about a package by searching the database. The current 17 | implementation is focused on the data that package developers and release 18 | engineers need to create packages and spin them into a distribution. 19 |

20 | 21 |

22 | 24 | 27 | 28 |

29 | 30 |

Recent packages added

31 | 32 | 33 | 34 | 35 | 36 | 37 | {% for pkg in latest_pkgs %} 38 | 39 | 43 | 44 | 50 | 51 | {% endfor %} 52 |
PackageSummaryBranches
41 | {{ pkg.namespace }}/{{ pkg.name }} 42 | {{ pkg.summary }} 45 | {% for listing in pkg.listings | sort(attribute="collection.branchname", reverse=True) 46 | %} {% if listing.collection.status != 'EOL' %} 47 | {{ listing.collection.branchname }} 48 | {% endif %}{% endfor %} 49 |
53 | 54 | {% endblock %} 55 | -------------------------------------------------------------------------------- /pkgdb2/templates/list_packagers.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | 3 | {% block title %} {{ super() }} {% endblock %} 4 | 5 | {%block tag %}packagers{% endblock %} 6 | 7 | {% block content %} 8 | 9 |

Search packagers

10 | 11 | {% if total_page and total_page > 1 and total_page >= page %} 12 | 13 | 14 | 23 | 24 | 33 | 34 | 35 |
15 | {% if page > 1 %} 16 | 17 | < Previous 18 | 19 | {% else %} 20 | < Previous 21 | {% endif %} 22 | {{ page }} / {{ total_page }} 25 | {% if page < total_page %} 26 | 27 | Next > 28 | 29 | {% else %} 30 | Next > 31 | {% endif %} 32 |
36 | {% endif %} 37 | 38 |
39 | 59 |
60 | 61 | {% endblock %} 62 | -------------------------------------------------------------------------------- /devel/ansible/roles/dev/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install dev packages 3 | dnf: 4 | name: "{{ item }}" 5 | state: present 6 | with_items: 7 | - gcc 8 | - postgresql-devel 9 | - python 10 | - python-devel 11 | - python-alembic 12 | - python-psycopg2 13 | - redhat-rpm-config 14 | 15 | - name: Install python packages 16 | pip: 17 | name: "{{ item }}" 18 | state: present 19 | with_items: 20 | - kitchen 21 | - paver 22 | - urllib3 23 | 24 | - name: Install python packages from requirements.txt 25 | pip: 26 | requirements: /vagrant/requirements.txt 27 | 28 | - name: Retrieve database dump 29 | get_url: 30 | url: https://infrastructure.fedoraproject.org/infra/db-dumps/pkgdb2.dump.xz 31 | dest: /tmp/pkgdb2.dump.xz 32 | 33 | - shell: xzcat /tmp/pkgdb2.dump.xz | runuser -l postgres -c 'psql pkgdb2' && touch /home/vagrant/.db-imported 34 | args: 35 | creates: /home/vagrant/.db-imported 36 | 37 | - command: cp /vagrant/pkgdb2/default_config.py /vagrant/pkgdb2/vagrant_default_config.py 38 | args: 39 | creates: /vagrant/pkgdb2/vagrant_default_config.py 40 | 41 | - replace: 42 | dest: /vagrant/pkgdb2/vagrant_default_config.py 43 | regexp: "sqlite:////var/tmp/pkgdb2_dev.sqlite" 44 | replace: "postgresql://postgres:whatever@localhost/pkgdb2" 45 | 46 | - command: cp /vagrant/utility/alembic.ini /vagrant/alembic.ini 47 | args: 48 | creates: /vagrant/alembic.ini 49 | 50 | - replace: 51 | dest: /vagrant/alembic.ini 52 | regexp: "driver://user:pass@localhost/dbname" 53 | replace: "postgresql://postgres:whatever@localhost/pkgdb2" 54 | 55 | - name: Apply database migrations 56 | command: alembic upgrade head 57 | args: 58 | chdir: /vagrant 59 | -------------------------------------------------------------------------------- /alembic/versions/563dd24698a7_add_the_allow_retire_field_to_the_.py: -------------------------------------------------------------------------------- 1 | """Add the allow_retire field to the collection table 2 | 3 | Revision ID: 563dd24698a7 4 | Revises: 80939061434 5 | Create Date: 2015-10-20 17:14:40.996865 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '563dd24698a7' 11 | down_revision = '80939061434' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | ''' Add the `allow_retire` column on the Collection table. ''' 19 | op.add_column( 20 | 'Collection', 21 | sa.Column( 22 | 'allow_retire', 23 | sa.Boolean, 24 | default=False, 25 | server_default='False', 26 | nullable=True) 27 | ) 28 | # Set True to EPEL - correct behavior 29 | op.execute('''UPDATE "Collection" ''' 30 | '''SET allow_retire=TRUE WHERE name='Fedora EPEL';''') 31 | # Set FALSE to all EOL releases - correct behavior 32 | op.execute('''UPDATE "Collection" ''' 33 | '''SET allow_retire=FALSE WHERE status='EOL';''') 34 | # Set FALSE to all Active releases - correct behavior 35 | op.execute('''UPDATE "Collection" ''' 36 | '''SET allow_retire=FALSE WHERE status='Active';''') 37 | # Set True to all releases Under Development - mostly correct, except for 38 | # after final freeze 39 | op.execute('''UPDATE "Collection" ''' 40 | '''SET allow_retire=TRUE WHERE status='Under Development';''') 41 | 42 | op.alter_column( 43 | 'Collection', 44 | column_name='allow_retire', 45 | nullable=False, 46 | existing_nullable=True) 47 | 48 | 49 | def downgrade(): 50 | ''' Drop the `allow_retire` column of the Collection table. ''' 51 | op.drop_column('Collection', 'allow_retire') 52 | -------------------------------------------------------------------------------- /pkgdb2/templates/actions_update_ro.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | {% from "_formhelpers.html" import render_field_in_row %} 3 | 4 | {% block title %} Branch Request {{ admin_action.id }} | PkgDB {% endblock %} 5 | 6 | {%block tag %}packages{% endblock %} 7 | 8 | {% block content %} 9 | 10 |

Request: {{ admin_action.id }}

11 | 12 | 13 | 14 | 15 | 16 | 27 | 28 | 29 | 30 | 31 | 32 | {% if admin_action.action == 'request.package' 33 | or admin_action.action == 'request.unretire' %} 34 | 35 | 36 | 41 | 42 | 43 | 44 | 47 | 48 | 49 | 50 | 53 | 54 | {% endif %} 55 |
User:{{ admin_action.user }}
Package: 17 | {% if admin_action.package %} 18 | 21 | {{ admin_action.package.name }} 22 | 23 | {% else %} 24 | {{ admin_action.info_data['pkg_name'] }} 25 | {% endif %} 26 |
Action:{{ admin_action.action }}
To branch:{{ admin_action.collection.branchname }}
Status{{ admin_action.status }}
Message{{ admin_action.message }}
Ticket 37 | 38 | {{ admin_action.info_data['pkg_review_url'] }} 39 | 40 |
Monitoring status 45 | {{ admin_action.info_data.get('monitoring_status', 'True') }} 46 |
Koschei integration 51 | {{ admin_action.info_data.get('koschei', 'False') }} 52 |
56 | 57 | {% endblock %} 58 | -------------------------------------------------------------------------------- /pkgdb2/templates/_formhelpers.html: -------------------------------------------------------------------------------- 1 | {% macro render_field(field, after="") %} 2 |
3 | {{ field.label }}{{ field(**kwargs)|safe }} 4 | {% if field.flags.required %}*{% endif %} 5 | {% if after %} {{ after }}{% endif %} 6 | {% if field.errors %} 7 |
    8 | {% for error in field.errors %} 9 |
  • {{ error }}
  • 10 | {% endfor %} 11 |
12 | {% endif %} 13 |
14 | {% endmacro %} 15 | 16 | {% macro render_field_invert(field, after="") %} 17 |
18 | {{ field(**kwargs)|safe }}{{ field.label }} 19 | {% if field.flags.required %}*{% endif %} 20 | {% if after %} {{ after }}{% endif %} 21 | {% if field.errors %} 22 |
    23 | {% for error in field.errors %} 24 |
  • {{ error }}
  • 25 | {% endfor %} 26 |
27 | {% endif %} 28 |
29 | {% endmacro %} 30 | 31 | {% macro render_field_in_row(field, after="") %} 32 | 33 | 34 | {{ field.label }} 35 | {% if field.flags.required %}*{% endif %} 36 | 37 | {{ field(**kwargs)|safe }} 38 | {% if after %} {{ after }}{% endif %} 39 | {% if field.errors %} 40 | {% for error in field.errors %} 41 | {{ error }} 42 | {% endfor %} 43 | {% endif %} 44 | 45 | {% endmacro %} 46 | 47 | {% macro render_field_in_cell(field, after="") %} 48 | 49 | {{ field.label }} 50 | {% if field.flags.required %}*{% endif %} 51 | 52 | {{ field(**kwargs)|safe }} 53 | {% if after %} {{ after }}{% endif %} 54 | {% if field.errors %} 55 | {% for error in field.errors %} 56 | {{ error }} 57 | {% endfor %} 58 | {% endif %} 59 | {% endmacro %} 60 | -------------------------------------------------------------------------------- /pkgdb2/templates/package_anitya.html: -------------------------------------------------------------------------------- 1 | {% if full %} 2 | {% extends "master.html" %} 3 | {% block title %} Anitya integration | PkgDB {% endblock %} 4 | {%block tag %}packages{% endblock %} 5 | {% endif %} 6 | 7 | {% from "_formhelpers.html" import render_field_in_row %} 8 | 9 | 10 | {% block content %} 11 | {% if not full %} 12 |
13 | {% endif %} 14 | 15 | {% if not full %} 16 | {% with messages = get_flashed_messages(with_categories=true) %} 17 | {% if category, messages %} 18 |
    19 | {% for category, message in messages %} 20 |
  • {{ message }}
  • 21 | {% endfor %} 22 |
23 | {% endif %} 24 | {% endwith %} 25 | {% endif %} 26 | 27 |

Anitya integration for package: {{ package }}

28 | 29 | 30 | {% if not data or data['output'] == 'notok' %} 31 |

32 | This package could not be found in 33 | anitya. 34 |

35 |

36 | 38 | Add it yourself! 39 | 40 |

41 | {% else %} 42 | 43 | 44 | 45 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 |
Project 46 | {{ config['PKGDB2_ANITYA_URL'] }}/project/{{ data['id'] }} 47 |
Homepage{{ data['homepage'] }}
Backend{{ data['backend'] }}
Latest versions{{ data['version'] }}
All versions found{{ data['versions'] | join(', ') }}
66 | {% endif %} 67 | 68 | {% if not full %} 69 |
70 | {% endif %} 71 | {% endblock %} 72 | -------------------------------------------------------------------------------- /pkgdb2/templates/list_collections.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | 3 | {% block title %} {{ super() }} {% endblock %} 4 | 5 | {%block tag %}collections{% endblock %} 6 | 7 | {% if is_admin %} 8 | {% set options = True %} 9 | {%block options %} 10 | 17 | {% endblock %} 18 | {% endif %} 19 | 20 | {% block content %} 21 | 22 |

Search collections

23 | 24 | {% if total_page and total_page > 1 %} 25 | 26 | 27 | 37 | 38 | 48 | 49 |
28 | {% if page > 1%} 29 | 31 | < Previous 32 | 33 | {% else %} 34 | < Previous 35 | {% endif %} 36 | {{ page }} / {{ total_page }} 39 | {% if page < total_page %} 40 | 42 | Next > 43 | 44 | {% else %} 45 | Next > 46 | {% endif %} 47 |
50 | {% endif %} 51 | 52 | 53 | 54 | 57 | 60 | 63 | 66 | 67 | {% for collection in collections %} 68 | 69 | 74 | 77 | 80 | 83 | 84 | {% endfor %} 85 |
55 | Branch name 56 | 58 | Name 59 | 61 | Release 62 | 64 | Status 65 |
70 | 72 | {{ collection. branchname }} 73 | 75 | {{ collection.name }} 76 | 78 | {{ collection.version }} 79 | 81 | {{ collection.status }} 82 |
86 | 87 | {% endblock %} 88 | -------------------------------------------------------------------------------- /alembic/env.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | from alembic import context 3 | from sqlalchemy import engine_from_config, pool 4 | from logging.config import fileConfig 5 | 6 | # this is the Alembic Config object, which provides 7 | # access to the values within the .ini file in use. 8 | config = context.config 9 | 10 | # Interpret the config file for Python logging. 11 | # This line sets up loggers basically. 12 | fileConfig(config.config_file_name) 13 | 14 | # add your model's MetaData object here 15 | # for 'autogenerate' support 16 | # from myapp import mymodel 17 | # target_metadata = mymodel.Base.metadata 18 | target_metadata = None 19 | 20 | # other values from the config, defined by the needs of env.py, 21 | # can be acquired: 22 | # my_important_option = config.get_main_option("my_important_option") 23 | # ... etc. 24 | 25 | 26 | def run_migrations_offline(): 27 | """Run migrations in 'offline' mode. 28 | 29 | This configures the context with just a URL 30 | and not an Engine, though an Engine is acceptable 31 | here as well. By skipping the Engine creation 32 | we don't even need a DBAPI to be available. 33 | 34 | Calls to context.execute() here emit the given string to the 35 | script output. 36 | 37 | """ 38 | url = config.get_main_option("sqlalchemy.url") 39 | context.configure(url=url) 40 | 41 | with context.begin_transaction(): 42 | context.run_migrations() 43 | 44 | 45 | def run_migrations_online(): 46 | """Run migrations in 'online' mode. 47 | 48 | In this scenario we need to create an Engine 49 | and associate a connection with the context. 50 | 51 | """ 52 | engine = engine_from_config( 53 | config.get_section(config.config_ini_section), 54 | prefix='sqlalchemy.', 55 | poolclass=pool.NullPool) 56 | 57 | connection = engine.connect() 58 | context.configure( 59 | connection=connection, 60 | target_metadata=target_metadata) 61 | 62 | try: 63 | with context.begin_transaction(): 64 | context.run_migrations() 65 | finally: 66 | connection.close() 67 | 68 | 69 | if context.is_offline_mode(): 70 | run_migrations_offline() 71 | else: 72 | run_migrations_online() 73 | -------------------------------------------------------------------------------- /doc/deployment.rst: -------------------------------------------------------------------------------- 1 | Deployment 2 | ========== 3 | 4 | From sources 5 | ------------ 6 | 7 | Clone the source:: 8 | 9 | git clone https://git.fedorahosted.org/git/pkgdb2.git 10 | 11 | Install the dependencies listed in the ``requirements.txt`` file. 12 | 13 | .. note:: The ``requirements.txt`` file require flask>=0.10 but this is only 14 | required for the unit-tests and in fact flask<0.10 is **required** for 15 | python-fedora to work at the moment. 16 | The next release of python-fedora should fix this problem. 17 | 18 | Copy the configuration files:: 19 | 20 | cp pkgdb2.cfg.sample pkgdb2.cfg 21 | 22 | Adjust the configuration files (secret key, database URL, admin group...). 23 | See :doc:`configuration` for detailed information about the configuration. 24 | 25 | 26 | Create the database scheme:: 27 | 28 | PKGDB2_CONFIG=/path/to/pkgdb2.cfg python createdb.py 29 | 30 | Set up the WSGI as described below. 31 | 32 | 33 | From system-wide packages 34 | ------------------------- 35 | 36 | Start by install pkgdb2:: 37 | 38 | yum install pkgdb2 39 | 40 | Adjust the configuration files: ``/etc/pkgdb2/pkgdb2.cfg``. 41 | See :doc:`configuration` for detailed information about the configuration. 42 | 43 | Find the file used to create the database:: 44 | 45 | rpm -ql pkgdb2 |grep createdb.py 46 | 47 | Create the database scheme:: 48 | 49 | PKGDB2_CONFIG=/etc/pkgdb2/pkgdb2.cfg python path/to/createdb.py 50 | 51 | Set up the WSGI as described below. 52 | 53 | 54 | Set-up WSGI 55 | ----------- 56 | 57 | Start by installing ``mod_wsgi``:: 58 | 59 | yum install mod_wsgi 60 | 61 | 62 | Then configure apache:: 63 | 64 | sudo vim /etc/httd/conf.d/pkgdb2.conf 65 | 66 | uncomment the content of the file and adjust as desired. 67 | 68 | 69 | Then edit the file ``/usr/share/pkgdb2/pkgdb2.wsgi`` and 70 | adjust as needed. 71 | 72 | 73 | Then restart apache and you should be able to access the website on 74 | http://localhost/pkgdb 75 | 76 | 77 | .. note:: `Flask `_ provides also some documentation 78 | on how to `deploy Flask application with WSGI and apache 79 | `_. 80 | 81 | 82 | For testing 83 | ----------- 84 | 85 | See :doc:`development` if you want to run pkgdb2 just to test it. 86 | 87 | -------------------------------------------------------------------------------- /doc/contributors.rst: -------------------------------------------------------------------------------- 1 | Contributors to packagedb 2 | ========================= 3 | 4 | Pkgdb2 would be nothing without its contributors. 5 | 6 | On July 26, 2017 (release 2.7.1), the list looks as follow: 7 | 8 | ================= =========== 9 | Number of commits Contributor 10 | ================= =========== 11 | 2818 Pierre-Yves Chibon 12 | 60 Ralph Bean 13 | 18 Till Maas 14 | 9 Devyani Kota 15 | 8 Ryan Lerch 16 | 5 Vít Ondruch 17 | 4 Patrick Uiterwijk 18 | 4 trishnaguha 19 | 3 Chaoyi Zha 20 | 3 Johan Cwiklinski 21 | 3 Michael Cronenworth 22 | 3 Mikolaj Izdebski 23 | 3 Ricky Elrod 24 | 3 Vivek Anand 25 | 2 Ryan Lerch 26 | 2 farhaanbukhsh 27 | 1 Alan Pevec 28 | 1 Christopher Meng 29 | 1 Micah Denn 30 | 1 Michael Haynes 31 | 1 Miro Hrončok 32 | 1 Patrick Uiterwijk 33 | 1 Ralph Bean 34 | 1 Ratnadeep Debnath 35 | 1 Subho-bcrec 36 | 1 Vít Ondruch 37 | 1 mhaynes121 38 | ================= =========== 39 | 40 | This list is generated using 41 | 42 | :: 43 | 44 | git shortlog -s -n -e 45 | 46 | 47 | The link icon (``static/link.png``) was designed by 48 | `Máirín Duffy `_. 49 | 50 | The package review icon (``static/fedora-infra-icon_review.png``) was designed 51 | by `Micah Denn `_ under CC-BY license. 52 | 53 | -------------------------------------------------------------------------------- /tests/test_flask_api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2013 Red Hat, Inc. 4 | # 5 | # This copyrighted material is made available to anyone wishing to use, 6 | # modify, copy, or redistribute it subject to the terms and conditions 7 | # of the GNU General Public License v.2, or (at your option) any later 8 | # version. This program is distributed in the hope that it will be 9 | # useful, but WITHOUT ANY WARRANTY expressed or implied, including the 10 | # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR 11 | # PURPOSE. See the GNU General Public License for more details. You 12 | # should have received a copy of the GNU General Public License along 13 | # with this program; if not, write to the Free Software Foundation, 14 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | # 16 | # Any Red Hat trademarks that are incorporated in the source 17 | # code or documentation are not subject to the GNU General Public 18 | # License and may only be used or replicated with the express permission 19 | # of Red Hat, Inc. 20 | # 21 | 22 | ''' 23 | pkgdb tests for the Flask API regarding collections. 24 | ''' 25 | 26 | __requires__ = ['SQLAlchemy >= 0.8'] 27 | import pkg_resources 28 | 29 | import json 30 | import unittest 31 | import sys 32 | import os 33 | 34 | sys.path.insert(0, os.path.join(os.path.dirname( 35 | os.path.abspath(__file__)), '..')) 36 | 37 | import pkgdb2 38 | from tests import Modeltests 39 | 40 | 41 | class FlaskApiTest(Modeltests): 42 | """ Flask API tests. """ 43 | 44 | def setUp(self): 45 | """ Set up the environnment, ran before every tests. """ 46 | super(FlaskApiTest, self).setUp() 47 | 48 | pkgdb2.APP.config['TESTING'] = True 49 | pkgdb2.SESSION = self.session 50 | pkgdb2.api.SESSION = self.session 51 | self.app = pkgdb2.APP.test_client() 52 | 53 | def test_api_version(self): 54 | """ Test the api_version function. """ 55 | 56 | output = self.app.get('/api/version') 57 | self.assertEqual(output.status_code, 200) 58 | data = json.loads(output.data) 59 | self.assertEqual(data['version'], pkgdb2.__api_version__) 60 | self.assertEqual(data.keys(), ['version']) 61 | 62 | 63 | if __name__ == '__main__': 64 | SUITE = unittest.TestLoader().loadTestsFromTestCase(FlaskApiTest) 65 | unittest.TextTestRunner(verbosity=2).run(SUITE) 66 | -------------------------------------------------------------------------------- /doc/pkgdb_api_design: -------------------------------------------------------------------------------- 1 | PkgDB API 2 | ========= 3 | 4 | The `user` argument here refers to the user that run the script. 5 | 6 | ACL 7 | --- 8 | 9 | * Get ACL 10 | /api/package/acl/get/ 11 | - package name 12 | 13 | * Update ACL 14 | @login_required 15 | /api/package/acl/ 16 | - package name 17 | - flag (request, approve, deny) 18 | - acl (commit, build, watchbugzilla, watchcommits, approveacls, checkout) 19 | - branch (default: devel) 20 | - user target (default: user) 21 | 22 | --> we could use POST and GET request to distinguish between these two and use one URL. 23 | 24 | * Reassign package from one user to another 25 | @login_required 26 | /api/package/acl/reassign/ 27 | - package name (list) 28 | - user 29 | - user target 30 | 31 | 32 | Package 33 | ------- 34 | 35 | * New package 36 | @login_required 37 | POST:/api/package/new/ 38 | - package name 39 | - summary 40 | - description ? 41 | - review_url 42 | - owner 43 | - branch (list of collection name) 44 | - user 45 | 46 | * Orphan package 47 | @login_required 48 | POST:/api/package/orphan/ 49 | - package name 50 | - user 51 | - branch (list) 52 | - all_pkgs (Defaults to False) 53 | 54 | * Unorphan package 55 | @login_required 56 | POST:/api/package/unorphan/ 57 | - package name 58 | - user 59 | - branch (list) 60 | - target user (?) 61 | 62 | * Deprecate package 63 | @login_required 64 | POST:/api/package/deprecate/ 65 | - package name 66 | - user 67 | - branch (list) 68 | 69 | * List packager according to a pattern or a flag 70 | GET:/api/package/list 71 | - pattern 72 | packager name (default to *) 73 | - orphaned (default to False) 74 | - deprecated (default to False) 75 | 76 | 77 | Collection 78 | ---------- 79 | 80 | * New collection 81 | @login_required 82 | POST:/api/collection/new/ 83 | - name 84 | - version 85 | - owner 86 | - user 87 | 88 | * Close collection 89 | @login_required 90 | POST:/api/collection/eol/ 91 | - name 92 | - version 93 | - user 94 | 95 | * List the collections 96 | GET:/api/collection/list/ 97 | 98 | 99 | Packager 100 | -------- 101 | 102 | * List packages of user 103 | GET:/api/packager/package/ 104 | - packager 105 | 106 | * List acl pending for action of user 107 | GET:/api/packager/acl/ 108 | - packager 109 | 110 | * List packagers 111 | GET:/api/packager/list/ 112 | - pattern 113 | packager name (default to *) 114 | 115 | 116 | -------------------------------------------------------------------------------- /pkgdb2/templates/acl_pending.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | {% from "_formhelpers.html" import render_field_in_row %} 3 | 4 | {% block title %} Pending ACLs | PkgDB {% endblock %} 5 | 6 | {%block tag %}pendingAcl{% endblock %} 7 | 8 | {% block content %} 9 | 10 |

Pending ACLs

11 | 12 | {% if pending_acls %} 13 |
14 |
16 | 20 | {{ form.csrf_token }} 21 |
22 |
24 | 28 | {{ form.csrf_token }} 29 |
30 |
31 | {% endif %} 32 | 33 | {% if pending_acls %} 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | {% for pending_acl in pending_acls %} 43 | 44 | 51 | 58 | 59 | 60 | 61 | 69 | 70 | {% endfor %} 71 |
UserPackageCollectionACLStatus
45 | 47 | {{ pending_acl['user'] | avatar(32) | safe }} 48 | {{ pending_acl['user'] }} 49 | 50 | 52 | 55 | {{ pending_acl['package'] }} 56 | 57 | {{ pending_acl['collection'] }}{{ pending_acl['acl'] }}{{ pending_acl['status'] }} 62 | 66 | 67 | 68 |
72 | {% else %} 73 |

74 | No pending ACLs for you 75 |

76 | {% endif %} 77 | 78 | {% endblock %} 79 | -------------------------------------------------------------------------------- /doc/groups.rst: -------------------------------------------------------------------------------- 1 | Group maintainership 2 | ==================== 3 | 4 | PkgDB2 integrates the possibility for FAS group to get ``watchcommits``, 5 | ``watchbugzilla`` and ``commit`` ACLs. 6 | 7 | .. note:: Please note that FAS group cannot get ``approveacls`` permissions. 8 | This is to prevent anyone in the group to approve him/herself and drop 9 | the ACLs for everybody else. 10 | 11 | 12 | There are some requirements for the FAS group: 13 | 14 | * name must end with ``-sig`` 15 | * must be of type ``pkgdb`` 16 | * must require people to be in the ``packager`` group 17 | * must have a mailing list address 18 | * must require sponsoring 19 | 20 | 21 | One requirement for the mailing list address: 22 | 23 | * The mailing list address given to the FAS group must have a corresponding 24 | bugzilla account 25 | 26 | 27 | .. note:: If you wish to share you ACLs with a FAS group, open a new ticket on 28 | the `infrastructure pagure.io tracker `_. 29 | 30 | 31 | 32 | Once the group has been created in FAS, you may give it ``commit``, 33 | ``watchcommits`` and ``watchbugzilla`` ACLs using the ``Manage`` button on 34 | the package's page. 35 | 36 | On the manage page, you will have to click on ``Add someone`` and specify 37 | which ACL you want to give and on which branch. 38 | 39 | .. note:: For groups, the packager name will then have the format 40 | ``group::``. 41 | If you do not respect this format, pkgdb2 will refuse to add the group as 42 | co-maintainer. 43 | 44 | 45 | The suggested package configuration for groups maintenance is: 46 | 47 | * Since group cannot have ``approveacls`` permissions, there have to be 48 | at least one explicit "human" package administrator. 49 | * Group should have ``commits`` bit. This allows every member of the 50 | group to make changes to package in dist-git. This is actually the main 51 | purpose of groups. 52 | * Add ``watchcommits`` bit to allow notification of the group members about 53 | changes in the package dist-git. 54 | * Group should have ``watchbugzilla`` permissions to allow group to be 55 | notified about bugzilla issues associated with the package. 56 | * Optionally, the group can become PoC of the package. This effectively 57 | results only in change of default assignee in bugzilla, nothing else. 58 | * All users who are members of the group might be optionally removed from 59 | the ``commits``, ``watchcommits`` and ``watchbugzilla`` lists, since they 60 | inherit the group rights. 61 | -------------------------------------------------------------------------------- /tests/test_pkgdb.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2013 Red Hat, Inc. 4 | # 5 | # This copyrighted material is made available to anyone wishing to use, 6 | # modify, copy, or redistribute it subject to the terms and conditions 7 | # of the GNU General Public License v.2, or (at your option) any later 8 | # version. This program is distributed in the hope that it will be 9 | # useful, but WITHOUT ANY WARRANTY expressed or implied, including the 10 | # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR 11 | # PURPOSE. See the GNU General Public License for more details. You 12 | # should have received a copy of the GNU General Public License along 13 | # with this program; if not, write to the Free Software Foundation, 14 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | # 16 | # Any Red Hat trademarks that are incorporated in the source 17 | # code or documentation are not subject to the GNU General Public 18 | # License and may only be used or replicated with the express permission 19 | # of Red Hat, Inc. 20 | # 21 | 22 | ''' 23 | pkgdb tests for the Collection object. 24 | ''' 25 | 26 | __requires__ = ['SQLAlchemy >= 0.8'] 27 | import pkg_resources 28 | 29 | import unittest 30 | import sys 31 | import os 32 | 33 | sys.path.insert(0, os.path.join(os.path.dirname( 34 | os.path.abspath(__file__)), '..')) 35 | 36 | import pkgdb2 37 | from tests import Modeltests, FakeFasUser, FakeFasUserAdmin 38 | 39 | 40 | class Pkgdbtests(Modeltests): 41 | """ pkgdb tests. """ 42 | 43 | def test_is_pkgdb_admin(self): 44 | """ Test the is_pkgdb_admin function of pkgdb2. """ 45 | user = FakeFasUser() 46 | out = pkgdb2.is_pkgdb_admin(user) 47 | self.assertEqual(out, False) 48 | 49 | user.groups = [] 50 | out = pkgdb2.is_pkgdb_admin(user) 51 | self.assertEqual(out, False) 52 | 53 | user = FakeFasUser() 54 | out = pkgdb2.is_pkgdb_admin(None) 55 | self.assertEqual(out, False) 56 | 57 | user = FakeFasUserAdmin() 58 | out = pkgdb2.is_pkgdb_admin(user) 59 | self.assertEqual(out, True) 60 | 61 | pkgdb2.APP.config['ADMIN_GROUP'] = 'sysadmin-main' 62 | 63 | out = pkgdb2.is_pkgdb_admin(user) 64 | self.assertEqual(out, False) 65 | 66 | # Reset the ADMIN_GROUP for the other tests 67 | pkgdb2.APP.config['ADMIN_GROUP'] = ('sysadmin-main', 'sysadmin-cvs') 68 | 69 | 70 | if __name__ == '__main__': 71 | SUITE = unittest.TestLoader().loadTestsFromTestCase(Pkgdbtests) 72 | unittest.TextTestRunner(verbosity=2).run(SUITE) 73 | -------------------------------------------------------------------------------- /pkgdb2/proxy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2014 Red Hat, Inc. 4 | # 5 | # This copyrighted material is made available to anyone wishing to use, 6 | # modify, copy, or redistribute it subject to the terms and conditions 7 | # of the GNU General Public License v.2, or (at your option) any later 8 | # version. This program is distributed in the hope that it will be 9 | # useful, but WITHOUT ANY WARRANTY expressed or implied, including the 10 | # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR 11 | # PURPOSE. See the GNU General Public License for more details. You 12 | # should have received a copy of the GNU General Public License along 13 | # with this program; if not, write to the Free Software Foundation, 14 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | # 16 | # Any Red Hat trademarks that are incorporated in the source 17 | # code or documentation are not subject to the GNU General Public 18 | # License and may only be used or replicated with the express permission 19 | # of Red Hat, Inc. 20 | # 21 | 22 | ''' 23 | Makes pkgdb an application behind a reverse proxy and thus ensure the 24 | redirects are using ``https``. 25 | 26 | Source: http://flask.pocoo.org/snippets/35/ by Peter Hansen 27 | ''' 28 | 29 | 30 | class ReverseProxied(object): # pragma: no cover 31 | '''Wrap the application in this middleware and configure the 32 | front-end server to add these headers, to let you quietly bind 33 | this to a URL other than / and to an HTTP scheme that is 34 | different than what is used locally. 35 | 36 | In nginx: 37 | location /myprefix { 38 | proxy_pass http://192.168.0.1:5001; 39 | proxy_set_header Host $host; 40 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 41 | proxy_set_header X-Scheme $scheme; 42 | proxy_set_header X-Script-Name /myprefix; 43 | } 44 | 45 | :param app: the WSGI application 46 | ''' 47 | def __init__(self, app): 48 | self.app = app 49 | 50 | def __call__(self, environ, start_response): 51 | script_name = environ.get('HTTP_X_SCRIPT_NAME', '') 52 | if script_name: 53 | environ['SCRIPT_NAME'] = script_name 54 | path_info = environ['PATH_INFO'] 55 | if path_info.startswith(script_name): 56 | environ['PATH_INFO'] = path_info[len(script_name):] 57 | 58 | server = environ.get('HTTP_X_FORWARDED_HOST', '') 59 | if server: 60 | environ['HTTP_HOST'] = server 61 | 62 | scheme = environ.get('HTTP_X_SCHEME', '') 63 | if scheme: 64 | environ['wsgi.url_scheme'] = scheme 65 | 66 | return self.app(environ, start_response) 67 | -------------------------------------------------------------------------------- /pkgdb2/templates/list_logs.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | 3 | {% block title %} {{ super() }} {% endblock %} 4 | 5 | {%block header %} 6 | {% if refresh %} 7 | 8 | {% endif %} 9 | {% endblock %} 10 | 11 | {%block tag %}admin{% endblock %} 12 | 13 | 14 | {% set options = True %} 15 | {%block options %} 16 |
17 | Restrict to package:
18 | Restrict to packager:
19 | From date:
20 | Auto-refresh:
21 | 22 |
23 | {% endblock %} 24 | 25 | {% block content %} 26 | 27 |

Logs

28 | 29 | {% if refresh %} 30 | This page should refresh automatically every 5 seconds 31 | {% endif %} 32 | 33 | {% if total_page and total_page > 1 and total_page >= page %} 34 | 35 | 36 | 47 | 48 | 59 | 60 |
37 | {% if page > 1%} 38 | 41 | < Previous 42 | 43 | {% else %} 44 | < Previous 45 | {% endif %} 46 | {{ page }} / {{ total_page }} 49 | {% if page < total_page %} 50 | 53 | Next > 54 | 55 | {% else %} 56 | Next > 57 | {% endif %} 58 |
61 | {% endif %} 62 | 63 | {% if total_page >= page and page > 0 %} 64 | 65 | {% for log in logs %} 66 | 67 | 70 | 73 | 74 | {% endfor %} 75 |
68 | {{ log.change_time.strftime('%Y-%m-%d %H:%M:%S') }} 69 | 71 | {{ log.description }} 72 |
76 | {% elif total_page %} 77 |

78 | Sorry, but the page you are requesting is unavailable.
79 | 80 | Back to the list 81 | 82 |

83 | {% else %} 84 |

No logs found in the database.

85 | {% endif %} 86 | 87 | 88 | {% endblock %} 89 | 90 | {% block jscripts %} 91 | {{ super() }} 92 | 96 | 103 | {% endblock %} 104 | -------------------------------------------------------------------------------- /pkgdb2/doc_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2013 Red Hat, Inc. 4 | # 5 | # This copyrighted material is made available to anyone wishing to use, 6 | # modify, copy, or redistribute it subject to the terms and conditions 7 | # of the GNU General Public License v.2, or (at your option) any later 8 | # version. This program is distributed in the hope that it will be 9 | # useful, but WITHOUT ANY WARRANTY expressed or implied, including the 10 | # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR 11 | # PURPOSE. See the GNU General Public License for more details. You 12 | # should have received a copy of the GNU General Public License along 13 | # with this program; if not, write to the Free Software Foundation, 14 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | # 16 | # Any Red Hat trademarks that are incorporated in the source 17 | # code or documentation are not subject to the GNU General Public 18 | # License and may only be used or replicated with the express permission 19 | # of Red Hat, Inc. 20 | # 21 | 22 | ''' 23 | Provide utility function to convert rst in docstring of functions into html 24 | ''' 25 | 26 | import docutils 27 | import docutils.examples 28 | import textwrap 29 | 30 | import markupsafe 31 | 32 | 33 | def modify_rst(rst): 34 | """ Downgrade some of our rst directives if docutils is too old. """ 35 | 36 | ## We catch Exception if we want :-p 37 | # pylint: disable=W0703 38 | try: 39 | # The rst features we need were introduced in this version 40 | minimum = [0, 9] 41 | version = [int(cpt) for cpt in docutils.__version__.split('.')] 42 | 43 | # If we're at or later than that version, no need to downgrade 44 | if version >= minimum: 45 | return rst 46 | except Exception: # pragma: no cover 47 | # If there was some error parsing or comparing versions, run the 48 | # substitutions just to be safe. 49 | pass 50 | 51 | # On Fedora this will never work as the docutils version is to recent 52 | # Otherwise, make code-blocks into just literal blocks. 53 | substitutions = { # pragma: no cover 54 | '.. code-block:: javascript': '::', 55 | } 56 | for old, new in substitutions.items(): # pragma: no cover 57 | rst = rst.replace(old, new) 58 | 59 | return rst # pragma: no cover 60 | 61 | 62 | def modify_html(html): 63 | """ Perform style substitutions where docutils doesn't do what we want. 64 | """ 65 | 66 | substitutions = { 67 | '': '', 68 | '': '', 69 | } 70 | for old, new in substitutions.items(): 71 | html = html.replace(old, new) 72 | 73 | return html 74 | 75 | 76 | def load_doc(endpoint): 77 | """ Utility to load an RST file and turn it into fancy HTML. """ 78 | 79 | rst = unicode(textwrap.dedent(endpoint.__doc__)) 80 | 81 | rst = modify_rst(rst) 82 | 83 | api_docs = docutils.examples.html_body(rst) 84 | 85 | api_docs = modify_html(api_docs) 86 | 87 | api_docs = markupsafe.Markup(api_docs) 88 | return api_docs 89 | -------------------------------------------------------------------------------- /pkgdb2/templates/packager.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | 3 | {% block title %} {{ super() }} {% endblock %} 4 | 5 | {%block tag %}packagers{% endblock %} 6 | 7 | {% block content %} 8 | 9 | 10 |

11 | {{ packager | avatar | safe }} 12 | {{ packager }}

(fas - 19 | 20 | requests) 21 | {% if config['FEDOCAL_URL'] and config['FEDOCAL_CALENDAR_SHIELD'] %} 22 | 24 | {% endif %} 25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
Point of contact:{{ packages | length }}
Co-maintainer:{{ packages_co | length}}
Watched:{{ packages_watch | length}}
41 | 42 | {% if packages %} 43 |

Point of contact:

44 |
    45 | {% for info in packages %} 46 |
  • 47 | 49 | {{ info[0].namespace }}/{{ info[0].name }} 50 | -- {{ info[0].summary }} 51 | ( 52 | {% for clt in info[1] | sort(attribute='branchname', reverse=True) %} 53 | {{ clt.branchname }} 54 | {% endfor %} 55 | ) 56 |
  • 57 | {% endfor %} 58 |
59 | {% endif %} 60 | 61 | {% if packages_co %} 62 |

Co-maintainer:

63 |
    64 | {% for info in packages_co %} 65 |
  • 66 | 68 | {{ info[0].namespace }}/{{ info[0].name }} 69 | -- {{ info[0].summary }} 70 | ( 71 | {% for clt in info[1] | sort(attribute='branchname', reverse=True) %} 72 | {{ clt.branchname }} 73 | {% endfor %} 74 | ) 75 |
  • 76 | {% endfor %} 77 |
78 | {% endif %} 79 | 80 | {% if packages_watch %} 81 |

Package{% if packages_watch | length > 1 %}s{%endif%} watched:

82 |
    83 | {% for info in packages_watch %} 84 |
  • 85 | 87 | {{ info[0].namespace }}/{{ info[0].name }} 88 | -- {{ info[0].summary }} 89 | ( 90 | {% for clt in info[1] | sort(attribute='branchname', reverse=True) %} 91 | {{ clt.branchname }} 92 | {% endfor %} 93 | ) 94 |
  • 95 | {% endfor %} 96 |
97 | {% endif %} 98 | 99 | {% endblock %} 100 | -------------------------------------------------------------------------------- /pkgdb2/templates/actions_update.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | {% from "_formhelpers.html" import render_field_in_row %} 3 | 4 | {% block title %} Update action | PkgDB {% endblock %} 5 | 6 | {%block tag %}{% if tag %}{{ tag }}{% else %}admin{% endif %}{% endblock %} 7 | 8 | {% block content %} 9 | 10 | {% if package %} 11 |

Update request: {{ admin_action.id }}

12 | {% else %} 13 |

Update admin action: {{ admin_action.id }}

14 | {% endif %} 15 | 16 | {% if package %} 17 |

18 | As current admin of the package {{ package.name }} you have the possibility 19 | for 7 days to block a branch request for EPEL branches. 20 |

21 | {% endif %} 22 | {% if tag %} 23 | {% if admin_action.action != 'request.package' %} 24 |

25 | During the 7 days following the request, you can 'Block' the new branch 26 | process by setting the request to Blocked 27 | or 'Approve' it by setting it to Awaiting Review 28 | to inform admins that they can now review this request. 29 |

30 | {% endif %} 31 |
34 | {% else %} 35 | 37 | {% endif %} 38 | {% if is_admin %} 39 |

40 | Updating the status of a request does not process the request. The proper 41 | way to process requests is via the pkgdb-admin CLI tool. 42 |

43 | {% endif %} 44 | 45 | 46 | 47 | 48 | 49 | 60 | 61 | 62 | 63 | {% if admin_action.action == 'request.package' 64 | or admin_action.action == 'request.unretire' %} 65 | 66 | 67 | 72 | 73 | 74 | 75 | 78 | 79 | 80 | 81 | 84 | 85 | {% endif %} 86 | {{ render_field_in_row(form.status) }} 87 | {{ render_field_in_row( 88 | form.message, 89 | placeholder='Required if the action is blocked or denied') }} 90 |
User:{{ admin_action.user }}
Package: 50 | {% if admin_action.package %} 51 | 54 | {{ admin_action.package.name }} 55 | 56 | {% else %} 57 | {{ admin_action.info_data['pkg_name'] }} 58 | {% endif %} 59 |
Action:{{ admin_action.action }}
To branch:{{ admin_action.collection.branchname }}
Ticket 68 | 69 | {{ admin_action.info_data['pkg_review_url'] }} 70 | 71 |
Monitoring status 76 | {{ admin_action.info_data.get('monitoring_status', 'True') }} 77 |
Koschei integration 82 | {{ admin_action.info_data.get('koschei', 'False') }} 83 |
91 |

92 | 93 | 94 | {{ form.csrf_token }} 95 |

96 |
97 | {% endblock %} 98 | -------------------------------------------------------------------------------- /doc/new_branch_request_graph: -------------------------------------------------------------------------------- 1 | 2 | 3 | +--------------------+ +------------------------+ +----------------+ 4 | | New branch +---------------------->| New Fedora branch +----------->| APPROVED |<--------+ 5 | | request | | request | +----------------+ | 6 | +---------+----------+ +------------------------+ | 7 | | | 8 | | | 9 | | +----+---------+ +--------------+ | 10 | | | Review by +------->| OBSOLETE | | 11 | | | submitter | +--------------+ | 12 | | +--------------+ | 13 | | ^ | 14 | v | | 15 | +---------------------+ +---+-------+ +---------------------+ +-----------+ | 16 | | New EPEL branch +----->| PENDING +----->| Review by package +------------>| BLOCKED | | 17 | | request | +-----+-----+ | admin | +-----------+ | 18 | +---------------------+ | +---------+-----------+ | 19 | | | | 20 | | | | 21 | | | | 22 | After a week | v | 23 | | +---------------------+ +--------------------+ | 24 | +----------->| AWAITING REVIEW +------------->| Review by admins +----+ 25 | +---------------------+ | | 26 | +---+----------------+ 27 | | 28 | | 29 | v 30 | +----------+ 31 | | DENIED | 32 | +----------+ 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /pkgdb2/lib/notifications.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2013 Red Hat, Inc. 4 | # 5 | # This copyrighted material is made available to anyone wishing to use, 6 | # modify, copy, or redistribute it subject to the terms and conditions 7 | # of the GNU General Public License v.2, or (at your option) any later 8 | # version. This program is distributed in the hope that it will be 9 | # useful, but WITHOUT ANY WARRANTY expressed or implied, including the 10 | # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR 11 | # PURPOSE. See the GNU General Public License for more details. You 12 | # should have received a copy of the GNU General Public License along 13 | # with this program; if not, write to the Free Software Foundation, 14 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | # 16 | # Any Red Hat trademarks that are incorporated in the source 17 | # code or documentation are not subject to the GNU General Public 18 | # License and may only be used or replicated with the express permission 19 | # of Red Hat, Inc. 20 | # 21 | 22 | """ This is a temporary shim that allows fedmsg to be an optional dependency of 23 | pkgdb. If fedmsg is installed, these function calls will try to actually send 24 | messages. If it is not installed, it will return silently. 25 | 26 | :Author: Ralph Bean 27 | 28 | """ 29 | 30 | import smtplib 31 | import warnings 32 | 33 | from email.mime.text import MIMEText 34 | 35 | import pkgdb2 36 | 37 | 38 | def fedmsg_publish(*args, **kwargs): # pragma: no cover 39 | ''' Try to publish a message on the fedmsg bus. ''' 40 | ## We catch Exception if we want :-p 41 | # pylint: disable=W0703 42 | ## Ignore message about fedmsg import 43 | # pylint: disable=F0401 44 | kwargs['modname'] = 'pkgdb' 45 | try: 46 | import fedmsg 47 | fedmsg.publish(*args, **kwargs) 48 | except Exception as err: 49 | warnings.warn(str(err)) 50 | 51 | 52 | def email_publish( 53 | user, package, message, subject=None, 54 | to_email=None): # pragma: no cover 55 | ''' Send notification by email. ''' 56 | 57 | if not package and not to_email: 58 | # If we have no package and no to_email, we have no way to know 59 | # where to send the email 60 | return 61 | 62 | msg = MIMEText(message) 63 | 64 | if subject: 65 | msg['Subject'] = '[PkgDB] %s' % subject 66 | elif package: 67 | msg['Subject'] = '[PkgDB] {0} updated {1}'.format( 68 | user, package.name) 69 | else: 70 | msg['Subject'] = '[PkgDB] updated by {0}'.format(user) 71 | 72 | from_email = pkgdb2.APP.config.get( 73 | 'PKGDB2_EMAIL_FROM', 'nobody@fedoraproject.org') 74 | 75 | if not to_email: 76 | email_to_template = pkgdb2.APP.config.get( 77 | 'PKGDB2_EMAIL_TO', '{pkg_name}-owner@fedoraproject.org') 78 | to_email = email_to_template.format(pkg_name=package.name) 79 | 80 | msg['From'] = from_email 81 | msg['To'] = to_email 82 | cc_email = pkgdb2.APP.config.get('PKGDB2_EMAIL_CC', None) 83 | if cc_email: 84 | msg['Cc'] = cc_email 85 | 86 | if isinstance(cc_email, basestring): 87 | cc_email = [cc_email] 88 | if isinstance(to_email, basestring): 89 | to_email = [to_email] 90 | if cc_email: 91 | to_email.extend(cc_email) 92 | 93 | # Send the message via our own SMTP server, but don't include the 94 | # envelope header. 95 | smtp = smtplib.SMTP(pkgdb2.APP.config.get( 96 | 'PKGDB2_EMAIL_SMTP_SERVER', 'localhost')) 97 | smtp.sendmail(from_email, to_email, msg.as_string()) 98 | smtp.quit() 99 | -------------------------------------------------------------------------------- /pkgdb2/templates/package_request.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | {% from "_formhelpers.html" import render_field_in_row, render_field_in_cell %} 3 | 4 | {% block title %} Request package | PkgDB {% endblock %} 5 | 6 | {%block tag %}packages{% endblock %} 7 | 8 | {% block content %} 9 | 10 |

Request a new package

11 | 12 | 13 |
14 | 15 | 16 | {{ render_field_in_cell(form.review_url, 17 | title="Ticket number of the package review", placeholder="1234") }} 18 | 21 | 22 | {{ render_field_in_row(form.pkgname) }} 23 | {{ render_field_in_row(form.summary) }} 24 | {{ render_field_in_row(form.branches) }} 25 | {{ render_field_in_row(form.description, rows=5) }} 26 | {{ render_field_in_row(form.upstream_url, title="URL of the upstream project (if any)") }} 27 | {{ render_field_in_row(form.monitoring_status, title="New release monitoring") }} 28 | {{ render_field_in_row(form.koschei, title="Koschei integration/monitoring") }} 29 | {{ render_field_in_row(form.comaintainers, title="List of potential co-maintainers on the package") }} 30 | {{ render_field_in_row(form.namespace, title="Namespace of the package") }} 31 |
19 | 20 |
32 |

33 | 34 | 35 | {{ form.csrf_token }} 36 |

37 | 38 |
39 | {% endblock %} 40 | 41 | {% block jscripts %} 42 | {{ super() }} 43 | 44 | 95 | {% endblock %} 96 | -------------------------------------------------------------------------------- /pkgdb2/static/toggle.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2013 Thibaut Courouble 3 | * http://www.cssflow.com 4 | * 5 | * Licensed under the MIT License: 6 | * http://www.opensource.org/licenses/mit-license.php 7 | */ 8 | 9 | 10 | .container { 11 | margin: 50px auto; 12 | width: 280px; 13 | } 14 | 15 | .container > .switch { 16 | margin-left: 1em; 17 | } 18 | 19 | .switch { 20 | position: relative; 21 | display: inline-block; 22 | vertical-align: top; 23 | width: 75px; 24 | height: 20px; 25 | padding: 3px; 26 | background-color: white; 27 | border-radius: 18px; 28 | box-shadow: inset 0 -1px white, inset 0 1px 1px rgba(0, 0, 0, 0.05); 29 | cursor: pointer; 30 | background-image: -webkit-linear-gradient(top, #eeeeee, white 25px); 31 | background-image: -moz-linear-gradient(top, #eeeeee, white 25px); 32 | background-image: -o-linear-gradient(top, #eeeeee, white 25px); 33 | background-image: linear-gradient(to bottom, #eeeeee, white 25px); 34 | } 35 | 36 | .switch-input { 37 | position: absolute; 38 | top: 0; 39 | left: 0; 40 | opacity: 0; 41 | } 42 | 43 | .switch-label { 44 | position: relative; 45 | display: block; 46 | height: inherit; 47 | font-size: 10px; 48 | text-transform: uppercase; 49 | background: #eceeef; 50 | border-radius: inherit; 51 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.12), inset 0 0 2px rgba(0, 0, 0, 0.15); 52 | -webkit-transition: 0.15s ease-out; 53 | -moz-transition: 0.15s ease-out; 54 | -o-transition: 0.15s ease-out; 55 | transition: 0.15s ease-out; 56 | -webkit-transition-property: opacity background; 57 | -moz-transition-property: opacity background; 58 | -o-transition-property: opacity background; 59 | transition-property: opacity background; 60 | } 61 | .switch-label:before, .switch-label:after { 62 | position: absolute; 63 | top: 50%; 64 | margin-top: -.5em; 65 | line-height: 1; 66 | -webkit-transition: inherit; 67 | -moz-transition: inherit; 68 | -o-transition: inherit; 69 | transition: inherit; 70 | } 71 | .switch-label:before { 72 | content: attr(data-off); 73 | right: 11px; 74 | color: #aaa; 75 | text-shadow: 0 1px rgba(255, 255, 255, 0.5); 76 | } 77 | .switch-label:after { 78 | content: attr(data-on); 79 | left: 11px; 80 | color: white; 81 | text-shadow: 0 1px rgba(0, 0, 0, 0.2); 82 | opacity: 0; 83 | } 84 | .switch-input:checked ~ .switch-label { 85 | background: #3b6eb4; 86 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15), inset 0 0 3px rgba(0, 0, 0, 0.2); 87 | } 88 | .switch-input:checked ~ .switch-label:before { 89 | opacity: 0; 90 | } 91 | .switch-input:checked ~ .switch-label:after { 92 | opacity: 1; 93 | } 94 | 95 | .switch-handle { 96 | position: absolute; 97 | top: 4px; 98 | left: 4px; 99 | width: 18px; 100 | height: 18px; 101 | background: white; 102 | border-radius: 10px; 103 | box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2); 104 | background-image: -webkit-linear-gradient(top, white 40%, #f0f0f0); 105 | background-image: -moz-linear-gradient(top, white 40%, #f0f0f0); 106 | background-image: -o-linear-gradient(top, white 40%, #f0f0f0); 107 | background-image: linear-gradient(to bottom, white 40%, #f0f0f0); 108 | -webkit-transition: left 0.15s ease-out; 109 | -moz-transition: left 0.15s ease-out; 110 | -o-transition: left 0.15s ease-out; 111 | transition: left 0.15s ease-out; 112 | } 113 | .switch-handle:before { 114 | content: ''; 115 | position: absolute; 116 | top: 50%; 117 | left: 50%; 118 | margin: -6px 0 0 -6px; 119 | width: 12px; 120 | height: 12px; 121 | background: #f9f9f9; 122 | border-radius: 6px; 123 | box-shadow: inset 0 1px rgba(0, 0, 0, 0.02); 124 | background-image: -webkit-linear-gradient(top, #eeeeee, white); 125 | background-image: -moz-linear-gradient(top, #eeeeee, white); 126 | background-image: -o-linear-gradient(top, #eeeeee, white); 127 | background-image: linear-gradient(to bottom, #eeeeee, white); 128 | } 129 | .switch-input:checked ~ .switch-handle { 130 | left: 59px; 131 | box-shadow: -1px 1px 5px rgba(0, 0, 0, 0.2); 132 | } 133 | 134 | .switch-green > .switch-input:checked ~ .switch-label { 135 | background: #4fb845; 136 | } 137 | -------------------------------------------------------------------------------- /pkgdb2/templates/package_timeline.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | 3 | {% block title %} Timeline {{ package }} - {{ super() }} {% endblock %} 4 | 5 | {%block header %} 6 | {% if refresh %} 7 | 8 | {% endif %} 9 | {% endblock %} 10 | 11 | {%block tag %}packages{% endblock %} 12 | 13 | 14 | {% set options = True %} 15 | {%block options %} 16 |
19 | Restrict to packager:
20 | From date:
21 | 22 |
23 | {% endblock %} 24 | 25 | {% block content %} 26 | 27 |

28 | 30 | {{ package }} 31 | > Timeline 32 |

33 | 34 | {% if refresh %} 35 | This page should refresh automatically every 5 seconds 36 | {% endif %} 37 | 38 | {% if total_page and total_page > 1 and total_page >= page %} 39 | 40 | 41 | 52 | 53 | 64 | 65 |
42 | {% if page > 1%} 43 | 46 | < Previous 47 | 48 | {% else %} 49 | < Previous 50 | {% endif %} 51 | {{ page }} / {{ total_page }} 54 | {% if page < total_page %} 55 | 58 | Next > 59 | 60 | {% else %} 61 | Next > 62 | {% endif %} 63 |
66 | {% endif %} 67 | 68 | {% if total_page >= page and page > 0 %} 69 | 70 | {% for log in logs %} 71 | 72 | 75 | 78 | 79 | {% endfor %} 80 |
73 | {{ log.change_time.strftime('%Y-%m-%d %H:%M:%S') }} 74 | 76 | {{ log.description }} 77 |
81 | {% elif total_page %} 82 |

83 | Sorry, but the page you are requesting is unavailable.
84 | 86 | Back to the list 87 | 88 |

89 | {% else %} 90 |

No logs found in the database.

91 | {% endif %} 92 | 93 | 94 | {% endblock %} 95 | 96 | {% block jscripts %} 97 | {{ super() }} 98 | 102 | 135 | {% endblock %} 136 | -------------------------------------------------------------------------------- /tests/test_collection.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2013 Red Hat, Inc. 4 | # 5 | # This copyrighted material is made available to anyone wishing to use, 6 | # modify, copy, or redistribute it subject to the terms and conditions 7 | # of the GNU General Public License v.2, or (at your option) any later 8 | # version. This program is distributed in the hope that it will be 9 | # useful, but WITHOUT ANY WARRANTY expressed or implied, including the 10 | # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR 11 | # PURPOSE. See the GNU General Public License for more details. You 12 | # should have received a copy of the GNU General Public License along 13 | # with this program; if not, write to the Free Software Foundation, 14 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | # 16 | # Any Red Hat trademarks that are incorporated in the source 17 | # code or documentation are not subject to the GNU General Public 18 | # License and may only be used or replicated with the express permission 19 | # of Red Hat, Inc. 20 | # 21 | 22 | ''' 23 | pkgdb tests for the Collection object. 24 | ''' 25 | 26 | __requires__ = ['SQLAlchemy >= 0.8'] 27 | import pkg_resources 28 | 29 | import unittest 30 | import sys 31 | import os 32 | 33 | sys.path.insert(0, os.path.join(os.path.dirname( 34 | os.path.abspath(__file__)), '..')) 35 | 36 | from pkgdb2.lib import model 37 | from tests import Modeltests, create_collection 38 | 39 | 40 | class Collectiontests(Modeltests): 41 | """ Collection tests. """ 42 | 43 | def test_init_collection(self): 44 | """ Test the __init__ function of Collection. """ 45 | create_collection(self.session) 46 | self.assertEqual(5, len(model.Collection.all(self.session))) 47 | 48 | def test_repr_collection(self): 49 | """ Test the __repr__ function of Collection. """ 50 | create_collection(self.session) 51 | collections = sorted( 52 | model.Collection.all(self.session), key=lambda x: x.branchname) 53 | self.assertEqual(collections[0].branchname, 'el4') 54 | self.assertEqual(collections[1].branchname, 'el6') 55 | self.assertEqual("Collection(u'Fedora', u'17', u'Active', " 56 | "owner:u'toshio')", 57 | collections[2].__repr__()) 58 | self.assertEqual(collections[3].branchname, 'f18') 59 | 60 | def test_search(self): 61 | """ Test the search function of Collection. """ 62 | create_collection(self.session) 63 | 64 | collections = model.Collection.search(self.session, 'EPEL%') 65 | self.assertEqual(len(collections), 0) 66 | 67 | collections = model.Collection.search(self.session, 'f%', 'Active') 68 | self.assertEqual("Collection(u'Fedora', u'17', u'Active', " 69 | "owner:u'toshio')", 70 | collections[0].__repr__()) 71 | 72 | collections = model.Collection.search(self.session, 'f%') 73 | self.assertEqual(2, len(collections)) 74 | 75 | cnt = model.Collection.search(self.session, 'f%', count=True) 76 | self.assertEqual(2, cnt) 77 | 78 | collections = model.Collection.search( 79 | session=self.session, 80 | clt_name='f%', 81 | offset=1) 82 | self.assertEqual(1, len(collections)) 83 | 84 | collections = model.Collection.search( 85 | session=self.session, 86 | clt_name='f%', 87 | limit=1) 88 | self.assertEqual(1, len(collections)) 89 | 90 | def test_to_json(self): 91 | """ Test the to_json function of Collection. """ 92 | create_collection(self.session) 93 | collection = model.Collection.by_name(self.session, 'f18') 94 | collection = collection.to_json() 95 | self.assertEqual( 96 | sorted(collection.keys()), 97 | sorted([ 98 | 'allow_retire', 'branchname', 'date_created', 'date_updated', 99 | 'dist_tag', 'koji_name', 'name', 'status', 'version' ] 100 | ) 101 | ) 102 | 103 | 104 | if __name__ == '__main__': 105 | SUITE = unittest.TestLoader().loadTestsFromTestCase(Collectiontests) 106 | unittest.TextTestRunner(verbosity=2).run(SUITE) 107 | -------------------------------------------------------------------------------- /tests/test_orphan_group.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2015 Red Hat, Inc. 4 | # 5 | # This copyrighted material is made available to anyone wishing to use, 6 | # modify, copy, or redistribute it subject to the terms and conditions 7 | # of the GNU General Public License v.2, or (at your option) any later 8 | # version. This program is distributed in the hope that it will be 9 | # useful, but WITHOUT ANY WARRANTY expressed or implied, including the 10 | # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR 11 | # PURPOSE. See the GNU General Public License for more details. You 12 | # should have received a copy of the GNU General Public License along 13 | # with this program; if not, write to the Free Software Foundation, 14 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | # 16 | # Any Red Hat trademarks that are incorporated in the source 17 | # code or documentation are not subject to the GNU General Public 18 | # License and may only be used or replicated with the express permission 19 | # of Red Hat, Inc. 20 | # 21 | 22 | ''' 23 | pkgdb tests for orphaning/retiring a package whose PoC is a group. 24 | ''' 25 | 26 | __requires__ = ['SQLAlchemy >= 0.8'] 27 | import pkg_resources 28 | 29 | import unittest 30 | import sys 31 | import os 32 | 33 | from mock import patch 34 | 35 | sys.path.insert(0, os.path.join(os.path.dirname( 36 | os.path.abspath(__file__)), '..')) 37 | 38 | import pkgdb2 39 | import pkgdb2.lib as pkgdblib 40 | import pkgdb2.lib.model as model 41 | from tests import Modeltests, FakeFasUser, FakeFasUserAdmin, \ 42 | create_collection, create_package 43 | 44 | 45 | class PkgdbOrphanGrouptests(Modeltests): 46 | """ pkgdb orphan group tests. """ 47 | 48 | @patch('pkgdb2.lib.utils.set_bugzilla_owner') 49 | def test_orphan_group_package(self, bz_owner): 50 | """ Test the is_pkgdb_admin function of pkgdb2. """ 51 | bz_owner.return_value = None 52 | 53 | create_collection(self.session) 54 | create_package(self.session) 55 | 56 | guake_pkg = model.Package.by_name(self.session, 'rpms', 'guake') 57 | fedocal_pkg = model.Package.by_name(self.session, 'rpms', 'fedocal') 58 | 59 | f18_collec = model.Collection.by_name(self.session, 'f18') 60 | devel_collec = model.Collection.by_name(self.session, 'master') 61 | 62 | # Pkg: guake - Collection: master - Approved 63 | pkgltg = model.PackageListing( 64 | point_of_contact='group::infra-sig', 65 | status='Approved', 66 | package_id=guake_pkg.id, 67 | collection_id=devel_collec.id, 68 | ) 69 | self.session.add(pkgltg) 70 | 71 | # Pkg: guake - Collection: f18 - Approved 72 | pkgltg = model.PackageListing( 73 | point_of_contact='pingou', 74 | status='Approved', 75 | package_id=guake_pkg.id, 76 | collection_id=f18_collec.id, 77 | ) 78 | self.session.add(pkgltg) 79 | 80 | # Pkg: fedocal - Collection: master - Approved 81 | pkgltg = model.PackageListing( 82 | point_of_contact='group::infra-sig', 83 | status='Approved', 84 | package_id=fedocal_pkg.id, 85 | collection_id=devel_collec.id, 86 | ) 87 | self.session.add(pkgltg) 88 | 89 | user = FakeFasUser() 90 | 91 | # Orphan allowed (?) 92 | msg = pkgdblib.update_pkg_status( 93 | self.session, namespace='rpms', pkg_name='fedocal', 94 | pkg_branch='master', status='Orphaned', user=user, poc='orphan') 95 | 96 | self.assertEqual( 97 | msg, 98 | 'user: pingou updated package: fedocal status from: Approved to ' 99 | 'Orphaned on branch: master') 100 | 101 | # Retired blocked 102 | msg = pkgdblib.update_pkg_status( 103 | self.session, namespace='rpms', pkg_name='guake', 104 | pkg_branch='master', status='Retired', user=user, poc='orphan') 105 | 106 | self.assertEqual( 107 | msg, 108 | 'user: pingou updated package: guake status from: Approved to ' 109 | 'Retired on branch: master') 110 | 111 | 112 | if __name__ == '__main__': 113 | SUITE = unittest.TestLoader().loadTestsFromTestCase(PkgdbOrphanGrouptests) 114 | unittest.TextTestRunner(verbosity=2).run(SUITE) 115 | -------------------------------------------------------------------------------- /utility/pkgdb2.cfg.sample: -------------------------------------------------------------------------------- 1 | # Beware that the quotes around the values are mandatory 2 | 3 | ### Secret key for the Flask application 4 | SECRET_KEY='' 5 | 6 | ### Full URL to the website hosting the pkgdb2 instance. This is required 7 | ### to make the opensearch working properly. 8 | SITE_URL = 'http://127.0.0.1:5000' 9 | 10 | ### Project name 11 | PROJECT_NAME = 'Fedora' 12 | 13 | ### url to the database server: 14 | #DB_URL='mysql://user:pass@host/db_name' 15 | #DB_URL='postgres://user:pass@host/db_name' 16 | DB_URL='sqlite:////var/tmp/pkgdb2_dev.sqlite' 17 | 18 | ### the number of items (packages, packagers..) to display on the search 19 | ### pages 20 | ITEMS_PER_PAGE = 50 21 | 22 | ### Blacklisted items, format: namespace/name 23 | REQUEST_BLACKLIST = ['docker/fedora'] 24 | 25 | 26 | ### List the ACL which are automatically approved (don't need reviewing) 27 | AUTO_APPROVE = ['watchcommits', 'watchbugzilla'] 28 | 29 | ### FAS group for the pkgdb admins 30 | ADMIN_GROUP = ['sysadmin-main', 'sysadmin-cvs'] 31 | 32 | ### The default backend for dogpile 33 | ### Options are listed at: 34 | ### https://dogpilecache.readthedocs.org/en/latest/api.html (backend section) 35 | PKGDB2_CACHE_BACKEND = 'dogpile.cache.memcached' 36 | PKGDB2_CACHE_KWARGS = { 37 | 'arguments': { 38 | 'url': "127.0.0.1:11211", 39 | } 40 | } 41 | 42 | ### Information regarding where the application is deployed 43 | SITE_ROOT = 'https://admin.fedoraproject.org' 44 | SITE_URL = '%s/pkgdb' % SITE_ROOT 45 | 46 | 47 | ### Bugzilla information 48 | 49 | ## Upon changes in pkgdb, update bugzilla 50 | PKGDB2_BUGZILLA_NOTIFICATION = False 51 | ## URL to the bugzilla instance to update 52 | PKGDB2_BUGZILLA_URL = 'https://bugzilla.redhat.com' 53 | ## name of the user the pkgdb application can log in to bugzilla with 54 | PKGDB2_BUGZILLA_USER = None 55 | ## password of the user the pkgdb application can log in to bugzilla with 56 | PKGDB2_BUGZILLA_PASSWORD = None 57 | 58 | ### Settings specific to the ``pkgdb-sync-bugzilla`` script/cron 59 | PKGDB2_BUGZILLA_NOTIFY_EMAIL = [ 60 | 'kevin@fedoraproject.org', 61 | 'pingou@fedoraproject'] 62 | BUGZILLA_COMPONENT_API = "component.get" 63 | ## Boolean to specify if the pkgdb-sync-bugzilla script runs for real or not 64 | PKGDB2_BUGZILLA_DRY_RUN = False 65 | 66 | ### FAS information 67 | 68 | ## URL to the FAS instance to query 69 | PKGDB2_FAS_URL = 'https://admin.fedoraproject.org/accounts' 70 | ## name of the user the pkgdb application can log in to FAS with 71 | PKGDB2_FAS_USER = None 72 | ## password of the user the pkgdb application can log in to FAS with 73 | PKGDB2_FAS_PASSWORD = None 74 | 75 | 76 | ### pkgdb notifications 77 | 78 | ## Pkgdb broadcasts its notifications via fedmsg 79 | PKGDB2_FEDMSG_NOTIFICATION = True 80 | ## Pkgdb sends its notifications by email 81 | PKGDB2_EMAIL_NOTIFICATION = False 82 | ## Template to build the email address pkgdb sends its notifications to 83 | PKGDB2_EMAIL_TO = '{pkg_name}-owner@fedoraproject.org' 84 | ## The From address email notifications are sent with 85 | PKGDB2_EMAIL_FROM = 'nobody@fedoraproject.org' 86 | ## The SMTP server to use to send email notifications 87 | PKGDB2_EMAIL_SMTP_SERVER = 'localhost' 88 | ## Email address that should be cc'ed to every emails sent 89 | PKGDB2_EMAIL_CC = None 90 | 91 | 92 | ### Email stacktrace 93 | 94 | ## pkgdb sends email when it faces an exception (trying to add an existing 95 | ## package or something alike. These emails are sent to the address set 96 | ## here: 97 | #MAIL_ADMIN = 'admin@pkgdb' 98 | 99 | 100 | ## List the packages that are not accessible to the provenpackager group 101 | PKGS_NOT_PROVENPACKAGER = ['firefox'] 102 | 103 | ## Make browsers send session cookie only via HTTPS 104 | SESSION_COOKIE_SECURE = True 105 | 106 | ## Set a default application root to prevent any potential cookie conflict if 107 | ## pgkdb is not deployed at the root of the server. This is used to set the 108 | ## `path` value of the cookie, actually avoiding the cookie conflict. 109 | APPLICATION_ROOT = '/pkgdb/' 110 | 111 | 112 | ### Fedmenu integration 113 | ## PkgDB2 can be integrated with Fedmenu if the following item are activated. 114 | 115 | ## URL to where are located the fedmenu js files 116 | # FEDMENU_URL = 'https://apps.fedoraproject.org/fedmenu/' 117 | ## URL to where is the data to load/show in fedmenu 118 | # FEDMENU_DATA_URL = 'https://apps.fedoraproject.org/js/data.js' 119 | -------------------------------------------------------------------------------- /pkgdb2/templates/stats.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | 3 | {% block title %} {{ super() }} {% endblock %} 4 | 5 | {%block tag %}stats{% endblock %} 6 | 7 | {% block content %} 8 | 9 |

{{ config['PROJECT_NAME'] }} Package Database

10 | 11 |

12 | PkgDB stores currently information about {{ collections | length }} 13 | active {{ config['PROJECT_NAME'] }} releases. 14 |

15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% for collec in collections | sort %} 23 | 24 | 25 | 26 | 27 | {% endfor %} 28 |
{{ config['PROJECT_NAME'] }} branch / releaseNumber of packages
{{ collec[0] }}{{ collec[1] }}
29 | 30 | 31 |

Evolution in {{ config['PROJECT_NAME'] }}

32 | 33 |
34 |
35 |
36 | 37 |

Top maintainers

38 | 39 | 40 | {% for row in top_maintainers %} 41 | 42 | 45 | 51 | 54 | 55 | {% endfor %} 56 |
43 | #{{ loop.index }} 44 | 46 | {{ row[0] | avatar(32) | safe }} 47 | 48 | {{ row[0] }} 49 | 50 | 52 | {{ row[1] }} 53 |
57 | 58 |

Top point of contact

59 | 60 | 61 | {% for row in top_poc %} 62 | 63 | 66 | 72 | 75 | 76 | {% endfor %} 77 |
64 | #{{ loop.index }} 65 | 67 | {{ row[0] | avatar(32) | safe }} 68 | 69 | {{ row[0] }} 70 | 71 | 73 | {{ row[1] }} 74 |
78 | 79 | {% endblock %} 80 | 81 | {% block jscripts %} 82 | {{ super() }} 83 | 84 | 86 | 88 | 89 | 90 | 152 | 153 | {% endblock %} 154 | -------------------------------------------------------------------------------- /pkgdb2/templates/list_packages.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | 3 | {% block title %} {{ super() }} {% endblock %} 4 | 5 | {%block tag %}packages{% endblock %} 6 | 7 | 8 | 9 | {% set options = True %} 10 | {%block options %} 11 |
12 | Restrict to branch:
13 | Restrict to status: 14 |
20 | Restrict to owner:
21 | Restrict to namespace: 22 |
27 | 28 |
29 | {% if is_admin %} 30 | 37 | {% endif %} 38 | {% endblock %} 39 | 40 | {% block content %} 41 | 42 |

Search packages{% if namespace %} ({{ namespace }}) {% endif %}

43 | 44 | {% if origin == 'list_packages' %} 45 |

See the list of orphaned 46 | or retired packages

47 | {% elif origin == 'list_orphaned' %} 48 |

See the list of active 49 | or retired packages

50 | {% elif origin == 'list_retired' %} 51 |

See the list of active 52 | or orphaned packages

53 | {% endif %} 54 | 55 |

{{ packages_count }} packages found

56 | 57 | {% if total_page and total_page > 1 and total_page >= page %} 58 | 59 | 60 | 74 | 75 | 89 | 90 |
61 | {% if page > 1 %} 62 | 68 | < Previous 69 | 70 | {% else %} 71 | < Previous 72 | {% endif %} 73 | {{ page }} / {{ total_page }} 76 | {% if page < total_page %} 77 | 83 | Next > 84 | 85 | {% else %} 86 | Next > 87 | {% endif %} 88 |
91 | {% endif %} 92 | 93 |
    94 | {% if total_page >= page and page > 0 %} 95 | {% for pkg in packages %} 96 |
  • 97 | 99 | {{ pkg.namespace }}/{{ pkg.name }} 100 | -- {{ pkg.summary }} 101 | ( 102 | {% for listing in pkg.listings | sort(attribute="collection.branchname", reverse=True) 103 | %} {% if listing.collection.status != 'EOL' %} 104 | {% if origin == 'list_packages' and listing.status == 'Approved' %} 105 | {{ listing.collection.branchname }} 106 | {% elif origin == 'list_retired' and listing.status == 'Retired' %} 107 | {{ listing.collection.branchname }} 108 | {% elif origin == 'list_orphaned' and listing.status == 'Orphaned' %} 109 | {{ listing.collection.branchname }} 110 | {% endif %} 111 | {% endif %}{% endfor %} 112 | ) 113 |
  • 114 | {% endfor %} 115 | {% elif total_page %} 116 |
  • Sorry, but the page you are requesting is unavailable.
    117 | 118 | Back to the list 119 | 120 |
  • 121 | {% else %} 122 |

    No packages found in the database.

    123 | {% endif %} 124 |
125 | 126 | {% endblock %} 127 | -------------------------------------------------------------------------------- /pkgdb2/templates/acl_update.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | 3 | {% block title %} {{ package.name }} | PkgDB {% endblock %} 4 | 5 | {%block tag %}packages{% endblock %} 6 | 7 | {% block content %} 8 | 9 |
10 |

11 | 14 | {{ package.name }} 15 | > Edit {{ acl.capitalize() }} Access

16 | 17 |
18 |
19 |

User(s)

20 |
21 | 22 |
25 | 26 | 27 | 28 | {% for branch in branches | sort_branches %} 29 | 32 | {% endfor %} 33 | 34 | {% for user in commit_acls | sort %} 35 | {% if loop.last %} 36 | 37 | {% else %} 38 | 39 | {% endif %} 40 | 46 | {% for branch in branches | sort_branches %} 47 | {% set coltype = loop.cycle('col_odd', 'col_even') %} 48 | {% if acl in commit_acls[user][branch] %} 49 | {% set select_cl = commit_acls[user][branch][acl].replace(' ', '_') %} 50 | {% else %} 51 | {% set select_cl = '' %} 52 | {% endif %} 53 | {% if not loop.last %} 54 | 70 | {% endfor %} 71 | 72 | {% endfor %} 73 |
30 | 31 | {{ branch }}
41 | 42 | 43 | {{ user }} 44 | 45 | 55 | {% else %} 56 | 57 | {% endif %} 58 | 68 | 69 |
74 | 75 | 100 | {{ form.csrf_token }} 101 |
102 |
103 | 104 |
105 | {% endblock %} 106 | 107 | {% block jscripts %} 108 | {{ super() }} 109 | 131 | {% endblock %} 132 | -------------------------------------------------------------------------------- /tests/test_flask_ui_packagers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2013 Red Hat, Inc. 4 | # 5 | # This copyrighted material is made available to anyone wishing to use, 6 | # modify, copy, or redistribute it subject to the terms and conditions 7 | # of the GNU General Public License v.2, or (at your option) any later 8 | # version. This program is distributed in the hope that it will be 9 | # useful, but WITHOUT ANY WARRANTY expressed or implied, including the 10 | # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR 11 | # PURPOSE. See the GNU General Public License for more details. You 12 | # should have received a copy of the GNU General Public License along 13 | # with this program; if not, write to the Free Software Foundation, 14 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | # 16 | # Any Red Hat trademarks that are incorporated in the source 17 | # code or documentation are not subject to the GNU General Public 18 | # License and may only be used or replicated with the express permission 19 | # of Red Hat, Inc. 20 | # 21 | 22 | ''' 23 | pkgdb tests for the Flask application. 24 | ''' 25 | 26 | __requires__ = ['SQLAlchemy >= 0.8'] 27 | import pkg_resources 28 | 29 | import unittest 30 | import sys 31 | import os 32 | 33 | sys.path.insert(0, os.path.join(os.path.dirname( 34 | os.path.abspath(__file__)), '..')) 35 | 36 | import pkgdb2 37 | from tests import (Modeltests, create_package_acl) 38 | 39 | 40 | class FlaskUiPackagersTest(Modeltests): 41 | """ Flask tests. """ 42 | 43 | def setUp(self): 44 | """ Set up the environnment, ran before every tests. """ 45 | super(FlaskUiPackagersTest, self).setUp() 46 | 47 | pkgdb2.APP.config['TESTING'] = True 48 | pkgdb2.SESSION = self.session 49 | pkgdb2.ui.SESSION = self.session 50 | pkgdb2.ui.acls.SESSION = self.session 51 | pkgdb2.ui.admin.SESSION = self.session 52 | pkgdb2.ui.collections.SESSION = self.session 53 | pkgdb2.ui.packagers.SESSION = self.session 54 | pkgdb2.ui.packages.SESSION = self.session 55 | self.app = pkgdb2.APP.test_client() 56 | 57 | def test_list_packagers(self): 58 | """ Test the list_packagers function. """ 59 | 60 | output = self.app.get('/packagers/') 61 | self.assertEqual(output.status_code, 200) 62 | self.assertTrue('

Search packagers

' in output.data) 63 | 64 | output = self.app.get('/packagers/?limit=abc&page=def') 65 | self.assertEqual(output.status_code, 200) 66 | self.assertTrue('

Search packagers

' in output.data) 67 | 68 | def test_packager_info(self): 69 | """ Test the packager_info function. """ 70 | create_package_acl(self.session) 71 | 72 | output = self.app.get('/packager/pingou/') 73 | self.assertEqual(output.status_code, 200) 74 | self.assertTrue('pingou' in output.data) 75 | self.assertTrue('' in output.data) 76 | 77 | output = self.app.get('/packager/random/') 78 | self.assertEqual(output.status_code, 200) 79 | self.assertTrue( 80 | '
  • No packager of this name found.
  • ' 81 | in output.data) 82 | 83 | def test_packager_requests(self): 84 | """ Test the packager_requests function. """ 85 | 86 | output = self.app.get('/packager/pingou/requests') 87 | self.assertEqual(output.status_code, 200) 88 | self.assertTrue("

    pingou's requests

    " in output.data) 89 | self.assertTrue( 90 | 'Restrict to package: ' 91 | in output.data) 92 | 93 | output = self.app.get( 94 | 'packager/pingou/requests?page=abc&limit=def&status=ghi&package=test') 95 | self.assertEqual(output.status_code, 200) 96 | self.assertTrue("

    pingou's requests

    " in output.data) 97 | self.assertTrue( 98 | 'Restrict to package: ' 99 | in output.data) 100 | self.assertTrue( 101 | 'class="errors">Incorrect limit provided, using defaultNo package exists' in output.data) 105 | 106 | output = self.app.get('packager/pingou/requests?package=guake') 107 | self.assertEqual(output.status_code, 200) 108 | self.assertTrue("

    pingou's requests

    " in output.data) 109 | self.assertTrue( 110 | 'Restrict to package: ' 111 | in output.data) 112 | 113 | 114 | if __name__ == '__main__': 115 | SUITE = unittest.TestLoader().loadTestsFromTestCase(FlaskUiPackagersTest) 116 | unittest.TextTestRunner(verbosity=2).run(SUITE) 117 | -------------------------------------------------------------------------------- /pkgdb2/templates/api.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | 3 | {% block title %} API | PkgDB {% endblock %} 4 | 5 | {%block tag %}index{% endblock %} 6 | 7 | {% block content %} 8 | 9 |

    10 | Collections 11 | 12 | 13 | 14 |

    15 |
    16 | {% for html in collections %} 17 | {% autoescape false %} 18 | {{ html | InsertDiv}} 19 | {% endautoescape %} 20 | {% endfor %} 21 |
    22 | 23 |

    24 | Packagers 25 | 26 | 27 | 28 |

    29 |
    30 | {% for html in packagers %} 31 | {% autoescape false %} 32 | {{ html | InsertDiv}} 33 | {% endautoescape %} 34 | {% endfor %} 35 |
    36 | 37 |

    38 | Packages 39 | 40 | 41 | 42 |

    43 | 44 |
    45 | {% for html in packages %} 46 | {% autoescape false %} 47 | {{ html | InsertDiv}} 48 | {% endautoescape %} 49 | {% endfor %} 50 |
    51 | 52 |

    53 | ACLs 54 | 55 | 56 | 57 |

    58 |
    59 | {% for html in acls %} 60 | {% autoescape false %} 61 | {{ html | InsertDiv}} 62 | {% endautoescape %} 63 | {% endfor %} 64 |
    65 | 66 |

    67 | Other 68 | 69 | 70 | 71 |

    72 |
    73 | {% for html in other %} 74 | {% autoescape false %} 75 | {{ html | InsertDiv}} 76 | {% endautoescape %} 77 | {% endfor %} 78 |
    79 | 80 |

    81 | Admin 82 | 83 | 84 | 85 |

    86 |
    87 | {% for html in admin %} 88 | {% autoescape false %} 89 | {{ html | InsertDiv}} 90 | {% endautoescape %} 91 | {% endfor %} 92 |
    93 | 94 |

    95 | Extras 96 | 97 | 98 | 99 |

    100 | 101 |

    102 | The pkgdb2 api has few other endpoints that are used for very specific 103 | tasks such as synchronizing the point of contact and CC list with 104 | bugzilla, or synchronizing the ACLs with the version control system (vcs). 105 | These endpoints, althought listed here, are not quite meant for public 106 | consumption, you may use them, just be cautious. 107 |

    108 | 109 |
    110 | {% for html in extras %} 111 | {% autoescape false %} 112 | {{ html | InsertDiv}} 113 | {% endautoescape %} 114 | {% endfor %} 115 |
    116 | 117 | {% endblock %} 118 | 119 | {% block jscripts %} 120 | {{ super() }} 121 | 159 | {% endblock %} 160 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Fedora PackageDB 2 | ================ 3 | 4 | PackageDB2 is a rewrite of `packagedb `_ 5 | using flask. 6 | 7 | PackageDB is the package database for Fedora. 8 | 9 | It is the application handling who is allowed to commit on the git of the 10 | Fedora packages, it also handles who is the person getting the bugs on the 11 | bugzilla and who get the notifications for changes in the git, builds or bugs. 12 | 13 | 14 | :Project page: https://fedorahosted.org/pkgdb2/ 15 | :Documentation: https://pkgdb2.readthedocs.org/ 16 | :Git repository: https://git.fedorahosted.org/git/pkgdb2 17 | :Github mirror: https://github.com/fedora-infra/pkgdb2 18 | :Mailing list: https://lists.fedorahosted.org/mailman/listinfo/packagedb 19 | 20 | 21 | Hacking 22 | ======= 23 | 24 | Hacking with Vagrant 25 | -------------------- 26 | Quickly start hacking on pkgdb2 using the vagrant setup that is included in the 27 | pkgdb2 repo is super simple. 28 | 29 | First, install Ansible, Vagrant, the vagrant-sshfs plugin, and the vagrant-libvirt 30 | plugin from the official Fedora repos:: 31 | 32 | $ sudo dnf install ansible vagrant vagrant-libvirt vagrant-sshfs 33 | 34 | 35 | Now, from within main directory (the one with the Vagrantfile in it) of your git 36 | checkout of pkgdb, run the ``vagrant up`` command to provision your dev 37 | environment:: 38 | 39 | $ vagrant up 40 | 41 | When this command is completed (it may take a while) you will be able to ssh 42 | into your dev VM with ``vagrant ssh`` and then run the command to start the 43 | pkgdb2 server:: 44 | 45 | $ vagrant ssh 46 | [vagrant@localhost ~]$ pushd /vagrant/; ./runserver.py -c pkgdb2/vagrant_default_config.py --host "0.0.0.0"; 47 | 48 | Once that is running, simply go to http://localhost:5001/ in your browser on 49 | your host to see your running pkgdb2 test instance. 50 | 51 | Setting up a Dev Environment by hand 52 | ------------------------------------ 53 | 54 | Here are some preliminary instructions about how to stand up your own instance 55 | of packagedb2. We'll use a virtualenv and a sqlite database and we'll install 56 | our dependencies from the Python Package Index (PyPI). None of these are best 57 | practices for a production instance, but we haven't gotten around to writing 58 | and testing those instructions yet. 59 | 60 | First, set up a virtualenv:: 61 | 62 | $ sudo yum install python-virtualenv 63 | $ virtualenv my-pkgdb2-env 64 | $ source my-pkgdb2-env/bin/activate 65 | 66 | Issuing that last command should change your prompt to indicate that you are 67 | operating in an active virtualenv. 68 | 69 | Next, install your dependencies:: 70 | 71 | (my-pkgdb2-env)$ pip install kitchen paver urllib3 72 | (my-pkgdb2-env)$ git clone https://github.com/fedora-infra/pkgdb2.git 73 | (my-pkgdb2-env)$ cd pkgdb2 74 | (my-pkgdb2-env)$ pip install -r requirements.txt 75 | (my-pkgdb2-env)$ sudo dnf install postgresql-devel # required for psycopg2 76 | (my-pkgdb2-env)$ pip install -r test_requirements.txt 77 | 78 | You should run the test suite to make sure nothing is broken before proceeding:: 79 | 80 | (my-pkgdb2-env)$ ./runtests.sh 81 | 82 | By default the tests are ran against a local sqlite database, but you can have 83 | them run against `faitout `_ by setting 84 | an environment variable ``BUILD_ID``, for example:: 85 | 86 | (my-pkgdb2-env)$ BUILD_ID=1 ./runtests.sh 87 | 88 | Similarly, you can set the environment variable ``OFFLINE`` to skip tests 89 | requiring network access (handy if you are, for example, working on a plane), 90 | for example:: 91 | 92 | (my-pkgdb2-env)$ OFFLINE=2 ./runtests.sh 93 | 94 | 95 | You should then create your own sqlite database for your development instance of 96 | pkgdb2:: 97 | 98 | (my-pkgdb2-env)$ python createdb.py 99 | 100 | Setting up PostgreSQL 101 | ~~~~~~~~~~~~~~~~~~~~~ 102 | 103 | Using PostgreSQL is optional but if you want to work with real datadump then 104 | setting up PostgreSQL will be a better option 105 | 106 | For setting up the PostgreSQL database you can look into the `Fedora documentation about PostgresQL 107 | `_ 108 | 109 | .. note:: If you need/want a copy of the database used in production, follow the 110 | instructions in the `documentation 111 | `_ 112 | 113 | After executing all the above steps, you now need to `Adjust Postgresql Connection Settings 114 | `_ 115 | 116 | Now, you need to edit `/pkgdb2/default_config.py` file and replace:: 117 | 118 | DB_URL = 'sqlite:////var/tmp/pkgdb2_dev.sqlite' 119 | 120 | by:: 121 | 122 | DB_URL = 'postgresql://postgres:whatever@localhost/pkgdb2' 123 | 124 | If all goes well, you can start a development instance of the server by 125 | running:: 126 | 127 | (my-pkgdb2-env)$ python runserver.py 128 | 129 | Open your browser and visit http://localhost:5000 to check it out. 130 | 131 | 132 | For more information about the project configuration or deployment, check out 133 | the `documentation `_ 134 | -------------------------------------------------------------------------------- /pkgdb2/templates/list_actions.html: -------------------------------------------------------------------------------- 1 | {% extends "master.html" %} 2 | 3 | {% block title %} {{ super() }} {% endblock %} 4 | 5 | {%block header %} 6 | {% if refresh %} 7 | 8 | {% endif %} 9 | {% endblock %} 10 | 11 | 12 | {%block tag %}{% 13 | if select %}{% 14 | if g.fas_user and g.fas_user.username == packager %}myRequests{% 15 | else %}{{ select }}{% 16 | endif %}{% 17 | else %}admin{% 18 | endif 19 | %}{% endblock %} 20 | 21 | 22 | {% set options = True %} 23 | {%block options %} 24 | {% if select %} 25 |
    26 | {% else %} 27 | 28 | {% endif %} 29 | Restrict to package:
    30 | {% if not select %} 31 | Restrict to packager:
    32 | {% endif %} 33 | Restrict to action:
    34 | Restrict to Status: 35 | 43 |
    44 | 45 |
    46 | {% endblock %} 47 | 48 | {% block content %} 49 | 50 | {% if select %} 51 |

    {{ packager }}'s requests

    52 | {% else %} 53 |

    Actions

    54 | {% endif %} 55 | 56 | {% if total_page and total_page > 1 %} 57 | 58 | 59 | 76 | 77 | 92 | 93 |
    60 | {% if page > 1%} 61 | {% if select %} 62 | 65 | {% else %} 66 | 69 | {% endif %} 70 | < Previous 71 | 72 | {% else %} 73 | < Previous 74 | {% endif %} 75 | {{ page }} / {{ total_page }} 78 | {% if page < total_page %} 79 | {% if select %} 80 | 82 | {% else %} 83 | 85 | {% endif %} 86 | Next > 87 | 88 | {% else %} 89 | Next > 90 | {% endif %} 91 |
    94 | {% endif %} 95 | 96 | {% if actions %} 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | {% for action in actions %} 113 | {% if loop.last %} 114 | 115 | {% else %} 116 | 117 | {% endif %} 118 | 119 | 122 | 127 | 146 | 147 | 148 | 152 | 169 | 170 | {% endfor %} 171 | 172 |
    DateUserPackageActionBranchStatus
    120 | {{ action.date_created.strftime('%Y-%m-%d %H:%M:%S') }} 121 | 123 | 124 | {{ action.user }} 125 | 126 | 128 | {% if action.package %} 129 | 132 | {{ action.package.name }} 133 | 134 | {% elif action.action == 'request.package' and 135 | action.status == 'Approved' %} 136 | 140 | {{ action.info_data['pkg_name'] }} 141 | 142 | {% else %} 143 | {{ action.info_data['pkg_name'] }} 144 | {% endif %} 145 | {{ action.action }}{{ action.collection.branchname }} 149 | {{ action.status }} {%- if action.message %}: 150 | {{ action.message }}{% endif %} 151 | 153 | {% if not select %} 154 | 155 | Update 156 | 157 | {% else %} 158 | 160 | {% if action.status in ['Pending', 'Awaiting Review'] 161 | and g.fas_user and g.fas_user.username == action.user %} 162 | Update 163 | {% else %} 164 | Details 165 | {% endif %} 166 | 167 | {% endif %} 168 |
    173 | 174 | {% else %} 175 |

    No actions found

    176 | {% endif %} 177 | 178 | {% endblock %} 179 | 180 | {% block jscripts %} 181 | {{ super() }} 182 | 183 | 188 | 189 | 197 | {% endblock %} 198 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pkgdb.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pkgdb.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/pkgdb" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pkgdb" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /doc/development.rst: -------------------------------------------------------------------------------- 1 | Development 2 | =========== 3 | 4 | Get the sources 5 | --------------- 6 | 7 | Anonymous: 8 | 9 | :: 10 | 11 | git clone http://git.fedorahosted.org/git/pkgdb2.git 12 | 13 | Contributors: 14 | 15 | :: 16 | 17 | git clone ssh://@git.fedorahosted.org/git/pkgdb2.git 18 | 19 | 20 | Dependencies 21 | ------------ 22 | 23 | The dependencies of pkgdb2 are listed in the file ``requirements.txt`` 24 | at the top level of the sources. 25 | 26 | 27 | .. note:: if you work in a `virtualenv `_ 28 | the installation of python-fedora might fail the first time you 29 | try, just try to run the command twice, the second time it should 30 | work. 31 | 32 | 33 | Run pkgdb for development 34 | ------------------------- 35 | Copy the configuration file:: 36 | 37 | cp pkgdb2.cfg.sample pkgdb2.cfg 38 | 39 | Adjust the configuration file (secret key, database URL, admin group...) 40 | See :doc:`configuration` for more detailed information about the 41 | configuration. 42 | 43 | 44 | Create the database scheme:: 45 | 46 | ./createdb 47 | 48 | Run the server:: 49 | 50 | ./runserver 51 | 52 | You should be able to access the server at http://localhost:5000 53 | 54 | 55 | Every time you save a file, the project will be automatically restarted 56 | so you can see your change immediatly. 57 | 58 | 59 | Get a working database 60 | ---------------------- 61 | 62 | We provide a daily dump of the pkgdb2 database used in `production in Fedora 63 | `_. You can load this database dump 64 | and use it for your tests and hacking. 65 | 66 | You will need to set up your postgresql server first. 67 | 68 | Once you postgresql database is running, download the latest database dump:: 69 | 70 | wget http://infrastructure.fedoraproject.org/infra/db-dumps/pkgdb2.dump.xz 71 | 72 | Create the database itself:: 73 | 74 | sudo -u postgres createdb pkgdb2 75 | 76 | Load the dump:: 77 | 78 | xzcat pkgdb2.dump.xz | sudo -u postgres psql pkgdb2 79 | 80 | Please refer to ``createdb --help`` and ``pg_restore --help`` for further 81 | help on using these commands. 82 | 83 | 84 | Coding standards 85 | ---------------- 86 | 87 | We are trying to make the code `PEP8-compliant 88 | `_. There is a `pep8 tool 89 | `_ that can automatically check 90 | your source. 91 | 92 | 93 | We are also inspecting the code using `pylint 94 | `_ and aim of course for a 10/10 code 95 | (but it is an assymptotic goal). 96 | 97 | .. note:: both pep8 and pylint are available in Fedora via yum: 98 | 99 | :: 100 | 101 | yum install python-pep8 pylint 102 | 103 | 104 | Send patch 105 | ---------- 106 | 107 | The easiest way to work on pkgdb2 is to make your own branch in git, make 108 | your changes to this branch, commit whenever you want, rebase on master, 109 | whenever you need and when you are done, send the patch either by email, 110 | via the trac or a pull-request (using git or github). 111 | 112 | 113 | The workflow would therefore be something like: 114 | 115 | :: 116 | 117 | git branch 118 | git checkout 119 | 120 | git commit file1 file2 121 | 122 | git commit file3 file4 123 | git checkout master 124 | git pull 125 | git checkout 126 | git rebase master 127 | git format-patch -2 128 | 129 | This will create two patch files that you can send by email to submit in the 130 | trac. 131 | 132 | .. note:: You can send your patch by email to the `packagedb mailing-list 133 | `_ 134 | 135 | 136 | Unit-tests 137 | ---------- 138 | 139 | Pkgdb2 has a number of unit-tests providing at the moment a full 140 | coverage of the backend library (pkgdb.lib). 141 | 142 | 143 | We aim at having a full (100%) coverage of the whole code (including the 144 | Flask application) and of course a smart coverage as in we want to check 145 | that the functions work the way we want but also that they fail when we 146 | expect it and the way we expect it. 147 | 148 | 149 | Tests checking that function are failing when/how we want are as important 150 | as tests checking they work the way they are intended to. 151 | 152 | ``runtests.sh``, located at the top of the sources, helps to run the 153 | unit-tests of the project with coverage information using `python-nose 154 | `_. 155 | 156 | 157 | .. note:: You can specify additional arguments to the nose command used 158 | in this script by just passing arguments to the script. 159 | 160 | For example you can specify the ``-x`` / ``--stop`` argument: 161 | `Stop running tests after the first error or failure` by just doing 162 | 163 | :: 164 | 165 | ./runtests.sh --stop 166 | 167 | 168 | Each unit-tests files (located under ``tests/``) can be called 169 | by alone, allowing easier debugging of the tests. For example: 170 | 171 | :: 172 | 173 | python tests/test_collection.py 174 | 175 | Similarly as for nose you can also ask that the unit-test stop at the first 176 | error or failure. For example, the command could be: 177 | 178 | :: 179 | 180 | PKGDB2_CONFIG=tests/pkgd2b_test.cfg python -m unittest -f -v pkgdb2.tests.test_collection 181 | 182 | 183 | .. note:: In order to have coverage information you might have to install 184 | ``python-coverage`` 185 | 186 | :: 187 | 188 | yum install python-coverage 189 | 190 | One of our test requires network access, which means when you are working 191 | offline (when traveling for example) the test will fail. In order to have the 192 | whole test suite succeed when working offline, you can skip this test by setting 193 | an ``OFFLINE`` environment variable. 194 | For example: 195 | 196 | :: 197 | 198 | OFFLINE=1 ./runtests.sh -x 199 | 200 | 201 | Troubleshooting 202 | --------------- 203 | 204 | + Login fails in development mode 205 | 206 | The Flask FAS extension requires a secure cookie which ensures that it is 207 | always encrypted during client/server exchanges. 208 | This makes the authentication cookie less likely to be exposed to cookie 209 | theft by eavesdropping. 210 | 211 | You can disable the secure cookie for testing purposes by setting the 212 | configuration key ``FAS_HTTPS_REQUIRED`` to False. 213 | 214 | .. WARNING:: 215 | Do not use this option in production as it causes major security issues 216 | 217 | -------------------------------------------------------------------------------- /pkgdb2/mail_logging.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2014 Red Hat, Inc. 4 | # 5 | # This copyrighted material is made available to anyone wishing to use, 6 | # modify, copy, or redistribute it subject to the terms and conditions 7 | # of the GNU General Public License v.2, or (at your option) any later 8 | # version. This program is distributed in the hope that it will be 9 | # useful, but WITHOUT ANY WARRANTY expressed or implied, including the 10 | # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR 11 | # PURPOSE. See the GNU General Public License for more details. You 12 | # should have received a copy of the GNU General Public License along 13 | # with this program; if not, write to the Free Software Foundation, 14 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | # 16 | # Any Red Hat trademarks that are incorporated in the source 17 | # code or documentation are not subject to the GNU General Public 18 | # License and may only be used or replicated with the express permission 19 | # of Red Hat, Inc. 20 | # 21 | 22 | ''' 23 | Mail handler for logging. 24 | ''' 25 | import logging 26 | import logging.handlers 27 | 28 | import inspect 29 | import os 30 | import socket 31 | import traceback 32 | 33 | import flask 34 | 35 | psutil = None 36 | try: 37 | import psutil 38 | except (OSError, ImportError): 39 | # We run into issues when trying to import psutil from inside mod_wsgi on 40 | # rhel7. If we hit that here, then just fail quietly. 41 | # https://github.com/jmflinuxtx/kerneltest-harness/pull/17#issuecomment-48007837 42 | pass 43 | 44 | 45 | class ContextInjector(logging.Filter): 46 | """ Logging filter that adds context to log records. 47 | 48 | Filters are typically used to "filter" log records. They declare a filter 49 | method that can return True or False. Only records with 'True' will 50 | actually be logged. 51 | 52 | Here, we somewhat abuse the concept of a filter. We always return true, 53 | but we use the opportunity to hang important contextual information on the 54 | log record to later be used by the logging Formatter. We don't normally 55 | want to see all this stuff in normal log records, but we *do* want to see 56 | it when we are emailed error messages. Seeing an error, but not knowing 57 | which host it comes from, is not that useful. 58 | 59 | http://docs.python.org/2/howto/logging-cookbook.html#filters-contextual 60 | 61 | This code has been originally written by Ralph Bean for the fedmsg 62 | project: 63 | https://github.com/fedora-infra/fedmsg/ 64 | and can be found at: 65 | https://infrastructure.fedoraproject.org/cgit/ansible.git/tree/roles/fedmsg/base/templates/logging.py.j2 66 | 67 | """ 68 | 69 | def filter(self, record): 70 | current_process = ContextInjector.get_current_process() 71 | current_hostname = socket.gethostname() 72 | 73 | record.host = current_hostname 74 | record.proc = current_process 75 | record.pid = '-' 76 | if not isinstance(current_process, str): 77 | record.pid = current_process.pid 78 | record.proc_name = current_process.name 79 | # Be compatible with python-psutil 1.0 and 2.0, 3.0 80 | cmd_line = current_process.cmdline 81 | if callable(cmd_line): 82 | cmd_line = cmd_line() 83 | record.command_line = " ".join(cmd_line) 84 | record.callstack = self.format_callstack() 85 | 86 | record.url = '-' 87 | record.args = '-' 88 | record.form = '-' 89 | record.username = '-' 90 | try: 91 | record.url = flask.request.url 92 | except RuntimeError: 93 | pass 94 | try: 95 | record.args = dict(flask.request.args) 96 | except RuntimeError: 97 | pass 98 | try: 99 | record.form = dict(flask.request.form) 100 | if 'csrf_token' in record.form: 101 | record.form['csrf_token'] = 'Was present, is reset' 102 | except RuntimeError: 103 | pass 104 | try: 105 | record.username = flask.g.fas_user.username 106 | except: 107 | pass 108 | 109 | return True 110 | 111 | @staticmethod 112 | def format_callstack(): 113 | for i, frame in enumerate(f[0] for f in inspect.stack()): 114 | if '__name__' not in frame.f_globals: 115 | continue 116 | modname = frame.f_globals['__name__'].split('.')[0] 117 | if modname != "logging": 118 | break 119 | 120 | def _format_frame(frame): 121 | return ' File "%s", line %i in %s\n %s' % (frame) 122 | 123 | stack = traceback.extract_stack() 124 | stack = stack[:-i] 125 | return "\n".join([_format_frame(frame) for frame in stack]) 126 | 127 | @staticmethod 128 | def get_current_process(): 129 | mypid = os.getpid() 130 | 131 | if not psutil: 132 | return "Could not import psutil for %r" % mypid 133 | 134 | for proc in psutil.process_iter(): 135 | if proc.pid == mypid: 136 | return proc 137 | 138 | # This should be impossible. 139 | raise ValueError("Could not find process %r" % mypid) 140 | 141 | 142 | MSG_FORMAT = """Process Details 143 | --------------- 144 | host: %(host)s 145 | PID: %(pid)s 146 | name: %(proc_name)s 147 | command: %(command_line)s 148 | 149 | Message type: %(levelname)s 150 | Location: %(pathname)s:%(lineno)d 151 | Module: %(module)s 152 | Function: %(funcName)s 153 | Time: %(asctime)s 154 | 155 | 156 | URL: %(url)s 157 | args: %(args)s 158 | form: %(form)s 159 | user: %(username)s 160 | 161 | 162 | Message: 163 | -------- 164 | 165 | %(message)s 166 | 167 | 168 | Callstack that lead to the logging statement 169 | -------------------------------------------- 170 | %(callstack)s 171 | """ 172 | 173 | 174 | def get_mail_handler(smtp_server, mail_admin): 175 | """ Set up the handler sending emails for big exception 176 | """ 177 | import pkgdb2 178 | 179 | sender = pkgdb2.APP.config.get( 180 | 'PKGDB2_EMAIL_FROM', 'nobody@fedoraproject.org'), 181 | mail_handler = logging.handlers.SMTPHandler( 182 | smtp_server, 183 | sender, 184 | mail_admin, 185 | 'PkgDB2 error') 186 | mail_handler.setFormatter(logging.Formatter(MSG_FORMAT)) 187 | mail_handler.setLevel(logging.ERROR) 188 | mail_handler.addFilter(ContextInjector()) 189 | return mail_handler 190 | -------------------------------------------------------------------------------- /pkgdb2/ui/packagers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2013 Red Hat, Inc. 4 | # 5 | # This copyrighted material is made available to anyone wishing to use, 6 | # modify, copy, or redistribute it subject to the terms and conditions 7 | # of the GNU General Public License v.2, or (at your option) any later 8 | # version. This program is distributed in the hope that it will be 9 | # useful, but WITHOUT ANY WARRANTY expressed or implied, including the 10 | # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR 11 | # PURPOSE. See the GNU General Public License for more details. You 12 | # should have received a copy of the GNU General Public License along 13 | # with this program; if not, write to the Free Software Foundation, 14 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | # 16 | # Any Red Hat trademarks that are incorporated in the source 17 | # code or documentation are not subject to the GNU General Public 18 | # License and may only be used or replicated with the express permission 19 | # of Red Hat, Inc. 20 | # 21 | 22 | ''' 23 | UI namespace for the Flask application. 24 | ''' 25 | 26 | import flask 27 | from math import ceil 28 | 29 | import pkgdb2.lib as pkgdblib 30 | from pkgdb2 import SESSION, APP 31 | from pkgdb2.ui import UI 32 | from pkgdb2.lib.exceptions import PkgdbException 33 | 34 | 35 | @UI.route('/packagers/') 36 | @UI.route('/packagers//') 37 | @UI.route('/packagers//') 38 | def list_packagers(motif=None): 39 | ''' Display the list of packagers corresponding to the motif. ''' 40 | 41 | pattern = flask.request.args.get('motif', motif) or '*' 42 | limit = flask.request.args.get('limit', APP.config['ITEMS_PER_PAGE']) 43 | page = flask.request.args.get('page', 1) 44 | 45 | try: 46 | page = abs(int(page)) 47 | except ValueError: 48 | page = 1 49 | 50 | try: 51 | limit = abs(int(limit)) 52 | except ValueError: 53 | limit = APP.config['ITEMS_PER_PAGE'] 54 | flask.flash('Incorrect limit provided, using default', 'errors') 55 | 56 | packagers = pkgdblib.search_packagers( 57 | SESSION, 58 | pattern=pattern, 59 | page=page, 60 | limit=limit, 61 | ) 62 | 63 | packagers_count = pkgdblib.search_packagers( 64 | SESSION, 65 | pattern=pattern, 66 | page=page, 67 | limit=limit, 68 | count=True, 69 | ) 70 | total_page = int(ceil(packagers_count / float(limit))) 71 | 72 | packagers = [pkg[0] for pkg in packagers] 73 | 74 | if len(packagers) == 1: 75 | flask.flash( 76 | 'Only one packager matching, redirecting you to ''his/her page') 77 | return flask.redirect(flask.url_for( 78 | '.packager_info', packager=packagers[0])) 79 | 80 | return flask.render_template( 81 | 'list_packagers.html', 82 | select='packagers', 83 | packagers=packagers, 84 | motif=motif, 85 | total_page=total_page, 86 | page=page 87 | ) 88 | 89 | 90 | @UI.route('/packager//') 91 | def packager_info(packager): 92 | ''' Display the information about the specified packager. ''' 93 | eol = flask.request.args.get('eol', False) 94 | 95 | packages_co = pkgdblib.get_package_maintained( 96 | SESSION, 97 | packager=packager, 98 | poc=False, 99 | eol=eol, 100 | ) 101 | 102 | packages = pkgdblib.get_package_maintained( 103 | SESSION, 104 | packager=packager, 105 | poc=True, 106 | eol=eol, 107 | ) 108 | 109 | packages_watch = pkgdblib.get_package_watch( 110 | SESSION, 111 | packager=packager, 112 | eol=eol, 113 | ) 114 | 115 | # Filter out from the watch list packaged where user has commit rights 116 | packages_obj = set([it[0] for it in packages_co]) 117 | packages_obj = packages_obj.union(set([it[0] for it in packages])) 118 | packages_watch = [ 119 | it for it in packages_watch if it[0] not in packages_obj] 120 | 121 | if not packages and not packages_co and not packages_watch: 122 | flask.flash('No packager of this name found.', 'errors') 123 | return flask.render_template('msg.html') 124 | 125 | return flask.render_template( 126 | 'packager.html', 127 | select='packagers', 128 | packager=packager, 129 | packages=packages, 130 | packages_co=packages_co, 131 | packages_watch=packages_watch, 132 | ) 133 | 134 | 135 | @UI.route('/packager//requests') 136 | def packager_requests(packager): 137 | ''' Display the requests made by the specified packager. ''' 138 | action = flask.request.args.get('action') or None 139 | package = flask.request.args.get('package') or None 140 | status = flask.request.args.get('status', 'All') 141 | limit = flask.request.args.get('limit', APP.config['ITEMS_PER_PAGE']) 142 | page = flask.request.args.get('page', 1) 143 | 144 | try: 145 | page = abs(int(page)) 146 | except ValueError: 147 | page = 1 148 | 149 | try: 150 | limit = abs(int(limit)) 151 | except ValueError: 152 | limit = APP.config['ITEMS_PER_PAGE'] 153 | flask.flash('Incorrect limit provided, using default', 'errors') 154 | 155 | actions = [] 156 | cnt_actions = 0 157 | try: 158 | actions = pkgdblib.search_actions( 159 | SESSION, 160 | packager=packager, 161 | package=package, 162 | action=action, 163 | status=status, 164 | page=page, 165 | limit=limit, 166 | order='desc', 167 | ) 168 | cnt_actions = pkgdblib.search_actions( 169 | SESSION, 170 | packager=packager, 171 | package=package, 172 | action=action, 173 | status=status, 174 | page=page, 175 | limit=limit, 176 | count=True, 177 | ) 178 | except PkgdbException as err: 179 | flask.flash(err, 'errors') 180 | 181 | total_page = int(ceil(cnt_actions / float(limit))) 182 | 183 | action_status = pkgdblib.get_status( 184 | SESSION, 'admin_status')['admin_status'] 185 | action_status.insert(0, 'All') 186 | 187 | return flask.render_template( 188 | 'list_actions.html', 189 | select='packagers', 190 | actions=actions, 191 | cnt_actions=cnt_actions, 192 | total_page=total_page, 193 | page=page, 194 | package=package or '', 195 | packager=packager, 196 | action=action, 197 | status=status, 198 | statuses=action_status, 199 | ) 200 | -------------------------------------------------------------------------------- /pkgdb2/static/jquery.flot.categories.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for plotting textual data or categories. 2 | 3 | Copyright (c) 2007-2013 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | Consider a dataset like [["February", 34], ["March", 20], ...]. This plugin 7 | allows you to plot such a dataset directly. 8 | 9 | To enable it, you must specify mode: "categories" on the axis with the textual 10 | labels, e.g. 11 | 12 | $.plot("#placeholder", data, { xaxis: { mode: "categories" } }); 13 | 14 | By default, the labels are ordered as they are met in the data series. If you 15 | need a different ordering, you can specify "categories" on the axis options 16 | and list the categories there: 17 | 18 | xaxis: { 19 | mode: "categories", 20 | categories: ["February", "March", "April"] 21 | } 22 | 23 | If you need to customize the distances between the categories, you can specify 24 | "categories" as an object mapping labels to values 25 | 26 | xaxis: { 27 | mode: "categories", 28 | categories: { "February": 1, "March": 3, "April": 4 } 29 | } 30 | 31 | If you don't specify all categories, the remaining categories will be numbered 32 | from the max value plus 1 (with a spacing of 1 between each). 33 | 34 | Internally, the plugin works by transforming the input data through an auto- 35 | generated mapping where the first category becomes 0, the second 1, etc. 36 | Hence, a point like ["February", 34] becomes [0, 34] internally in Flot (this 37 | is visible in hover and click events that return numbers rather than the 38 | category labels). The plugin also overrides the tick generator to spit out the 39 | categories as ticks instead of the values. 40 | 41 | If you need to map a value back to its label, the mapping is always accessible 42 | as "categories" on the axis object, e.g. plot.getAxes().xaxis.categories. 43 | 44 | */ 45 | 46 | (function ($) { 47 | var options = { 48 | xaxis: { 49 | categories: null 50 | }, 51 | yaxis: { 52 | categories: null 53 | } 54 | }; 55 | 56 | function processRawData(plot, series, data, datapoints) { 57 | // if categories are enabled, we need to disable 58 | // auto-transformation to numbers so the strings are intact 59 | // for later processing 60 | 61 | var xCategories = series.xaxis.options.mode == "categories", 62 | yCategories = series.yaxis.options.mode == "categories"; 63 | 64 | if (!(xCategories || yCategories)) 65 | return; 66 | 67 | var format = datapoints.format; 68 | 69 | if (!format) { 70 | // FIXME: auto-detection should really not be defined here 71 | var s = series; 72 | format = []; 73 | format.push({ x: true, number: true, required: true }); 74 | format.push({ y: true, number: true, required: true }); 75 | 76 | if (s.bars.show || (s.lines.show && s.lines.fill)) { 77 | var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero)); 78 | format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale }); 79 | if (s.bars.horizontal) { 80 | delete format[format.length - 1].y; 81 | format[format.length - 1].x = true; 82 | } 83 | } 84 | 85 | datapoints.format = format; 86 | } 87 | 88 | for (var m = 0; m < format.length; ++m) { 89 | if (format[m].x && xCategories) 90 | format[m].number = false; 91 | 92 | if (format[m].y && yCategories) 93 | format[m].number = false; 94 | } 95 | } 96 | 97 | function getNextIndex(categories) { 98 | var index = -1; 99 | 100 | for (var v in categories) 101 | if (categories[v] > index) 102 | index = categories[v]; 103 | 104 | return index + 1; 105 | } 106 | 107 | function categoriesTickGenerator(axis) { 108 | var res = []; 109 | for (var label in axis.categories) { 110 | var v = axis.categories[label]; 111 | if (v >= axis.min && v <= axis.max) 112 | res.push([v, label]); 113 | } 114 | 115 | res.sort(function (a, b) { return a[0] - b[0]; }); 116 | 117 | return res; 118 | } 119 | 120 | function setupCategoriesForAxis(series, axis, datapoints) { 121 | if (series[axis].options.mode != "categories") 122 | return; 123 | 124 | if (!series[axis].categories) { 125 | // parse options 126 | var c = {}, o = series[axis].options.categories || {}; 127 | if ($.isArray(o)) { 128 | for (var i = 0; i < o.length; ++i) 129 | c[o[i]] = i; 130 | } 131 | else { 132 | for (var v in o) 133 | c[v] = o[v]; 134 | } 135 | 136 | series[axis].categories = c; 137 | } 138 | 139 | // fix ticks 140 | if (!series[axis].options.ticks) 141 | series[axis].options.ticks = categoriesTickGenerator; 142 | 143 | transformPointsOnAxis(datapoints, axis, series[axis].categories); 144 | } 145 | 146 | function transformPointsOnAxis(datapoints, axis, categories) { 147 | // go through the points, transforming them 148 | var points = datapoints.points, 149 | ps = datapoints.pointsize, 150 | format = datapoints.format, 151 | formatColumn = axis.charAt(0), 152 | index = getNextIndex(categories); 153 | 154 | for (var i = 0; i < points.length; i += ps) { 155 | if (points[i] == null) 156 | continue; 157 | 158 | for (var m = 0; m < ps; ++m) { 159 | var val = points[i + m]; 160 | 161 | if (val == null || !format[m][formatColumn]) 162 | continue; 163 | 164 | if (!(val in categories)) { 165 | categories[val] = index; 166 | ++index; 167 | } 168 | 169 | points[i + m] = categories[val]; 170 | } 171 | } 172 | } 173 | 174 | function processDatapoints(plot, series, datapoints) { 175 | setupCategoriesForAxis(series, "xaxis", datapoints); 176 | setupCategoriesForAxis(series, "yaxis", datapoints); 177 | } 178 | 179 | function init(plot) { 180 | plot.hooks.processRawData.push(processRawData); 181 | plot.hooks.processDatapoints.push(processDatapoints); 182 | } 183 | 184 | $.plot.plugins.push({ 185 | init: init, 186 | options: options, 187 | name: 'categories', 188 | version: '1.0' 189 | }); 190 | })(jQuery); 191 | -------------------------------------------------------------------------------- /pkgdb2/api/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2013 Red Hat, Inc. 4 | # 5 | # This copyrighted material is made available to anyone wishing to use, 6 | # modify, copy, or redistribute it subject to the terms and conditions 7 | # of the GNU General Public License v.2, or (at your option) any later 8 | # version. This program is distributed in the hope that it will be 9 | # useful, but WITHOUT ANY WARRANTY expressed or implied, including the 10 | # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR 11 | # PURPOSE. See the GNU General Public License for more details. You 12 | # should have received a copy of the GNU General Public License along 13 | # with this program; if not, write to the Free Software Foundation, 14 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | # 16 | # Any Red Hat trademarks that are incorporated in the source 17 | # code or documentation are not subject to the GNU General Public 18 | # License and may only be used or replicated with the express permission 19 | # of Red Hat, Inc. 20 | # 21 | 22 | ''' 23 | API namespace for the Flask application. 24 | ''' 25 | 26 | import flask 27 | 28 | 29 | API = flask.Blueprint('api_ns', __name__, url_prefix='/api') 30 | 31 | from pkgdb2 import __version__, __api_version__, APP 32 | from pkgdb2.doc_utils import load_doc 33 | 34 | 35 | def get_limit(): 36 | """ Retrieve the limit used to limit the output retrieved. """ 37 | limit = flask.request.args.get('limit', 250) 38 | try: 39 | limit = abs(int(limit)) 40 | except ValueError: 41 | limit = 250 42 | 43 | if limit > 500: 44 | limit = 500 45 | 46 | return limit 47 | 48 | 49 | from pkgdb2.api import admin 50 | from pkgdb2.api import acls 51 | from pkgdb2.api import collections 52 | from pkgdb2.api import extras 53 | from pkgdb2.api import packagers 54 | from pkgdb2.api import packages 55 | 56 | 57 | @APP.template_filter('InsertDiv') 58 | def insert_div(content): 59 | """ Template filter inserting an opening
    and closing
    60 | after the first title and then at the end of the content. 61 | """ 62 | # This is quite a hack but simpler solution using .replace() didn't work 63 | # for some reasons... 64 | content = content.split('\n') 65 | output = [] 66 | for row in content: 67 | if row.startswith('
    ', '
    ') 72 | output = output.replace('h1', 'h3') 73 | 74 | return output 75 | 76 | 77 | @API.context_processor 78 | def inject_pkgdb_version(): 79 | """ Inject whether the pkgdb2 version number on every template of this 80 | namespace as well. 81 | """ 82 | return dict(version=__version__) 83 | 84 | 85 | @API.route('/') 86 | def api(): 87 | ''' Display the api information page. ''' 88 | api_collection_new = load_doc(collections.api_collection_new) 89 | api_collection_status = load_doc(collections.api_collection_status) 90 | api_collection_list = load_doc(collections.api_collection_list) 91 | 92 | api_packager_acl = load_doc(packagers.api_packager_acl) 93 | api_packager_package = load_doc(packagers.api_packager_package) 94 | api_packager_list = load_doc(packagers.api_packager_list) 95 | api_packager_stats = load_doc(packagers.api_packager_stats) 96 | 97 | api_package_info = load_doc(packages.api_package_info) 98 | api_package_new = load_doc(packages.api_package_new) 99 | api_package_edit = load_doc(packages.api_package_edit) 100 | api_package_critpath = load_doc(packages.api_package_critpath) 101 | api_package_orphan = load_doc(packages.api_package_orphan) 102 | api_package_unorphan = load_doc(packages.api_package_unorphan) 103 | api_package_retire = load_doc(packages.api_package_retire) 104 | api_package_unretire = load_doc(packages.api_package_unretire) 105 | api_package_list = load_doc(packages.api_package_list) 106 | api_monitor_package = load_doc(packages.api_monitor_package) 107 | api_koschei_package = load_doc(packages.api_koschei_package) 108 | api_package_request = load_doc(packages.api_package_request) 109 | 110 | api_acl_update = load_doc(acls.api_acl_update) 111 | api_acl_reassign = load_doc(acls.api_acl_reassign) 112 | 113 | api_admin_actions = load_doc(admin.api_admin_actions) 114 | api_admin_action = load_doc(admin.api_admin_action) 115 | api_admin_update_actions = load_doc(admin.api_admin_action_edit_status) 116 | 117 | api_version_doc = load_doc(api_version) 118 | api_extras_bugzilla = load_doc(extras.api_bugzilla) 119 | api_extras_critpath = load_doc(extras.api_critpath) 120 | api_extras_notify = load_doc(extras.api_notify) 121 | api_extras_notify_all = load_doc(extras.api_notify_all) 122 | api_extras_vcs = load_doc(extras.api_vcs) 123 | api_extras_pendingacls = load_doc(extras.api_pendingacls) 124 | api_extras_api_groups = load_doc(extras.api_groups) 125 | api_extras_monitored = load_doc(extras.api_monitored) 126 | api_extras_koschei = load_doc(extras.api_koschei) 127 | api_extras_retired = load_doc(extras.api_retired) 128 | api_extras_pkgrequest = load_doc(extras.api_pkgrequest) 129 | 130 | return flask.render_template( 131 | 'api.html', 132 | collections=[ 133 | api_collection_new, 134 | api_collection_status, 135 | api_collection_list, 136 | ], 137 | packagers=[ 138 | api_packager_list, api_packager_acl, api_packager_package, 139 | api_packager_stats 140 | ], 141 | packages=[ 142 | api_package_info, api_package_list, 143 | api_package_new, api_package_edit, 144 | api_package_critpath, api_monitor_package, api_koschei_package, 145 | api_package_orphan, api_package_unorphan, 146 | api_package_retire, api_package_unretire, 147 | api_package_request, 148 | ], 149 | acls=[ 150 | api_acl_update, api_acl_reassign, 151 | ], 152 | other=[ 153 | api_version_doc, 154 | ], 155 | admin=[ 156 | api_admin_actions, api_admin_action, api_admin_update_actions 157 | ], 158 | extras=[ 159 | api_extras_bugzilla, api_extras_critpath, 160 | api_extras_notify, api_extras_notify_all, 161 | api_extras_vcs, api_extras_pendingacls, 162 | api_extras_api_groups, api_extras_monitored, 163 | api_extras_koschei, api_extras_retired, 164 | api_extras_pkgrequest, 165 | ] 166 | ) 167 | 168 | 169 | @API.route('/version/') 170 | @API.route('/version') 171 | def api_version(): 172 | ''' 173 | API Version 174 | ----------- 175 | Display the api version information. 176 | 177 | :: 178 | 179 | /api/version 180 | 181 | Accepts GET queries only. 182 | 183 | Sample response: 184 | 185 | :: 186 | 187 | { 188 | "version": "1.6" 189 | } 190 | 191 | ''' 192 | return flask.jsonify({'version': __api_version__}) 193 | --------------------------------------------------------------------------------