Skip to content

Closes #1: Enable branch migrations #264

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: feature
Choose a base branch
from
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
27 changes: 16 additions & 11 deletions docs/models/branch.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,22 @@ The unique, randomly-generated identifier of the PostgreSQL schema which houses

The current status of the branch. This must be one of the following values.

| Status | Description |
|--------------|-------------------------------------------------------------------|
| New | Not yet provisioned in the database |
| Provisioning | A job is running to provision the branch's PostgreSQL schema |
| Ready | The branch is healthy and ready to be synchronized or merged |
| Syncing | A job is running to synchronize changes from main into the branch |
| Merging | A job is running to merge changes from the branch into main |
| Reverting | A job is running to revert previously merged changes in main |
| Merged | Changes from this branch have been successfully merged into main |
| Archived | A merged branch which has been deprovisioned in the database |
| Failed | Provisioning the schema for this branch has failed |
| Status | Description |
|--------------|--------------------------------------------------------------------|
| New | Not yet provisioned in the database |
| Provisioning | A job is running to provision the branch's PostgreSQL schema |
| Ready | The branch is healthy and ready to be synchronized or merged |
| Syncing | A job is running to synchronize changes from main into the branch |
| Migrating | A job is running to apply database migrations to the branch schema |
| Merging | A job is running to merge changes from the branch into main |
| Reverting | A job is running to revert previously merged changes in main |
| Merged | Changes from this branch have been successfully merged into main |
| Archived | A merged branch which has been deprovisioned in the database |
| Failed | Provisioning the schema for this branch has failed |

### Applied Migrations

A list of database migrations which have been applied to the branch since it was created. This may be necessary to keep open branches up to date during NetBox upgrades.

### Last Sync

Expand Down
1 change: 1 addition & 0 deletions netbox_branching/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class AppConfig(PluginConfig):
# Branch action validators
'sync_validators': [],
'merge_validators': [],
'migrate_validators': [],
'revert_validators': [],
'archive_validators': [],
}
Expand Down
8 changes: 8 additions & 0 deletions netbox_branching/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,48 +8,56 @@ class BranchStatusChoices(ChoiceSet):
PROVISIONING = 'provisioning'
READY = 'ready'
SYNCING = 'syncing'
MIGRATING = 'migrating'
MERGING = 'merging'
REVERTING = 'reverting'
MERGED = 'merged'
ARCHIVED = 'archived'
PENDING_MIGRATIONS = 'pending-migrations'
FAILED = 'failed'

CHOICES = (
(NEW, _('New'), 'cyan'),
(PROVISIONING, _('Provisioning'), 'orange'),
(READY, _('Ready'), 'green'),
(SYNCING, _('Syncing'), 'orange'),
(MIGRATING, _('Migrating'), 'orange'),
(MERGING, _('Merging'), 'orange'),
(REVERTING, _('Reverting'), 'orange'),
(MERGED, _('Merged'), 'blue'),
(ARCHIVED, _('Archived'), 'gray'),
(PENDING_MIGRATIONS, _('Pending Migrations'), 'red'),
(FAILED, _('Failed'), 'red'),
)

TRANSITIONAL = (
PROVISIONING,
SYNCING,
MIGRATING,
MERGING,
REVERTING,
)

WORKING = (
NEW,
READY,
PENDING_MIGRATIONS,
*TRANSITIONAL,
)


class BranchEventTypeChoices(ChoiceSet):
PROVISIONED = 'provisioned'
SYNCED = 'synced'
MIGRATED = 'migrated'
MERGED = 'merged'
REVERTED = 'reverted'
ARCHIVED = 'archived'

CHOICES = (
(PROVISIONED, _('Provisioned'), 'green'),
(SYNCED, _('Synced'), 'cyan'),
(MIGRATED, _('Migrated'), 'purple'),
(MERGED, _('Merged'), 'blue'),
(REVERTED, _('Reverted'), 'orange'),
(ARCHIVED, _('Archived'), 'gray'),
Expand Down
2 changes: 2 additions & 0 deletions netbox_branching/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
BRANCH_ACTIONS = (
'sync',
'merge',
'migrate',
'revert',
'archive',
)
Expand All @@ -20,6 +21,7 @@
INCLUDE_MODELS = (
'dcim.cablepath',
'extras.cachedvalue',
'tenancy.contactgroupmembership', # Fix for NetBox v4.3.0
)

# Models for which branching support is explicitly disabled
Expand Down
17 changes: 16 additions & 1 deletion netbox_branching/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class BranchAwareRouter:
A Django database router that returns the appropriate connection/schema for
the active branch (if any).
"""
connection_prefix = 'schema_'

def _get_db(self, model, **hints):
# Warn & exit if branching support has not yet been initialized
if 'branching' not in registry['model_features']:
Expand All @@ -28,7 +30,7 @@ def _get_db(self, model, **hints):

# Return the schema for the active branch (if any)
if branch := active_branch.get():
return f'schema_{branch.schema_name}'
return f'{self.connection_prefix}{branch.schema_name}'

def db_for_read(self, model, **hints):
return self._get_db(model, **hints)
Expand All @@ -39,3 +41,16 @@ def db_for_write(self, model, **hints):
def allow_relation(self, obj1, obj2, **hints):
# Permit relations from the branch schema to the main schema
return True

def allow_migrate(self, db, app_label, model_name=None, **hints):
# This router has no opinion on non-branch connections
if not db.startswith(self.connection_prefix):
return

# Disallow migrations for models from the plugin itself within a branch
if app_label == 'netbox_branching':
return False

# Disallow migrations for models which don't support branching
if model_name and model_name not in registry['model_features']['branching'].get(app_label, []):
return False
11 changes: 11 additions & 0 deletions netbox_branching/forms/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
__all__ = (
'BranchActionForm',
'ConfirmationForm',
'MigrateBranchForm',
)


Expand Down Expand Up @@ -47,3 +48,13 @@ class ConfirmationForm(forms.Form):
required=True,
label=_('Confirm')
)


class MigrateBranchForm(forms.Form):
confirm = forms.BooleanField(
required=True,
label=_('Confirm migrations'),
help_text=_(
'All migrations will be applied in order. <strong>Migrations cannot be reversed once applied.</strong>'
)
)
22 changes: 22 additions & 0 deletions netbox_branching/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

__all__ = (
'MergeBranchJob',
'MigrateBranchJob',
'ProvisionBranchJob',
'RevertBranchJob',
'SyncBranchJob',
Expand Down Expand Up @@ -131,3 +132,24 @@ def run(self, commit=True, *args, **kwargs):
branch.revert(user=self.job.user, commit=commit)
except AbortTransaction:
logger.info("Dry run completed; rolling back changes")


class MigrateBranchJob(JobRunner):
"""
Apply any outstanding database migrations from the main schema to the Branch.
"""
class Meta:
name = 'Migrate branch'

def run(self, *args, **kwargs):
# Initialize logging
logger = logging.getLogger('netbox_branching.branch.migrate')
logger.setLevel(logging.DEBUG)
logger.addHandler(ListHandler(queue=get_job_log(self.job)))

# Migrate the Branch
try:
branch = self.job.object
branch.migrate(user=self.job.user)
except AbortTransaction:
logger.info("Dry run completed; rolling back changes")
22 changes: 22 additions & 0 deletions netbox_branching/migrations/0005_branch_applied_migrations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import django.contrib.postgres.fields
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('netbox_branching', '0004_copy_migrations'),
]

operations = [
migrations.AddField(
model_name='branch',
name='applied_migrations',
field=django.contrib.postgres.fields.ArrayField(
base_field=models.CharField(max_length=200),
blank=True,
default=list,
size=None
),
),
]
Loading