diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..90af9bc --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["local>fedora-infra/shared:renovate-config"] +} diff --git a/.github/workflows/label-when-deployed.yml b/.github/workflows/label-when-deployed.yml new file mode 100644 index 0000000..b72ac1b --- /dev/null +++ b/.github/workflows/label-when-deployed.yml @@ -0,0 +1,27 @@ +name: Apply labels when deployed + +on: + push: + branches: + - staging + - stable + - main + +jobs: + label: + name: Apply labels + runs-on: ubuntu-latest + + steps: + - name: Staging deployment + uses: fedora-infra/label-when-in-branch@v1 + with: + token: ${{'{{'}} secrets.GITHUB_TOKEN {{'}}'}} + branch: staging + label: deployed:staging + - name: Production deployment + uses: fedora-infra/label-when-in-branch@v1 + with: + token: ${{'{{'}} secrets.GITHUB_TOKEN {{'}}'}} + branch: stable + label: deployed:prod diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..e84e17e --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,203 @@ +name: Test & Build + +on: + push: + branches: + - stable + - develop + - main + tags: + pull_request: + branches: + - stable + - develop + - main + +jobs: + + checks: + name: Checks + runs-on: ubuntu-latest + container: fedorapython/fedora-python-tox:latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + dnf install -y pre-commit git libpq-devel krb5-devel + pip install poetry + + - name: Mark the working directory as safe for Git + run: git config --global --add safe.directory $PWD + + - name: Install the project + run: poetry install + + - name: Run pre-commit checks + run: pre-commit run --all-files + + + licenses: + name: Licenses + runs-on: ubuntu-latest + container: fedorapython/fedora-python-tox:latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + dnf install -y libpq-devel krb5-devel + pip install poetry + + - name: Run the licenses check + run: tox -e licenses + + + docs: + name: Documentation + runs-on: ubuntu-latest + container: fedorapython/fedora-python-tox:latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + dnf install -y libpq-devel krb5-devel + pip install poetry>=1.2 + + - name: Build the docs + run: tox -e docs + + # - name: Save the docs + # uses: actions/upload-artifact@v2 + # with: + # name: docs + # path: {{ cookiecutter.pkg_name }}/docs/_build/html + + + unit-tests: + name: Unit tests + runs-on: ubuntu-latest + container: fedorapython/fedora-python-tox:latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + dnf install -y libpq-devel krb5-devel + pip install poetry>=1.2 + + - name: Mark the working directory as safe for Git + run: git config --global --add safe.directory $PWD + + - name: Run the tests + run: tox -e ${{ matrix.pyver }}-unit + + strategy: + matrix: + pyver: + - py38 + - py39 + - py310 + - py311 + + + # https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ + build: + name: Build distribution 📦 + runs-on: ubuntu-latest + needs: + - checks + - licenses + - docs + - unit-tests + outputs: + release-notes: ${{ steps.extract-changelog.outputs.markdown }} + + steps: + + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Install pypa/build + run: python3 -m pip install build --user + + - name: Build a binary wheel and a source tarball + run: python3 -m build + + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + - name: Extract changelog section + id: extract-changelog + uses: sean0x42/markdown-extract@v2 + with: + file: docs/release_notes.md + pattern: 'Version\s+\[[[:word:].-]+\]\(.*\)' + no-print-matched-heading: true + - name: Show the changelog + env: + CHANGELOG: ${{ steps.extract-changelog.outputs.markdown }} + run: echo "$CHANGELOG" + + + publish-to-pypi: + name: Publish to PyPI 🚀 + if: startsWith(github.ref, 'refs/tags/') && !contains(github.ref, 'rc') # only publish to PyPI on final tag pushes + needs: + - build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/webhook-to-fedora-messaging + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + + github-release: + name: Create a GitHub Release 📢 + needs: + - publish-to-pypi + # The "build" dep is redundant but needed to access the changelog + - build + runs-on: ubuntu-latest + permissions: + contents: write # IMPORTANT: mandatory for making GitHub Releases + id-token: write # IMPORTANT: mandatory for sigstore + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + - name: Sign the dists with Sigstore + uses: sigstore/gh-action-sigstore-python@v2.1.1 + with: + inputs: >- + ./dist/*.tar.gz + ./dist/*.whl + + - name: Release + uses: softprops/action-gh-release@v2 + with: + files: dist/* + fail_on_unmatched_files: true + body: ${{ needs.build.outputs.release-notes }} diff --git a/.gitignore b/.gitignore index 68bc17f..e66f6a9 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,7 @@ instance/ # Sphinx documentation docs/_build/ +docs/_source/ # PyBuilder .pybuilder/ @@ -127,6 +128,7 @@ venv/ ENV/ env.bak/ venv.bak/ +.vagrant # Spyder project settings .spyderproject diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..132d4d4 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,37 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks + +repos: + # Generic hooks + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + + # https://black.readthedocs.io/en/stable/integrations/source_version_control.html + - repo: https://github.com/psf/black + rev: 24.4.2 + hooks: + - id: black + + # Ruff + - repo: https://github.com/charliermarsh/ruff-pre-commit + # Ruff version. + rev: v0.5.0 + hooks: + - id: ruff + + - repo: https://github.com/myint/rstcheck + rev: v6.2.1 + hooks: + - id: rstcheck + additional_dependencies: [sphinx, toml, myst-parser] + + # License headers + - repo: https://github.com/fsfe/reuse-tool + rev: v3.0.2 + hooks: + - id: reuse diff --git a/.reuse/dep5 b/.reuse/dep5 new file mode 100644 index 0000000..d52f148 --- /dev/null +++ b/.reuse/dep5 @@ -0,0 +1,17 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Contact: Fedora Infrastructure +Source: https://github.com/fedora-infra/webhook-to-fedora-messaging + +# Sample paragraph, commented out: +# +# Files: src/* +# Copyright: $YEAR $NAME <$CONTACT> +# License: ... + +Files: *.yaml *.yml *.md *.toml .gitignore *.ini *.cfg Vagrantfile .s2i/* .github/* devel/ansible/* docs/* +Copyright: 2024 Contributors to the Fedora Project +License: GPL-3.0-or-later + +Files: poetry.lock +Copyright: None, autogenerated +License: GPL-3.0-or-later diff --git a/Vagrantfile b/Vagrantfile index 80c126f..5cc1943 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -6,21 +6,21 @@ Vagrant.configure(2) do |config| config.hostmanager.manage_host = true config.hostmanager.manage_guest = true - config.vm.define "webhook-to-fedora-messaging" do |webhook-to-fedora-messaging| - webhook-to-fedora-messaging.vm.box_url = "https://download.fedoraproject.org/pub/fedora/linux/releases/40/Cloud/x86_64/images/Fedora-Cloud-Base-Vagrant-libvirt.x86_64-40-1.14.vagrant.libvirt.box" - webhook-to-fedora-messaging.vm.box = "f38-cloud-libvirt" - webhook-to-fedora-messaging.vm.hostname = "webhook-to-fedora-messaging.tinystage.test" + config.vm.define "w2fm" do |w2fm| + w2fm.vm.box_url = "https://download.fedoraproject.org/pub/fedora/linux/releases/40/Cloud/x86_64/images/Fedora-Cloud-Base-Vagrant-libvirt.x86_64-40-1.14.vagrant.libvirt.box" + w2fm.vm.box = "f40-cloud-libvirt" + w2fm.vm.hostname = "w2fm.tinystage.test" - webhook-to-fedora-messaging.vm.synced_folder '.', '/vagrant', disabled: true - webhook-to-fedora-messaging.vm.synced_folder ".", "/home/vagrant/webhook-to-fedora-messaging", type: "sshfs" + w2fm.vm.synced_folder '.', '/vagrant', disabled: true + w2fm.vm.synced_folder ".", "/home/vagrant/webhook-to-fedora-messaging", type: "sshfs" - webhook-to-fedora-messaging.vm.provider :libvirt do |libvirt| + w2fm.vm.provider :libvirt do |libvirt| libvirt.cpus = 2 libvirt.memory = 2048 end - webhook-to-fedora-messaging.vm.provision "ansible" do |ansible| + w2fm.vm.provision "ansible" do |ansible| ansible.playbook = "devel/ansible/playbook.yml" ansible.config_file = "devel/ansible/ansible.cfg" ansible.verbose = true diff --git a/config.toml.example b/config.toml.example index 8d26fb1..3d1bdbd 100644 --- a/config.toml.example +++ b/config.toml.example @@ -1,5 +1,6 @@ [flaskapp] DEBUG = true +SQLALCHEMY_DATABASE_URI = "sqlite:////tmp/w2fm.db" TESTING = true PROPAGATE_EXCEPTIONS = "" SECRET_KEY = "PLEASE-CHANGE-THIS-BEFORE-STARTING" @@ -23,13 +24,6 @@ PREFERRED_URL_SCHEME = "http" TEMPLATES_AUTO_RELOAD = "" MAX_COOKIE_SIZE = "" - [flaskapp.database] - HOST = "localhost" - PORT = 5432 - USERNAME = "root" - PASSWORD = "password" - NAME = "database" - [flaskapp.logsconf] version = 1 disable_existing_loggers = false diff --git a/devel/ansible/playbook.yml b/devel/ansible/playbook.yml index 0b7a31b..449b588 100644 --- a/devel/ansible/playbook.yml +++ b/devel/ansible/playbook.yml @@ -4,16 +4,16 @@ become_method: sudo vars: - name: webhook-to-fedora-messaging + app_name: webhook-to-fedora-messaging pkg_name: webhook_to_fedora_messaging ipa_admin_user: admin ipa_admin_password: password krb_realm: TINYSTAGE.TEST + cert_owner: vagrant roles: - core - # If you need Tinystage: - # - ipa-client - # If you need a TLS cert from Tinystage: - # - cert + - ipa-client + - cert + - gss-proxy - dev diff --git a/devel/ansible/roles/dev/files/config.toml b/devel/ansible/roles/dev/files/config.toml new file mode 100644 index 0000000..bf00530 --- /dev/null +++ b/devel/ansible/roles/dev/files/config.toml @@ -0,0 +1,33 @@ +[flaskapp] +DEBUG = true +SECRET_KEY = "vagrant-env" +SQLALCHEMY_DATABASE_URI = "sqlite:////home/vagrant/w2fm.db" + + [flaskapp.logsconf] + version = 1 + disable_existing_loggers = false + + [flaskapp.logsconf.handlers] + + [flaskapp.logsconf.handlers.wsgi] + class = "logging.StreamHandler" + stream = "ext://flask.logging.wsgi_errors_stream" + level = "INFO" + formatter = "default" + + [flaskapp.logsconf.formatters] + + [flaskapp.logsconf.formatters.default] + format = "[W2FM] %(asctime)s - %(name)s - %(levelname)s - %(message)s" + datefmt = "[%Y-%m-%d %I:%M:%S %z]" + + [flaskapp.logsconf.root] + handlers = ["wsgi"] + level = "INFO" + + [flaskapp.logsconf.loggers] + + [flaskapp.logsconf.loggers.werkzeug] + handlers = ["wsgi"] + level = "INFO" + propagate = false diff --git a/devel/ansible/roles/dev/files/w2fm.service b/devel/ansible/roles/dev/files/w2fm.service new file mode 100644 index 0000000..fcc22d7 --- /dev/null +++ b/devel/ansible/roles/dev/files/w2fm.service @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: Contributors to the Fedora Project +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[Unit] +Description=fmn-api +After=network-online.target +Wants=network-online.target + +[Service] +AmbientCapabilities = CAP_NET_BIND_SERVICE +User=vagrant +Environment=GSS_USE_PROXY=yes +Environment=REQUESTS_CA_BUNDLE=/etc/pki/tls/certs/ca-bundle.crt +Environment=W2FM_APPCONFIG=/home/vagrant/config.toml +Environment=PYTHONUNBUFFERED=1 +Environment=FLASK_DEBUG=1 +Environment=FLASK_APP=webhook_to_fedora_messaging.main +WorkingDirectory=/home/vagrant/webhook-to-fedora-messaging +ExecStart=poetry run flask run --reload -p 443 -h "0.0.0.0" --cert=/etc/pki/tls/certs/server.pem --key=/etc/pki/tls/private/server.key + +[Install] +WantedBy=multi-user.target diff --git a/devel/ansible/roles/dev/tasks/main.yml b/devel/ansible/roles/dev/tasks/main.yml index cca7d8a..ef24139 100644 --- a/devel/ansible/roles/dev/tasks/main.yml +++ b/devel/ansible/roles/dev/tasks/main.yml @@ -8,6 +8,9 @@ - krb5-devel - libpq-devel - gcc + - sqlite3 + - git + - vim state: present - name: install python deps with poetry @@ -15,12 +18,42 @@ become: true become_user: vagrant args: - chdir: /home/vagrant/{{ name }}/ + chdir: /home/vagrant/webhook-to-fedora-messaging -- name: compile the translations - shell: - cmd: poetry run pybabel compile -d /home/vagrant/{{ name }}/{{ pkg_name }}/translations - # "removes" == "only if the file exists" - removes: /home/vagrant/{{ name }}/{{ pkg_name }}/translations/messages.pot - become: true +- name: copy the config files + copy: + src: config.toml + dest: /home/vagrant/config.toml + mode: 0644 + owner: vagrant + group: vagrant + loop: + - tahrir.cfg + +- name: Create or update the database + command: poetry run flask -A webhook_to_fedora_messaging.main db sync + environment: + W2FM_APPCONFIG: /home/vagrant/config.toml + FLASK_DEBUG: 1 + become: yes become_user: vagrant + args: + chdir: /home/vagrant/webhook-to-fedora-messaging + creates: /home/vagrant/w2fm.db + +- name: Install the systemd unit files + copy: + src: "{{ item }}" + dest: /etc/systemd/system/{{ item }} + mode: 0644 + loop: + - w2fm.service + +- name: Enable and start services using systemd + systemd: + name: "{{ item }}" + daemon_reload: yes + enabled: yes + state: started + loop: + - w2fm.service diff --git a/devel/ansible/roles/gss-proxy/defaults/main.yml b/devel/ansible/roles/gss-proxy/defaults/main.yml new file mode 100644 index 0000000..d42dbbc --- /dev/null +++ b/devel/ansible/roles/gss-proxy/defaults/main.yml @@ -0,0 +1,5 @@ +krb_service: HTTP +ipa_admin_user: admin +ipa_admin_password: password +krb_master_password: "{{ ipa_admin_password }}" +krb_realm: TINYSTAGE.TEST diff --git a/devel/ansible/roles/gss-proxy/tasks/main.yml b/devel/ansible/roles/gss-proxy/tasks/main.yml new file mode 100644 index 0000000..accb5d8 --- /dev/null +++ b/devel/ansible/roles/gss-proxy/tasks/main.yml @@ -0,0 +1,27 @@ +--- +- name: Install RPM packages + dnf: + name: + - gssproxy + state: present + +- name: Get the keytab + import_role: + name: ipa-keytab + vars: + keytab_directory: /var/lib/gssproxy + +- name: Copy gssproxy conf + template: + src: gssproxy.conf + dest: /etc/gssproxy/98-{{ krb_service | lower}}.conf + mode: 0644 + owner: root + group: root + +- name: Enable and restart GSSProxy + systemd: + state: restarted + name: gssproxy + enabled: yes + daemon_reload: yes diff --git a/devel/ansible/roles/gss-proxy/templates/gssproxy.conf b/devel/ansible/roles/gss-proxy/templates/gssproxy.conf new file mode 100644 index 0000000..2c4a307 --- /dev/null +++ b/devel/ansible/roles/gss-proxy/templates/gssproxy.conf @@ -0,0 +1,12 @@ +# +# /etc/gssproxy/99-{{ krb_service }}.conf +# + +[service/{{ krb_service | lower }}] + mechs = krb5 + cred_store = keytab:/var/lib/gssproxy/{{ krb_service }}.keytab + cred_store = client_keytab:/var/lib/gssproxy/{{ krb_service }}.keytab + allow_constrained_delegation = true + allow_client_ccache_sync = true + cred_usage = both + euid = vagrant diff --git a/devel/ansible/roles/ipa-keytab/defaults/main.yml b/devel/ansible/roles/ipa-keytab/defaults/main.yml new file mode 100644 index 0000000..00273d6 --- /dev/null +++ b/devel/ansible/roles/ipa-keytab/defaults/main.yml @@ -0,0 +1,9 @@ +krb_service: HTTP +krb_host_fqdn: "{{ ansible_fqdn }}" +keytab_directory: /etc +keytab_path: "{{ keytab_directory }}/{{ krb_service }}.keytab" +keytab_owner: root +keytab_group: root +ipa_admin_user: admin +ipa_admin_password: password +krb_realm: "{{ ansible_domain | upper }}" diff --git a/devel/ansible/roles/ipa-keytab/tasks/main.yml b/devel/ansible/roles/ipa-keytab/tasks/main.yml new file mode 100644 index 0000000..2cc255d --- /dev/null +++ b/devel/ansible/roles/ipa-keytab/tasks/main.yml @@ -0,0 +1,34 @@ +--- +- name: Install RPM packages + dnf: + name: + - krb5-workstation + state: present + +- name: kinit + shell: echo "{{ ipa_admin_password }}" | kinit {{ ipa_admin_user }}@{{ krb_realm }} + +- name: Create the service in IPA + command: ipa service-add --force {{ krb_service | upper }}/{{ krb_host_fqdn }} + register: service_add_result + changed_when: "'Added service' in service_add_result.stdout" + failed_when: "not ('Added service' in service_add_result.stdout or 'already exists' in service_add_result.stderr)" + +- name: Allow the host to manage the virtual service + shell: ipa service-add-host --hosts={{ ansible_fqdn }} {{ krb_service | upper }}/{{ krb_host_fqdn }} + when: krb_host_fqdn != ansible_fqdn + register: result + changed_when: '"Number of members added 1" in result.stdout' + failed_when: '(ansible_fqdn + ": This entry is already a member") not in result.stdout and result.rc != 0' + +- name: Get service keytab + shell: ipa-getkeytab -p {{ krb_service | upper }}/{{ krb_host_fqdn }}@{{ krb_realm }} -k {{ keytab_path }} + args: + creates: "{{ keytab_path }}" + +- name: Set the correct permissions on keytab + file: + path: "{{ keytab_path }}" + owner: "{{ keytab_owner }}" + group: "{{ keytab_group }}" + mode: 0640 diff --git a/pyproject.toml b/pyproject.toml index 71d7496..4614b47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ authors = [ "Fedora Infrastructure " ] keywords = [ - "fedora", + "fedora", "amqp" ] diff --git a/tox.ini b/tox.ini index a6c5342..2309801 100644 --- a/tox.ini +++ b/tox.ini @@ -1,21 +1,17 @@ [tox] -envlist = checks,licenses,docs,{py36,py37,py38,py39,py310}-unit-sqla{1,2} +envlist = checks,licenses,docs,{py39,py310,py311,py312}-unit isolated_build = true [testenv] passenv = HOME sitepackages = false skip_install = true +set_env = + W2FM_APPCONFIG=config.toml.example allowlist_externals = poetry commands_pre = poetry install --all-extras - - sqla1: poetry run pip install sqlalchemy<2.0.0 - sqla2: poetry run pip install sqlalchemy>=2.0.0 -set_env = - SQLALCHEMY_WARN_20 = 1 - commands = unit: poetry run pytest -vv --cov --cov-report=html --cov-report=xml --cov-report=term-missing tests {posargs} diff --git a/webhook_to_fedora_messaging/config/__init__.py b/webhook_to_fedora_messaging/config/__init__.py index 2eaa335..7713cf3 100644 --- a/webhook_to_fedora_messaging/config/__init__.py +++ b/webhook_to_fedora_messaging/config/__init__.py @@ -9,7 +9,7 @@ from webhook_to_fedora_messaging.exceptions import ConfigError -def get_config(): +def get_config() -> dict: path = environ["W2FM_APPCONFIG"] try: with open(path, "rb") as file: diff --git a/webhook_to_fedora_messaging/database.py b/webhook_to_fedora_messaging/database.py index e827500..1351dac 100644 --- a/webhook_to_fedora_messaging/database.py +++ b/webhook_to_fedora_messaging/database.py @@ -8,20 +8,7 @@ Import the functions we will use in the main code and in migrations. """ -from sqlalchemy_helpers import ( # noqa: F401 - Base, - DatabaseManager, - exists_in_db, - get_or_create, - is_sqlite, -) +from sqlalchemy_helpers import Base, get_or_create, update_or_create, is_sqlite, exists_in_db # noqa: F401 +from sqlalchemy_helpers.flask_ext import DatabaseExtension, get_or_404, first_or_404 # noqa: F401 -from webhook_to_fedora_messaging.config import get_config - - -db_config = get_config().database.model_dump() -db = DatabaseManager( - str(db_config["sqlalchemy"]["url"]), - str(db_config["alembic"]["migrations_path"]), - engine_args=db_config["sqlalchemy"], -) +db = DatabaseExtension() diff --git a/webhook_to_fedora_messaging/exceptions.py b/webhook_to_fedora_messaging/exceptions.py index e1f393a..344bbfc 100644 --- a/webhook_to_fedora_messaging/exceptions.py +++ b/webhook_to_fedora_messaging/exceptions.py @@ -2,8 +2,6 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -import logging - class ConfigError(Exception): def __init__(self, text): diff --git a/webhook_to_fedora_messaging/main.py b/webhook_to_fedora_messaging/main.py index 28e6140..02ab300 100644 --- a/webhook_to_fedora_messaging/main.py +++ b/webhook_to_fedora_messaging/main.py @@ -7,17 +7,14 @@ custom configuration file will be inherently taken from the default values """ -import sys from flask import Flask - +from webhook_to_fedora_messaging.database import db from .config import get_config from .config.defaults import LOGGER_CONFIG - from logging.config import dictConfig from .endpoints.user import user_endpoint from webhook_to_fedora_messaging.exceptions import ConfigError - import logging @@ -34,13 +31,13 @@ def create_app(): # Then load the variables up from the custom configuration file try: - confdata = get_config() + confdata = get_config() except ConfigError as expt: logging.error(f"Exiting - Reason - {str(expt)}") + raise - main.config.from_mapping( - confdata - ) + main.config.from_mapping(confdata) + db.init_app(main) dictConfig(confdata["logsconf"]) main.register_blueprint(user_endpoint) diff --git a/webhook_to_fedora_messaging/migrations/env.py b/webhook_to_fedora_messaging/migrations/env.py index ab902a6..9ede731 100644 --- a/webhook_to_fedora_messaging/migrations/env.py +++ b/webhook_to_fedora_messaging/migrations/env.py @@ -7,8 +7,9 @@ from alembic import context from sqlalchemy import engine_from_config, pool -from webhook_to_fedora_messaging.config import get_config +from webhook_to_fedora_messaging.main import create_app from webhook_to_fedora_messaging.database import Base +from sqlalchemy_helpers.flask_ext import get_url_from_app # this is the Alembic Config object, which provides @@ -29,6 +30,9 @@ # my_important_option = alembic_config.get_main_option("my_important_option") # ... etc. +url = str(get_url_from_app(create_app)) +alembic_config.set_main_option("sqlalchemy.url", url) + def run_migrations_offline() -> None: """Run migrations in 'offline' mode. @@ -42,7 +46,6 @@ def run_migrations_offline() -> None: script output. """ - url = str(get_config().database.sqlalchemy.url) context.configure( url=url, target_metadata=target_metadata, @@ -73,8 +76,8 @@ def process_revision_directives(context, revision, directives): logger.info("No changes in schema detected.") connectable = engine_from_config( - get_config().database.sqlalchemy.model_dump(), - prefix="", + alembic_config.get_section(alembic_config.config_ini_section), + prefix="sqlalchemy.", poolclass=pool.NullPool, ) diff --git a/webhook_to_fedora_messaging/models/__init__.py b/webhook_to_fedora_messaging/models/__init__.py index 4d91f8a..637448d 100644 --- a/webhook_to_fedora_messaging/models/__init__.py +++ b/webhook_to_fedora_messaging/models/__init__.py @@ -1,16 +1,3 @@ # SPDX-FileCopyrightText: Contributors to the Fedora Project # # SPDX-License-Identifier: GPL-3.0-or-later - -from sqlalchemy import Column, Integer, Unicode - -from webhook_to_fedora_messaging.database import Base - - -class User(Base): - __tablename__ = "users" - - id = Column("id", Integer, primary_key=True) - name = Column(Unicode(254), index=True, unique=True, nullable=False) - full_name = Column(Unicode(254), nullable=False) - timezone = Column(Unicode(127), nullable=True) diff --git a/webhook_to_fedora_messaging/models/apikey.py b/webhook_to_fedora_messaging/models/apikey.py new file mode 100644 index 0000000..6a86524 --- /dev/null +++ b/webhook_to_fedora_messaging/models/apikey.py @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: Contributors to the Fedora Project +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from sqlalchemy import Column, Integer, UnicodeText, Boolean, ForeignKey, DateTime +from webhook_to_fedora_messaging.models.util import UUIDCreatableMixin, CreatableMixin + +from webhook_to_fedora_messaging.database import Base + +from uuid import uuid4 + + +class APIKey(Base, UUIDCreatableMixin, CreatableMixin): + __tablename__ = "apikeys" + + id = Column(Integer, primary_key=True, nullable=False) + user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), unique=False, nullable=False) + name = Column(UnicodeText, nullable=False) + token = Column(UnicodeText, unique=True, nullable=False, default=uuid4().hex) + expiry_date = Column(DateTime, nullable=True) + disabled = Column(Boolean, nullable=False, default=False) diff --git a/webhook_to_fedora_messaging/models/service.py b/webhook_to_fedora_messaging/models/service.py new file mode 100644 index 0000000..6297e5b --- /dev/null +++ b/webhook_to_fedora_messaging/models/service.py @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: Contributors to the Fedora Project +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from sqlalchemy import Column, Integer, UnicodeText, Boolean, ForeignKey +from webhook_to_fedora_messaging.models.util import UUIDCreatableMixin, CreatableMixin + +from webhook_to_fedora_messaging.database import Base + + +class Service(Base, UUIDCreatableMixin, CreatableMixin): + __tablename__ = "services" + + id = Column(Integer, primary_key=True, nullable=False) + user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), unique=False, nullable=False) + name = Column(UnicodeText, nullable=False) + type = Column(UnicodeText, nullable=False) + desc = Column(UnicodeText, nullable=False) + disabled = Column(Boolean, nullable=False, default=False) diff --git a/webhook_to_fedora_messaging/models/user.py b/webhook_to_fedora_messaging/models/user.py new file mode 100644 index 0000000..a3fc400 --- /dev/null +++ b/webhook_to_fedora_messaging/models/user.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: Contributors to the Fedora Project +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from sqlalchemy import Column, Integer, UnicodeText, Boolean +from webhook_to_fedora_messaging.models.util import UUIDCreatableMixin, CreatableMixin + +from webhook_to_fedora_messaging.database import Base + + +class User(Base, UUIDCreatableMixin, CreatableMixin): + __tablename__ = "users" + + id = Column(Integer, primary_key=True, nullable=False) + username = Column(UnicodeText, unique=True, nullable=False) + is_admin = Column(Boolean, nullable=False, default=False) diff --git a/webhook_to_fedora_messaging/models/util.py b/webhook_to_fedora_messaging/models/util.py new file mode 100644 index 0000000..95066df --- /dev/null +++ b/webhook_to_fedora_messaging/models/util.py @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: Contributors to the Fedora Project +# +# SPDX-License-Identifier: GPL-3.0-or-later + + +from sqlalchemy import Column, UnicodeText +from sqlalchemy.types import DateTime as SQLDateTime +from datetime import datetime, UTC +from functools import partial + +from uuid import uuid4 + + +class UUIDCreatableMixin: + """ + An SQLAlchemy mixin to automatically generate a custom 8-digit UUID string + """ + + uuid = Column("uuid", UnicodeText, unique=True, nullable=False, default=uuid4().hex[0:8]) + + +class CreatableMixin: + """ + An SQLAlchemy mixin to store the time when an entity was created + """ + + creation_date = Column("creation_date", SQLDateTime, nullable=False, default=partial(datetime.now, tz=UTC))