Skip to content

Closes #90: Implement branch archiving #96

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

Merged
merged 4 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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: 1 addition & 0 deletions docs/models/branch.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ The current status of the branch. This must be one of the following values.
| 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 |

### Last Sync
Expand Down
4 changes: 2 additions & 2 deletions docs/using-branches/reverting-a-branch.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

Once a branch has been merged, it is generally no longer needed, and can no longer be activated. However, occasionally you may find it necessary to undo the changes from a branch (due to an error or an otherwise undesired state). This can be done by _reverting_ the branch. Only merged branches can be reverted.

!!! note
Only branches which have not yet been deleted can be reverted. Once a branch is deleted, reversion is no longer possible.
!!! warning
Only branches which have not yet been archived or deleted can be reverted. Once a branch's schema has been deprovisioned, it can no longer be reverted.

Before reverting a branch, review the changes listed under its "Merged Changes" tab. NetBox will attempt to undo these specific changes when reverting the branch.

Expand Down
2 changes: 2 additions & 0 deletions docs/using-branches/syncing-merging.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ While a branch is being merged, its status will show "merging."
!!! tip
You can check on the status of the merging job under the "Jobs" tab of the branch view.

Once a branch has been merged, it can be [reverted](./reverting-a-branch.md), archived, or deleted. Archiving a branch removes its associated schema from the PostgreSQL database to deallocate space. An archived branch cannot be restored, however the branch record is retained for future reference.

## Dealing with Conflicts

In the event an object has been modified in both your branch _and_ in main in a diverging manner, this will be flagged as a conflict. For example, if both you and another user have modified the description of an interface to two different values in main and in the branch, this represents a conflict.
Expand Down
4 changes: 4 additions & 0 deletions netbox_branching/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class BranchStatusChoices(ChoiceSet):
MERGING = 'merging'
REVERTING = 'reverting'
MERGED = 'merged'
ARCHIVED = 'archived'
FAILED = 'failed'

CHOICES = (
Expand All @@ -21,6 +22,7 @@ class BranchStatusChoices(ChoiceSet):
(MERGING, _('Merging'), 'orange'),
(REVERTING, _('Reverting'), 'orange'),
(MERGED, _('Merged'), 'blue'),
(ARCHIVED, _('Archived'), 'gray'),
(FAILED, _('Failed'), 'red'),
)

Expand All @@ -37,10 +39,12 @@ class BranchEventTypeChoices(ChoiceSet):
SYNCED = 'synced'
MERGED = 'merged'
REVERTED = 'reverted'
ARCHIVED = 'archived'

CHOICES = (
(PROVISIONED, _('Provisioned'), 'green'),
(SYNCED, _('Synced'), 'cyan'),
(MERGED, _('Merged'), 'blue'),
(REVERTED, _('Reverted'), 'orange'),
(ARCHIVED, _('Archived'), 'gray'),
)
8 changes: 8 additions & 0 deletions netbox_branching/forms/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

__all__ = (
'BranchActionForm',
'ConfirmationForm',
)


Expand Down Expand Up @@ -36,3 +37,10 @@ def clean(self):
raise forms.ValidationError(_("All conflicts must be acknowledged in order to merge the branch."))

return self.cleaned_data


class ConfirmationForm(forms.Form):
confirm = forms.BooleanField(
required=True,
label=_('Confirm')
)
14 changes: 13 additions & 1 deletion netbox_branching/models/branches.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ def revert(self, user, commit=True):
# Disconnect the signal receiver
post_save.disconnect(handler, sender=ObjectChange_)

merge.alters_data = True
revert.alters_data = True

def provision(self, user):
"""
Expand Down Expand Up @@ -512,6 +512,18 @@ def provision(self, user):

provision.alters_data = True

def archive(self, user):
"""
Deprovision the Branch and set its status to "archived."
"""
self.deprovision()

# Update the branch's status to "archived"
Branch.objects.filter(pk=self.pk).update(status=BranchStatusChoices.ARCHIVED)
BranchEvent.objects.create(branch=self, user=user, type=BranchEventTypeChoices.ARCHIVED)

archive.alters_data = True

def deprovision(self):
"""
Delete the Branch's schema and all its tables from the database.
Expand Down
4 changes: 3 additions & 1 deletion netbox_branching/template_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ class BranchSelector(PluginTemplateExtension):
def navbar(self):
return self.render('netbox_branching/inc/branch_selector.html', extra_context={
'active_branch': active_branch.get(),
'branches': Branch.objects.exclude(status=BranchStatusChoices.MERGED),
'branches': Branch.objects.exclude(
status__in=[BranchStatusChoices.MERGED, BranchStatusChoices.ARCHIVED]
),
})


Expand Down
9 changes: 9 additions & 0 deletions netbox_branching/templates/netbox_branching/branch.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@
<i class="mdi mdi-arrow-u-left-top"></i> {% trans "Revert" %}
</button>
{% endif %}
{% if perms.netbox_branching.archive_branch %}
<a href="{% url 'plugins:netbox_branching:branch_archive' pk=object.pk %}" class="btn btn-primary">
<i class="mdi mdi-archive-outline"></i> {% trans "Archive" %}
</a>
{% else %}
<button type="button" class="btn btn-primary disabled" aria-disabled="true">
<i class="mdi mdi-archive-outline"></i> {% trans "Archive" %}
</button>
{% endif %}
{% endif %}
{% endblock %}

Expand Down
43 changes: 43 additions & 0 deletions netbox_branching/templates/netbox_branching/branch_archive.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{% extends 'generic/_base.html' %}
{% load form_helpers %}
{% load i18n %}

{% block title %}{% trans "Archive" %} {{ branch }}{% endblock %}

{% block tabs %}
<ul class="nav nav-tabs">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="action-form-tab" data-bs-toggle="tab" data-bs-target="#action-form" type="button" role="tab" aria-controls="merge-form" aria-selected="true">
{% trans "Archive" %}
</button>
</li>
</ul>
{% endblock tabs %}

{% block content %}
{# Form tab #}
<div class="tab-pane show active" id="action-form" role="tabpanel" aria-labelledby="action-form-tab">
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="row">
<div class="col col-6 offset-3">
<div class="alert alert-danger">
<i class="mdi mdi-alert-circle"></i>
{% blocktrans %}
Are you sure you want to archive the branch {{ branch }}? This will permanently deprovision
its database schema, and it will no longer be possible to automatically rever the branch.
{% endblocktrans %}
</div>
{% render_field form.confirm %}
<div class="col offset-3 my-3">
<a href="{{ branch.get_absolute_url }}" class="btn btn-outline-secondary">{% trans "Cancel" %}</a>
<button type="submit" name="_update" class="btn btn-primary">
{% trans "Archive" %}
</button>
</div>
</div>
</div>
</form>
</div>
{# /Form tab #}
{% endblock content %}
50 changes: 47 additions & 3 deletions netbox_branching/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from . import filtersets, forms, tables
from .choices import BranchStatusChoices
from .jobs import MergeBranchJob, RevertBranchJob, SyncBranchJob
from .models import ChangeDiff, Branch
from .models import Branch, ChangeDiff


#
Expand Down Expand Up @@ -186,7 +186,7 @@ def do_action(self, branch, request, form):

def get(self, request, **kwargs):
branch = self.get_object(**kwargs)
form = forms.BranchActionForm(branch)
form = self.form(branch)

return render(request, self.template_name, {
'branch': branch,
Expand All @@ -197,7 +197,7 @@ def get(self, request, **kwargs):

def post(self, request, **kwargs):
branch = self.get_object(**kwargs)
form = forms.BranchActionForm(branch, request.POST)
form = self.form(branch, request.POST)

if branch.status not in self.valid_states:
messages.error(request, _(
Expand Down Expand Up @@ -265,6 +265,50 @@ def do_action(self, branch, request, form):
return redirect(branch.get_absolute_url())


@register_model_view(Branch, 'archive')
class BranchArchiveView(generic.ObjectView):
"""
Archive a merged Branch, deleting its database schema but retaining the Branch object.
"""
queryset = Branch.objects.all()
template_name = 'netbox_branching/branch_archive.html'

def get_required_permission(self):
return f'netbox_branching.archive_branch'

@staticmethod
def _enforce_status(request, branch):
if branch.status != BranchStatusChoices.MERGED:
messages.error(request, _("Only merged branches can be archived."))
return redirect(branch.get_absolute_url())

def get(self, request, **kwargs):
branch = self.get_object(**kwargs)
self._enforce_status(request, branch)
form = forms.ConfirmationForm()

return render(request, self.template_name, {
'branch': branch,
'form': form,
})

def post(self, request, **kwargs):
branch = self.get_object(**kwargs)
self._enforce_status(request, branch)
form = forms.ConfirmationForm(request.POST)

if form.is_valid():
branch.archive(user=request.user)

messages.success(request, f"Branch {branch} has been archived.")
return redirect(branch.get_absolute_url())

return render(request, self.template_name, {
'branch': branch,
'form': form,
})


class BranchBulkImportView(generic.BulkImportView):
queryset = Branch.objects.all()
model_form = forms.BranchImportForm
Expand Down