From a99dc37dccbf2b02d99ca50e42b508b7572757c9 Mon Sep 17 00:00:00 2001 From: Randy Barlow Date: Dec 16 2016 19:52:51 +0000 Subject: Create a new Flask app that answers the GET /v2/ Docker API call. This commit creates all the boilerplate code to make a new Flask app, a Vagrant development environment, developer instructions, tests, and a handler that responds to the Docker v2 GET /v2/ API endpoint. fixes #2 --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f2c880 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*__pycache__ +.coverage +.dnf-cache +.eggs +.vagrant +coverage.xml +devel/ansible/playbook.retry +fegistry.egg-info/ +nosetests.xml +Vagrantfile diff --git a/README.md b/README.md index d8942eb..9770f05 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,74 @@ # fegistry -The Fedora registry endpoint. \ No newline at end of file +The Fedora registry endpoint. + + +## Development + +### Contribution guidelines + +Before you submit a pull request to fegistry, please ensure that it meets these criteria: + +* All tests must pass. +* Code should have 100% test coverage. This one is particularly important, as we don't want to + deploy any broken code into production. +* Functions, methods, and classes should have docblocks that explain what the code block is, and + describing any parameters it accepts and what it returns (if anything). +* Code should follow [PEP-8](https://www.python.org/dev/peps/pep-0008). You can use the + ```flake8``` utility to automatically check your code. There is a + ```fegistry.tests.test_style.TestStyle.test_code_with_flake8``` test, which enforced PEP-8 on the + codebase. + + +### Development environment + +[Vagrant](https://www.vagrantup.com) allows contributors to get quickly up and running with a +development environment by automatically configuring a virtual machine. Before you get +started, ensure that your host machine has virtualization extensions enabled in its BIOS so the +guest doesn't go slower than molasses. To get started, simply +use these commands: + +``` + $ sudo dnf install ansible libvirt vagrant-libvirt vagrant-sshfs + $ sudo systemctl enable libvirtd + $ sudo systemctl start libvirtd + $ cp Vagrantfile.example Vagrantfile + # Make sure your fegistry checkout is your shell's cwd + $ vagrant up +``` + +fegistry is now running in the guest, and port 5000 has been forwarded on your host: + +``` + $ curl -i http://localhost:5000/v2/ + HTTP/1.0 200 OK + Content-Type: application/json + Content-Length: 2 + Docker-Distribution-API-Version: registry/2.0 + Server: Werkzeug/0.11.10 Python/3.5.2 + Date: Thu, 15 Dec 2016 22:04:33 GMT + + {} +``` + +You can use ```vagrant ssh``` to ssh into the guest if you like. Inside the guest environment, you +will find the code shared at ```/home/vagrant/fegistry```. There are some convenient bash aliases: + +```flog```: Display the development server's log. You can pass a ```-f``` flag to continuously + display the log. +```frestart```: Restart the development server. The development server does automatically pick up + code changes, so you shouldn't need this much. +```fstart```: Start the development server. +```fstop```: Stop the development server. +```ftest```: Run the test suite. + + +```vagrant ssh``` also accepts a ```-c``` flag that allows you to run a command in the guest. For +example, you can run the tests wtih ```vagrant ssh -c ftest```. + +When you are done with your Vagrant guest, you can destroy it permanently by running this command on +the host: + +``` + $ vagrant destroy +``` diff --git a/Vagrantfile.example b/Vagrantfile.example new file mode 100644 index 0000000..4f79c0b --- /dev/null +++ b/Vagrantfile.example @@ -0,0 +1,61 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# To use Vagrant to develop fegistry, on your host: +# git clone https://pagure.io/fegistry.git +# cd fegistry +# cp Vagrantfile.example Vagrantfile +# vagrant up +# vagrant ssh # Now you're in the fegistry development environment, have fun! + +Vagrant.configure(2) do |config| + config.vm.box_url = "https://download.fedoraproject.org/pub/fedora/linux/releases/25/CloudImages"\ + "/x86_64/images/Fedora-Cloud-Base-Vagrant-25-1.3.x86_64.vagrant-libvirt.box" + config.vm.box = "f25-cloud-libvirt" + config.vm.network "forwarded_port", guest: 5000, host: 5000 + + # This is an optional plugin that, if installed, updates the host's /etc/hosts + # file with the hostname of the guest VM. In Fedora it is packaged as + # ``vagrant-hostmanager`` + if Vagrant.has_plugin?("vagrant-hostmanager") + config.hostmanager.enabled = true + config.hostmanager.manage_host = true + end + + config.vm.synced_folder ".", "/vagrant", disabled: true + config.vm.synced_folder ".", "/home/vagrant/fegistry", type: "sshfs" + + # To cache update packages (which is helpful if frequently doing `vagrant destroy && vagrant up`) + # you can create a local directory and share it to the guest's DNF cache. Uncomment the lines + # below to create and use a dnf cache directory + # + # Dir.mkdir('.dnf-cache') unless File.exists?('.dnf-cache') + # config.vm.synced_folder ".dnf-cache", "/var/cache/dnf", type: "sshfs", sshfs_opts_append: "-o nonempty" + + # Ansible needs the guest to have these + config.vm.provision "shell", inline: "sudo dnf install -y libselinux-python python2-dnf" + config.vm.provision "ansible" do |ansible| + ansible.playbook = "devel/ansible/playbook.yml" + end + + # Create a fegistry guest + config.vm.define "fegistry" do |fegistry| + fegistry.vm.host_name = "fegistry.example.com" + + fegistry.vm.provider :libvirt do |domain| + # Season to taste + domain.cpus = 4 + domain.graphics_type = "spice" + domain.memory = 512 + domain.video_type = "qxl" + + # Uncomment the following line if you would like to enable libvirt's unsafe cache + # mode. It is called unsafe for a reason, as it causes the virtual host to ignore all + # fsync() calls from the guest. Only do this if you are comfortable with the possibility of + # your development guest becoming corrupted (in which case you should only need to do a + # vagrant destroy and vagrant up to get a new one). + # + # domain.volume_cache = "unsafe" + end + end +end diff --git a/devel/ansible/playbook.yml b/devel/ansible/playbook.yml new file mode 100644 index 0000000..6de4d31 --- /dev/null +++ b/devel/ansible/playbook.yml @@ -0,0 +1,8 @@ +--- +- hosts: all + become: true + become_method: sudo + vars: + roles: + - core + - dev diff --git a/devel/ansible/roles/core/tasks/main.yml b/devel/ansible/roles/core/tasks/main.yml new file mode 100644 index 0000000..b119eae --- /dev/null +++ b/devel/ansible/roles/core/tasks/main.yml @@ -0,0 +1,12 @@ +--- +- name: Install basic packages + dnf: + name: "{{ item }}" + state: present + with_items: + - bash-completion + - dstat + - fedora-easy-karma + - htop + - tmux + - tree diff --git a/devel/ansible/roles/dev/files/.bashrc b/devel/ansible/roles/dev/files/.bashrc new file mode 100644 index 0000000..36e02f3 --- /dev/null +++ b/devel/ansible/roles/dev/files/.bashrc @@ -0,0 +1,16 @@ +# .bashrc + +# Source global definitions +if [ -f /etc/bashrc ]; then + . /etc/bashrc +fi + +# Uncomment the following line if you don't like systemctl's auto-paging feature: +# export SYSTEMD_PAGER= + +shopt -s expand_aliases +alias flog="sudo journalctl -u fegistry" +alias frestart="sudo systemctl restart fegistry" +alias fstart="sudo systemctl start fegistry" +alias fstop="sudo systemctl stop fegistry" +alias ftest="pushd /home/vagrant/fegistry && python3 setup.py nosetests; popd" diff --git a/devel/ansible/roles/dev/files/fegistry.service b/devel/ansible/roles/dev/files/fegistry.service new file mode 100644 index 0000000..ef34f8b --- /dev/null +++ b/devel/ansible/roles/dev/files/fegistry.service @@ -0,0 +1,13 @@ +[Unit] +Description=fegistry +After=network-online.target +Wants=network-online.target + +[Service] +Environment=FLASK_APP=/home/vagrant/fegistry/fegistry/views.py +Environment=FLASK_DEBUG=1 +User=vagrant +ExecStart=/usr/bin/flask run -h 0.0.0.0 + +[Install] +WantedBy=multi-user.target diff --git a/devel/ansible/roles/dev/tasks/main.yml b/devel/ansible/roles/dev/tasks/main.yml new file mode 100644 index 0000000..5dded3f --- /dev/null +++ b/devel/ansible/roles/dev/tasks/main.yml @@ -0,0 +1,38 @@ +--- +- name: Install dev packages + dnf: + name: "{{ item }}" + state: present + with_items: + - git + - python3-flake8 + - python3-flask + - python3-mock + - python3-nose + - python3-nose-cov + +- name: Install the .bashrc + copy: + src: .bashrc + dest: /home/vagrant/.bashrc + mode: 0644 + owner: vagrant + group: vagrant + +- name: Install fegistry in developer mode + command: python3 setup.py develop + args: + chdir: /home/vagrant/fegistry + creates: /usr/lib/python3.*/site-packages/fegistry.egg-link + +- name: Install the systemd unit + copy: + src: fegistry.service + dest: /etc/systemd/system/fegistry.service + mode: 0644 + +- name: Start and enable the fegistry service + systemd: + name: fegistry + state: started + enabled: yes diff --git a/fegistry/__init__.py b/fegistry/__init__.py new file mode 100644 index 0000000..abc99f0 --- /dev/null +++ b/fegistry/__init__.py @@ -0,0 +1,16 @@ +# Copyright ⓒ 2016 Red Hat, Inc. +# This file is part of fegistry. +# +# fegistry is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# fegistry is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Foobar. If not, see . +__version__ = '0.0.0' diff --git a/fegistry/tests/__init__.py b/fegistry/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/fegistry/tests/__init__.py diff --git a/fegistry/tests/test_style.py b/fegistry/tests/test_style.py new file mode 100644 index 0000000..b4e90d9 --- /dev/null +++ b/fegistry/tests/test_style.py @@ -0,0 +1,36 @@ +# Copyright ⓒ 2016 Red Hat, Inc. +# This file is part of fegistry. +# +# fegistry is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# fegistry is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Foobar. If not, see . +"""This test suite enforces code style automatically.""" + +import os +import subprocess +import unittest + + +REPO_PATH = os.path.abspath( + os.path.dirname(os.path.join(os.path.dirname(__file__), '..', '..', '..'))) + + +class TestStyle(unittest.TestCase): + """This test class contains tests pertaining to code style.""" + def test_code_with_flake8(self): + """Enforce PEP-8 compliance on the codebase. + + This test runs flake8 on the code. + """ + flake8_command = ['python3-flake8', '--max-line-length', '100', REPO_PATH] + + self.assertEqual(subprocess.call(flake8_command), 0) diff --git a/fegistry/tests/test_views.py b/fegistry/tests/test_views.py new file mode 100644 index 0000000..0c9d39a --- /dev/null +++ b/fegistry/tests/test_views.py @@ -0,0 +1,53 @@ +# Copyright ⓒ 2016 Red Hat, Inc. +# This file is part of fegistry. +# +# fegistry is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# fegistry is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Foobar. If not, see . +"""This test suite contains tests on fegistry.views.""" + +import json +import unittest + +import flask + +from fegistry import views + + +class ViewsTestCase(unittest.TestCase): + """A superclass for testing the views module.""" + def setUp(self): + views.app.config['TESTING'] = True + self.app = views.app.test_client() + + +class TestAddDockerHeaders(unittest.TestCase): + """This test class contains tests on the add_docker_headers() function.""" + def test_add_docker_headers(self): + """Assert correct behavior when the status code is 200.""" + response = flask.Response(status="200") + + response = views.add_docker_headers(response) + + self.assertEqual(response.headers['Docker-Distribution-API-Version'], 'registry/2.0') + + +class Testv2(ViewsTestCase): + """This test class tests the v2() function.""" + def test_v2(self): + """Check the /v2/ handler.""" + response = self.app.get('/v2/') + + self.assertEqual(response.status_code, 200) + self.assertEqual(json.loads(response.get_data().decode('utf-8')), {}) + self.assertEqual(response.headers['Docker-Distribution-API-Version'], 'registry/2.0') + self.assertEqual(response.headers['Content-Type'], 'application/json') diff --git a/fegistry/views.py b/fegistry/views.py new file mode 100644 index 0000000..cdd2a72 --- /dev/null +++ b/fegistry/views.py @@ -0,0 +1,36 @@ +# Copyright ⓒ 2016 Red Hat, Inc. +# This file is part of fegistry. +# +# fegistry is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# fegistry is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Foobar. If not, see . +import flask + + +app = flask.Flask(__name__) + + +@app.after_request +def add_docker_headers(response): + """ + Add the necssary Docker headers to every response. + """ + response.headers['Docker-Distribution-API-Version'] = 'registry/2.0' + return response + + +@app.route('/v2/') +def v2(): + """ + Answer the GET /v2/ API call with {}. + """ + return flask.json.jsonify({}) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..f34a41c --- /dev/null +++ b/setup.cfg @@ -0,0 +1,8 @@ +[nosetests] +cover-erase=TRUE +cover-inclusive=TRUE +cover-min-percentage=100 +cover-package=fegistry +cover-xml=TRUE +with-coverage=TRUE +with-xunit=TRUE diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f0f1ad7 --- /dev/null +++ b/setup.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# Copyright ⓒ 2016 Red Hat, Inc. +# This file is part of fegistry. +# +# fegistry is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# fegistry is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Foobar. If not, see . +import os + +from setuptools import setup, find_packages + +import fegistry + + +here = os.path.abspath(os.path.dirname(__file__)) +README = open(os.path.join(here, 'README.md')).read() +VERSION = fegistry.__version__ + +# Possible options are at https://pypi.python.org/pypi?%3Aaction=list_classifiers +CLASSIFIERS = [ + 'Development Status :: 2 - Pre-Alpha', + 'Framework :: Flask', + 'Intended Audience :: Developers', + 'Intended Audience :: System Administrators', + 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python :: 3 :: Only', + 'Topic :: System :: Software Distribution'] +LICENSE = 'GPLv3' +MAINTAINER = 'Fedora Infrastructure Team' +MAINTAINER_EMAIL = 'infrastructure@lists.fedoraproject.org' +PLATFORMS = ['Fedora', 'GNU/Linux'] +URL = 'https://pagure.io/fegistry' + + +setup( + name='fegistry', version=VERSION, description='The Fedora registry endpoint.', + long_description=README, classifiers=CLASSIFIERS, license=LICENSE, maintainer=MAINTAINER, + maintainer_email=MAINTAINER_EMAIL, platforms=PLATFORMS, url=URL, keywords='fedora', + packages=find_packages(exclude=('fegistry.tests', 'fegistry.tests.*')), + include_package_data=True, zip_safe=False, install_requires=['flask'], + tests_require=['flake8', 'mock', 'nose', 'nose-cov'], test_suite="nose.collector")