Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
config.py
instance_config_override.py
run.py
migrations/

# PyPi
.pypirc
Expand Down
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ All notable changes to ExaFS will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.2.2] - 2026-02-19

### Changed
- **Database migrations now tracked in git** — `migrations/` removed from `.gitignore`
- Replaced `db-init.py` with migration-based initialization (`flask db upgrade`)
- Removed one-time `/admin/set-org-if-zero` endpoint, replaced with standalone `scripts/migrate_v0x_to_v1.py`
- Fixed Flask-SQLAlchemy deprecation warning in Alembic `env.py`
- Template URLs changed to use `url_for` helper, removed unused `rule.html` template
- **`db-init.py` and `create-admin.py` moved to `scripts/`** — all setup scripts now live under `scripts/`

### Added
- Idempotent baseline migration (`001_baseline`) that brings any ExaFS database (from v0.4+ to current) to the v1.2.2 schema
- Optional `scripts/migrate_v0x_to_v1.py` helper for v0.x to v1.0+ data migration (org_id backfill)
- `scripts/create-admin.py` — interactive script to create the first admin user and organization (replaces manual SQL inserts)
- `scripts/db-init.py --reset` flag for development database reset
- Migration test suite (`tests/test_migration.py`) — 46 tests covering fresh install, idempotency, upgrade from v0.4/v0.8/v1.0 schemas, and real 2019 production backup upgrade
- `PYTHONPATH` set in Docker dev container for easier development

## [1.2.1] - 2026-01-30

### Fixed
Expand Down Expand Up @@ -286,6 +304,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Route Distinguisher for VRF now supported
- See config example and update your `config.py`

[1.2.2]: https://github.com/CESNET/exafs/compare/v1.2.1...v1.2.2
[1.2.1]: https://github.com/CESNET/exafs/compare/v1.2.0...v1.2.1
[1.2.0]: https://github.com/CESNET/exafs/compare/v1.1.9...v1.2.0
[1.1.9]: https://github.com/CESNET/exafs/compare/v1.1.8...v1.1.9
[1.1.8]: https://github.com/CESNET/exafs/compare/v1.1.7...v1.1.8
Expand Down
26 changes: 20 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,10 @@ exafs/
├── config.example.py # Configuration template
├── instance_config_override.example.py # Dashboard override template
├── run.example.py # Application run script template
├── db-init.py # Database initialization script
├── scripts/
│ ├── db-init.py # Database initialization (runs flask db upgrade)
│ ├── create-admin.py # Interactive first admin user setup
│ └── migrate_v0x_to_v1.py # Optional v0.x to v1.0+ migration helper
├── pyproject.toml # Project metadata and dependencies
├── setup.cfg # Setup configuration
├── CHANGELOG.md # Version history
Expand Down Expand Up @@ -283,8 +286,11 @@ cp run.example.py run.py

# Edit config.py with database credentials and settings

# Initialize database
python db-init.py
# Initialize database (runs flask db upgrade)
python scripts/db-init.py

# Create the first admin user and organization
python scripts/create-admin.py

# Run tests
pytest
Expand All @@ -295,15 +301,20 @@ python run.py

### Database Migrations

Migration files are tracked in `migrations/versions/` and committed to git.

```bash
# Create a new migration
# Create a new migration after model changes
flask db migrate -m "Description of changes"

# Apply migrations
flask db upgrade

# Rollback migration
flask db downgrade

# For existing databases adopting migrations for the first time
flask db stamp 001_baseline
```

### Running Tests
Expand Down Expand Up @@ -788,7 +799,10 @@ flask db upgrade # Apply migrations
flake8 . # Lint code

# Database
python db-init.py # Initialize database
python scripts/db-init.py # Initialize database (runs migrations)
python scripts/db-init.py --reset # Drop all tables and recreate (dev only)
python scripts/create-admin.py # Create first admin user interactively
flask db stamp 001_baseline # Mark existing DB as baseline
flask db current # Show current migration
flask db history # Show migration history

Expand All @@ -804,7 +818,7 @@ supervisorctl status # Check status
When working with this codebase:

1. **Always run tests** after making changes: `pytest`
2. **Create migrations** for model changes: `flask db migrate`
2. **Create migrations** for model changes: `flask db migrate` — commit migration files to git
3. **Follow the service layer pattern** - business logic goes in services, not views
4. **Use existing validators** in `flowapp/validators.py` for validation
5. **Check authentication** - most routes need `@auth_required` decorator
Expand Down
39 changes: 0 additions & 39 deletions db-init.py

This file was deleted.

109 changes: 91 additions & 18 deletions docs/DB_MIGRATIONS.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,108 @@
# How to Upgrade the Database
# Database Migrations

## General Guidelines
Migrations can be inconsistent. To avoid issues, we removed migrations from git repostory. To start the migration on your server, it is recomended reset the migration state on the server and run the migration based on the updated database models when switching application versions via Git.
ExaFS uses [Flask-Migrate](https://flask-migrate.readthedocs.io/) (Alembic) for database schema management. Migration files are shipped inside the `flowapp` package (`flowapp/migrations/`) and are found automatically — no `flask db init` is needed.

## New Installation

For a fresh database, run the migrations to create all tables and seed data:

```bash
rm -rf migrations/
flask db upgrade
```

```SQL
DROP TABLE alembic_version;
Or use the init script:

```bash
python scripts/db-init.py
```

## Upgrading Between Versions

When upgrading ExaFS to a new version, apply any new migrations:

```bash
flask db upgrade
```

This will apply only the migrations that haven't been applied yet.

## Existing Installation (One-Time Setup)

If you already have a running ExaFS database from any previous version, the baseline migration is idempotent — it will create missing tables, add missing columns, and skip anything that already exists.

### Deployments that used `flask db init` (self-managed migrations)

Some deployments previously ran `flask db init` to create a local `migrations/` directory and auto-generated migration files. Starting with v1.2.2, migration files are tracked in git and shipped with the project. To switch to the official migrations:

1. **Delete the local migrations directory** created by `flask db init`:
```bash
rm -rf migrations/
```
Migrations are now bundled inside the `flowapp` pip package — no local directory needed.

2. **Clear the old alembic_version** and **stamp the baseline** to register with the official migration track (your schema is already up to date):
```sql
DELETE FROM alembic_version;
```
```bash
flask db stamp 001_baseline
```

3. From now on, just run `flask db upgrade` when updating ExaFS.

### Deployments without any migration tracking

If your database has an `alembic_version` table from a previous migration setup but no local `migrations/` directory, clear it first:

```sql
DELETE FROM alembic_version;
```

Then run the upgrade:

```bash
flask db init
flask db migrate -m "Initial migration based on current DB state"
flask db upgrade
```

## Steps for Upgrading to v1.0.x
Limits for number of rules were introduced. Some database engines (Mariadb 10.x for example) have issue to set Non Null foreigin key to 0 and automatic migrations fail. The solution may be in diferent version (Mariadb 11.x works fine), or to set limits in db manually later.
The baseline migration will inspect your database and bring it up to the current schema without affecting existing data.

To set the limit to 0 for existing organizations run
## Upgrading from v0.x to v1.0+

```SQL
UPDATE organization
SET limit_flowspec4 = 0, limit_flowspec6 = 0, limit_rtbh = 0
WHERE limit_flowspec4 IS NULL OR limit_flowspec6 IS NULL OR limit_rtbh IS NULL;
If you are upgrading from a pre-1.0 version, the baseline migration will add the missing `org_id` columns and organization limit columns automatically. However, existing rules still need to be linked to organizations. An optional helper script is provided for this:

```bash
python scripts/migrate_v0x_to_v1.py
```

In all cases we need later assign rules to organizations. There's an admin endpoint for this:
This script:
1. Sets NULL organization limits to 0
2. Helps assign existing rules to organizations based on users' organizations
3. Reports users with multiple organizations or ambiguous rule ownership that need manual assignment

Feel free to contact jiri.vrany@cesnet.cz if you need help with the migration.

## Creating New Migrations

When you modify a database model, create a new migration:

```bash
flask db migrate -m "Description of changes"
```

`https://yourexafs.url/admin/set-org-if-zero`
Review the generated file in `flowapp/migrations/versions/`, then apply it:

```bash
flask db upgrade
```

Commit the migration file to git so other deployments can apply it.

## Development Reset

To completely reset the database during development:

```bash
python scripts/db-init.py --reset
```

Or you can start with clean database and manually migrate data by SQL dump later. Feel free to contact jiri.vrany@cesnet.cz if you need help with the DB migration to 1.0.x.
This drops all tables and recreates them from scratch. **Do not use in production.**
21 changes: 7 additions & 14 deletions docs/INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,26 +126,19 @@ You can skip this section if you are using a different deployment method, such a

#### Final steps - as deploy user

1. Copy config.example.py to config.py and fill out the DB credetials.
1. Copy config.example.py to config.py and fill out the DB credentials.

2. Create and populate database tables.
2. Create and populate database tables (roles, actions, rule states):
```
cd ~/www
source venv/bin/activate
python db-init.py
python scripts/db-init.py
```
DB-init script inserts default roles, actions, rule states and two organizations (TUL and Cesnet). But no users.

3. Before start, **use your favorite mysql admin tool and insert some users into database**.
The **uuid** of user should be set the **eppn** value provided by Shibboleth.

You can use following MYSQL commands to insert the user, give him role 'admin' and add him to the the organization 'Cesnet'.

3. Create the first admin user and organization using the interactive setup script:
```
insert into user (uuid,email,name) values ('example@cesnet.cz', 'example@cesnet.cz', 'Mr. Example Admin');
insert into user_role (user_id,role_id) values (1, 3);
insert into user_organization (user_id,organization_id) values (1, 2);
```
You can also modify the models.py for your own default values for db-init.
python scripts/create-admin.py
```
The script will prompt you for the admin's UUID (Shibboleth eppn), name, email, phone, and then create or select an organization with its network address range. It assigns the admin role automatically.

The application is installed and should be working now. The next step is to configure ExaBGP and connect it to the ExaAPI application. We also provide simple service called guarda to reload all the rules in case of ExaBGP restart.
2 changes: 1 addition & 1 deletion flowapp/__about__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "1.2.1"
__version__ = "1.2.2"
__title__ = "ExaFS"
__description__ = "Tool for creation, validation, and execution of ExaBGP messages."
__author__ = "CESNET / Jiri Vrany, Petr Adamec, Josef Verich, Jakub Man"
Expand Down
6 changes: 5 additions & 1 deletion flowapp/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
import os

from flask import Flask, redirect, render_template, session, url_for, flash

from flask_sso import SSO
Expand All @@ -13,9 +15,11 @@
from .__about__ import __version__
from .instance_config import InstanceConfig

# Migrations directory lives inside the package so it ships with pip install
_migrations_dir = os.path.join(os.path.dirname(__file__), "migrations")

db = SQLAlchemy()
migrate = Migrate()
migrate = Migrate(directory=_migrations_dir)
csrf = CSRFProtect()
ext = SSO()
sess = Session()
Expand Down
1 change: 1 addition & 0 deletions flowapp/migrations/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Single-database configuration for Flask.
50 changes: 50 additions & 0 deletions flowapp/migrations/alembic.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# A generic, single database configuration.

[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s

# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false


# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic,flask_migrate

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = WARN
handlers = console
qualname =

[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[logger_flask_migrate]
level = INFO
handlers =
qualname = flask_migrate

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
Loading