1
0
mirror of https://github.com/peeringdb/peeringdb.git synced 2024-05-11 05:55:09 +00:00
Files
peeringdb-peeringdb/peeringdb_server/admin_commandline_tools.py
Matt Griswold ba6f9b6432 Qu1003 (#621)
* use new peeringdb client (1.0.0) for pdb_load_data sync (#599)

* drop django-mobi for lack of py3/dj2 support (#492)
remove django-forms-bootstrap for lack of py3/dj2 support (#492)

* black formatted

* django2.2 and py3 upgrade (#492)

* drop ixlans (#21) ui and api changes

* drop local_asn (#168)

* org search (#193)

* phone number validation (#50)

* implement help text tooltips (#228)

* Mark own ASN as transit-free (#394)

* py3 fix for `pdb_migrate_ixlans` command when writing migration report

* pdb_migrate_ixlans: properly handle py3 Runtime error if ixlan dict changes during iteration

* set rest DEFAULT_SCHEMA_CLASS to coreapi to fix swagger apidocs
fix migration 0027 missing from facsimile manifest

* fix swagger doc strings

* fix tests that were broken from api doc fixes

* fix UniqueFieldValidator for netixlan ipaddress validation that broke during django/drf upgrade

* fix org merge tool layout issues

* travis config

* update pipfile and lock

* black formatting

* update travis dist

* beta mode banner (#411)

* add beta banner template (#411)

* automatically scheduled sync may not always be on, add a flag that lets us reflect that state in the beta banner message
clean up beta banner implementation (#411)

* add tests for beta banner (#411)
2020-01-08 13:29:58 -06:00

388 lines
11 KiB
Python

import io
import json
import reversion
from reversion.models import Version
from dal import autocomplete
from django import forms
from django.core.management import call_command
from peeringdb_server.models import (
REFTAG_MAP,
COMMANDLINE_TOOLS,
CommandLineTool,
InternetExchange,
Facility,
IXLan,
)
from peeringdb_server import maintenance
def _(m):
return m
TOOL_MAP = {}
def register_tool(cls):
TOOL_MAP[cls.tool] = cls
def get_tool(tool_id, form):
"""
Arguments:
tool_id (str): tool_id as it exists in COMMANDLINE_TOOLS
form (django.forms.Form): form instance
Returns:
CommandLineToolWrapper instance
"""
t = TOOL_MAP.get(tool_id)
t = t(form)
return t
def get_tool_from_data(data):
"""
Arguments:
data (dict): dict containing form data, at the very least
needs to have a "tool" key containing the tool_id
Returns:
CommandLineToolWrapper instance
"""
tool_id = data.get("tool")
t = TOOL_MAP.get(tool_id)
form = t.Form(data)
form.is_valid()
t = t(form)
return t
class EmptyId(object):
id = 0
class CommandLineToolWrapper(object):
tool = None
queue = 0
maintenance = False
class Form(forms.Form):
pass
def __init__(self, form):
self.status = 0
self.result = None
self.args = []
self.kwargs = {}
self.form_instance = form
self.set_arguments(form.cleaned_data)
@property
def name(self):
return dict(COMMANDLINE_TOOLS).get(self.tool)
@property
def form(self):
return self.Form()
@property
def description(self):
return self.tool
@property
def pretty_result(self):
if not self.result:
return ""
r = []
for line in self.result.split("\n"):
if line.find("[error]") > -1:
r.append('<div class="error">{}</div>'.format(line))
elif line.find("[warning]") > -1:
r.append('<div class="warning">{}</div>'.format(line))
else:
r.append('<div class="info">{}</div>'.format(line))
return "\n".join(r)
def set_arguments(self, form_data):
pass
def validate(self):
pass
def _run(self, user, commit=False):
r = io.StringIO()
if self.maintenance and commit:
maintenance.on()
try:
self.validate()
if commit:
call_command(
self.tool, *self.args, commit=True, stdout=r, **self.kwargs
)
else:
call_command(self.tool, *self.args, stdout=r, **self.kwargs)
self.result = r.getvalue()
except Exception as inst:
self.result = "[error] {}".format(inst)
self.status = 1
finally:
if self.maintenance and commit:
maintenance.off()
if commit:
CommandLineTool.objects.create(
user=user,
tool=self.tool,
description=self.description,
status="done",
arguments=json.dumps({"args": self.args, "kwargs": self.kwargs}),
result=self.result,
)
return self.result
def run(self, user, commit=False):
if self.queue and commit:
if (
CommandLineTool.objects.filter(tool=self.tool)
.exclude(status="done")
.count()
>= self.queue
):
self.result = "[error] {}".format(
_(
"This command is already waiting / running - please wait for it to finish before executing it again"
)
)
return self.result
CommandLineTool.objects.create(
user=user,
tool=self.tool,
description=self.description,
status="waiting",
arguments=json.dumps({"args": self.args, "kwargs": self.kwargs}),
result="",
)
self.result = "[warn] {}".format(
_(
"This command takes a while to complete and will be queued and ran in the "
"background. No output log can be provided at this point in time. You may "
"review once the command has finished."
)
)
return self.result
else:
with reversion.create_revision():
return self._run(user, commit=commit)
# TOOL: RENUMBER LAN
@register_tool
class ToolRenumberLans(CommandLineToolWrapper):
"""
This tools runs the pdb_renumber_lans command to
Renumber IP Spaces in an Exchange
"""
tool = "pdb_renumber_lans"
class Form(forms.Form):
exchange = forms.ModelChoiceField(
queryset=InternetExchange.handleref.undeleted().order_by("name"),
widget=autocomplete.ModelSelect2(url="/autocomplete/ix/json"),
)
old_prefix = forms.CharField(
help_text=_(
"Old prefix - renumber all netixlans that fall into this prefix"
)
)
new_prefix = forms.CharField(
help_text=_(
"New prefix - needs to be the same protocol and length as old prefix"
)
)
@property
def description(self):
""" Provide a human readable description of the command that was run """
try:
return "{}: {} to {}".format(
InternetExchange.objects.get(id=self.args[0]),
self.args[1],
self.args[2],
)
except:
# if a version of this command was run before, we still need to able
# to display a somewhat useful discription, so fall back to this basic
# display
return "(Legacy) {}".format(self.args)
def set_arguments(self, form_data):
self.args = [
form_data.get("exchange", EmptyId()).id,
form_data.get("old_prefix"),
form_data.get("new_prefix"),
]
@register_tool
class ToolMergeFacilities(CommandLineToolWrapper):
"""
This tool runs the pdb_fac_merge command to
merge two facilities
"""
tool = "pdb_fac_merge"
class Form(forms.Form):
other = forms.ModelChoiceField(
queryset=Facility.handleref.undeleted().order_by("name"),
widget=autocomplete.ModelSelect2(url="/autocomplete/fac/json"),
help_text=_("Merge this facility - it will be deleted"),
)
target = forms.ModelChoiceField(
queryset=Facility.handleref.undeleted().order_by("name"),
widget=autocomplete.ModelSelect2(url="/autocomplete/fac/json"),
help_text=_("Target facility"),
)
@property
def description(self):
""" Provide a human readable description of the command that was run """
return "{} into {}".format(
Facility.objects.get(id=self.kwargs["ids"]),
Facility.objects.get(id=self.kwargs["target"]),
)
def set_arguments(self, form_data):
self.kwargs = {
"ids": str(form_data.get("other", EmptyId()).id),
"target": str(form_data.get("target", EmptyId()).id),
}
@register_tool
class ToolMergeFacilitiesUndo(CommandLineToolWrapper):
"""
This tool runs the pdb_fac_merge_undo command to
undo a facility merge
"""
tool = "pdb_fac_merge_undo"
class Form(forms.Form):
merge = forms.ModelChoiceField(
queryset=CommandLineTool.objects.filter(tool="pdb_fac_merge").order_by(
"-created"
),
widget=autocomplete.ModelSelect2(
url="/autocomplete/admin/clt-history/pdb_fac_merge/"
),
help_text=_("Undo this merge"),
)
@property
def description(self):
""" Provide a human readable description of the command that was run """
# in order to make a useful description we need to collect the arguments
# from the merge command that was undone
kwargs = json.loads(
CommandLineTool.objects.get(id=self.kwargs["clt"]).arguments
).get("kwargs")
return "Undo: {} into {}".format(
Facility.objects.get(id=kwargs["ids"]),
Facility.objects.get(id=kwargs["target"]),
)
def set_arguments(self, form_data):
self.kwargs = {"clt": form_data.get("merge", EmptyId()).id}
@register_tool
class ToolReset(CommandLineToolWrapper):
tool = "pdb_wipe"
queue = 1
maintenance = True
class Form(forms.Form):
keep_users = forms.BooleanField(
required=False,
help_text=_(
"Don't delete users. Note that superuser accounts are always kept - regardless of this setting."
),
)
load_data = forms.BooleanField(
required=False, initial=True, help_text=_("Load data from peeringdb API")
)
load_data_url = forms.CharField(
required=False, initial="https://www.peeringdb.com/api"
)
@property
def description(self):
return "Reset environment"
def set_arguments(self, form_data):
self.kwargs = form_data
@register_tool
class ToolUndelete(CommandLineToolWrapper):
"""
Allows restoration of an object object and it's child objects
"""
tool = "pdb_undelete"
# These are the reftags that are currently supported by this
# tool.
supported_reftags = ["ixlan", "fac"]
class Form(forms.Form):
version = forms.ModelChoiceField(
queryset=Version.objects.all().order_by("-revision_id"),
widget=autocomplete.ModelSelect2(url="/autocomplete/admin/deletedversions"),
help_text=_("Restore this object - search by [reftag] [id]"),
)
@property
def description(self):
return "{reftag} {id}".format(**self.kwargs)
def set_arguments(self, form_data):
version = form_data.get("version")
if not version:
return
reftag = version.content_type.model_class().HandleRef.tag
self.kwargs = {
"reftag": reftag,
"id": version.object_id,
"version_id": version.id,
}
def validate(self):
if self.kwargs.get("reftag") not in self.supported_reftags:
raise ValueError(
_(
"Only {} type objects may be restored "
"through this interface at this point"
).format(",".join(self.supported_reftags))
)
obj = REFTAG_MAP[self.kwargs.get("reftag")].objects.get(
id=self.kwargs.get("id")
)
if obj.status != "deleted":
raise ValueError("{} is not currently marked as deleted".format(obj))