Skip to content

Commit cfd24be

Browse files
authored
Merge pull request #131 from DanSheps/nb-34-update
Update for NetBox 3.4
2 parents a63e082 + 8165680 commit cfd24be

49 files changed

Lines changed: 1174 additions & 563 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/pypi.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
name: python-package-distributions
2929
path: dist/
3030
publish-to-testpypi:
31-
name: Publish Python 🐍 distribution 📦 to PyPI
31+
name: Publish Python 🐍 distribution 📦 to TestPyPI
3232
needs:
3333
- build
3434
runs-on: ubuntu-latest
@@ -46,8 +46,8 @@ jobs:
4646
- name: Publish package to TestPyPI
4747
uses: pypa/gh-action-pypi-publish@release/v1
4848
with:
49-
repository_url: https://test.pypi.org/legacy/
50-
skip_existing: true
49+
repository-url: https://test.pypi.org/legacy/
50+
skip-existing: true
5151
publish-to-pypi:
5252
name: Publish Python 🐍 distribution 📦 to PyPI
5353
needs:
@@ -67,4 +67,4 @@ jobs:
6767
- name: Publish package
6868
uses: pypa/gh-action-pypi-publish@release/v1
6969
with:
70-
skip_existing: true
70+
skip-existing: true

.pre-commit-config.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
repos:
2+
- repo: https://github.com/astral-sh/ruff-pre-commit
3+
rev: v0.6.9
4+
hooks:
5+
- id: ruff
6+
name: "Ruff linter"
7+
args: [ netbox_config_backup/ ]
8+
- repo: https://github.com/psf/black-pre-commit-mirror
9+
rev: 25.1.0
10+
hooks:
11+
- id: black
12+
name: "Black"
13+

netbox_config_backup/__init__.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
1-
from datetime import datetime
21
from importlib.metadata import metadata
32

4-
from django.utils import timezone
5-
from django_rq import get_queue
6-
7-
from core.choices import JobStatusChoices
83
from netbox.plugins import PluginConfig
9-
from netbox_config_backup.utils.logger import get_logger
104

115
metadata = metadata('netbox_config_backup')
126

@@ -19,8 +13,7 @@ class NetboxConfigBackup(PluginConfig):
1913
author = metadata.get('Author')
2014
author_email = metadata.get('Author-email')
2115
base_url = 'configbackup'
22-
min_version = '4.1.0'
23-
max_version = '4.2.99'
16+
min_version = '4.4.0'
2417
required_settings = [
2518
'repository',
2619
'committer',
@@ -30,16 +23,15 @@ class NetboxConfigBackup(PluginConfig):
3023
# Frequency in seconds
3124
'frequency': 3600,
3225
}
33-
queues = [
34-
'jobs'
35-
]
26+
queues = ['jobs']
3627
graphql_schema = 'graphql.schema.schema'
3728

3829
def ready(self, *args, **kwargs):
3930
super().ready()
4031
import sys
32+
4133
if len(sys.argv) > 1 and 'rqworker' in sys.argv[1]:
42-
from netbox_config_backup.jobs.backup import BackupRunner
34+
from netbox_config_backup.jobs.backup import BackupRunner # noqa: F401
4335

4436

4537
config = NetboxConfigBackup
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from .serializers import *
1+
from .serializers import *

netbox_config_backup/api/views.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
21
from netbox.api.viewsets import NetBoxModelViewSet
32
from netbox_config_backup.api import BackupSerializer
43
from netbox_config_backup.models import Backup
54

65

76
class BackupViewSet(NetBoxModelViewSet):
87
queryset = Backup.objects.all()
9-
serializer_class = BackupSerializer
8+
serializer_class = BackupSerializer

netbox_config_backup/backup/processing.py

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import logging
22
import os
3-
import time
43
import traceback
54
from datetime import timedelta
65

76
import uuid
8-
from django.db.models import Q
97
from django.utils import timezone
108

119
from core.choices import JobStatusChoices
@@ -17,32 +15,39 @@
1715
from netbox_config_backup.utils.napalm import napalm_init
1816
from netbox_config_backup.utils.rq import can_backup
1917

20-
logger = logging.getLogger(f"netbox_config_backup")
18+
logger = logging.getLogger("netbox_config_backup")
2119

2220

2321
def remove_stale_backupjobs(job: BackupJob):
2422
pass
2523

24+
2625
def run_backup(job_id):
2726
close_db()
2827
logger.info(f'Starting backup for job {job_id}')
2928
try:
29+
logger.debug(f'Trying to load job {job_id}')
3030
job = BackupJob.objects.get(pk=job_id)
3131
except Exception as e:
3232
logger.error(f'Unable to load job {job_id}: {e}')
3333
logger.debug(f'\t{traceback.format_exc()}')
3434
raise e
3535

3636
try:
37+
logger.debug(f'Getting backup for {job}')
3738
backup = Backup.objects.get(pk=job.backup.pk)
3839
backup.refresh_from_db()
3940
pid = os.getpid()
4041

42+
logger.debug(f'Setting status and saving for {job}')
43+
4144
job.status = JobStatusChoices.STATUS_PENDING
4245
job.pid = pid
4346
job.save()
4447

48+
logger.debug(f'Checking backup status for {job}')
4549
if not can_backup(backup):
50+
logger.info(f'Cannot backup {backup}')
4651
job.status = JobStatusChoices.STATUS_FAILED
4752
if not job.data:
4853
job.data = {}
@@ -60,20 +65,38 @@ def run_backup(job_id):
6065
raise e
6166

6267
if ip:
68+
logger.debug(
69+
f'Trying to connect to device {backup.device} with ip {ip} for {job}'
70+
)
6371
try:
6472
d = napalm_init(backup.device, ip)
6573
except (TimeoutError, ServiceUnavailable):
6674
job.status = JobStatusChoices.STATUS_FAILED
67-
job.data = {'error': f'Timeout Connecting to {backup.device} with ip {ip}'}
68-
logger.debug = f'Timeout Connecting to {backup.device} with ip {ip}'
75+
job.data = {
76+
'error': f'Timeout Connecting to {backup.device} with ip {ip}'
77+
}
78+
logger.debug(f'Timeout Connecting to {backup.device} with ip {ip}')
6979
job.save()
7080
return
71-
81+
logger.debug(f'Connected to {backup.device} with ip {ip} for {job}')
7282
job.status = JobStatusChoices.STATUS_RUNNING
7383
job.started = timezone.now()
7484
job.save()
7585
try:
76-
status = check_config_save_status(d)
86+
logger.debug(f'Checking config save status for {backup}')
87+
config_save_status = check_config_save_status(d)
88+
status = config_save_status.get('status')
89+
running = backup.files.filter(type='running').first()
90+
startup = backup.files.filter(type='startup').first()
91+
if running.last_change != config_save_status.get('running', None):
92+
running.last_change = config_save_status.get('running', None)
93+
running.clean()
94+
running.save()
95+
if startup.last_change != config_save_status.get('startup', None):
96+
startup.last_change = config_save_status.get('startup', None)
97+
startup.clean()
98+
startup.save()
99+
77100
if status is not None:
78101
if status and not backup.config_status:
79102
backup.config_status = status
@@ -88,15 +111,23 @@ def run_backup(job_id):
88111
backup.config_status = status
89112
backup.save()
90113
except Exception as e:
114+
91115
logger.error(f'{backup}: had error setting backup status: {e}')
92116

117+
logger.debug(f'Getting config for {backup}')
93118
configs = d.get_config()
119+
logger.debug(f'Committing config for {backup}')
94120
commit = backup.set_config(configs)
95-
121+
logger.debug(
122+
f'Committed config for {backup} with {commit}; closing connection for {backup}'
123+
)
96124
d.close()
97125

126+
logger.debug(f'Scheduling next backup for {backup}')
98127
frequency = timedelta(
99-
seconds=settings.PLUGINS_CONFIG.get('netbox_config_backup', {}).get('frequency', 3600)
128+
seconds=settings.PLUGINS_CONFIG.get('netbox_config_backup', {}).get(
129+
'frequency', 3600
130+
)
100131
)
101132
new = BackupJob(
102133
runner=None,
@@ -115,6 +146,7 @@ def run_backup(job_id):
115146
job.save()
116147
remove_stale_backupjobs(job=job)
117148
else:
149+
logger.debug(f'{backup}: No IP set')
118150
job.status = JobStatusChoices.STATUS_FAILED
119151
if not job.data:
120152
job.data = {}

netbox_config_backup/choices.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# File Types for Backup Files
66
#
77

8+
89
class FileTypeChoices(ChoiceSet):
910

1011
TYPE_RUNNING = 'running'
@@ -15,6 +16,7 @@ class FileTypeChoices(ChoiceSet):
1516
(TYPE_STARTUP, 'Startup'),
1617
)
1718

19+
1820
class CommitTreeChangeTypeChoices(ChoiceSet):
1921

2022
TYPE_ADD = 'add'
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class JobExit(Exception):
2+
pass

netbox_config_backup/filtersets.py

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,10 @@
55
from django.utils.translation import gettext as _
66
from netaddr import AddrFormatError
77

8-
from core.choices import JobStatusChoices
9-
from ipam.models import IPAddress
10-
from netbox.filtersets import NetBoxModelFilterSet, BaseFilterSet
8+
from netbox.filtersets import BaseFilterSet
119
from dcim.models import Device
1210
from netbox_config_backup import models
1311
from netbox_config_backup.choices import FileTypeChoices
14-
from utilities.filters import MultiValueCharFilter
1512

1613

1714
class BackupJobFilterSet(BaseFilterSet):
@@ -24,6 +21,17 @@ class Meta:
2421
model = models.BackupJob
2522
fields = ['id', 'status']
2623

24+
def search(self, queryset, name, value):
25+
if not value.strip():
26+
return queryset
27+
qs_filter = (
28+
Q(backup__name__icontains=value)
29+
| Q(backup__ip__address__icontains=value)
30+
| Q(backup__device__name__icontains=value)
31+
)
32+
33+
return queryset.filter(qs_filter)
34+
2735

2836
class BackupFilterSet(BaseFilterSet):
2937
q = django_filters.CharFilter(
@@ -34,12 +42,16 @@ class BackupFilterSet(BaseFilterSet):
3442
field_name='device__name',
3543
queryset=Device.objects.all(),
3644
to_field_name='name',
37-
label='Device (name)',
45+
label=_('Device (name)'),
3846
)
3947
device_id = django_filters.ModelMultipleChoiceFilter(
4048
field_name='device',
4149
queryset=Device.objects.all(),
42-
label='Device (ID)',
50+
label=_('Device (ID)'),
51+
)
52+
config_status = django_filters.BooleanFilter(
53+
field_name='config_status',
54+
label=_('Config Saved'),
4355
)
4456
ip = django_filters.CharFilter(
4557
method='filter_address',
@@ -54,11 +66,11 @@ def search(self, queryset, name, value):
5466
if not value.strip():
5567
return queryset
5668
qs_filter = (
57-
Q(name__icontains=value) |
58-
Q(device__name__icontains=value) |
59-
Q(device__primary_ip4__address__contains=value.strip()) |
60-
Q(device__primary_ip6__address__contains=value.strip()) |
61-
Q(ip__address__contains=value.strip())
69+
Q(name__icontains=value)
70+
| Q(device__name__icontains=value)
71+
| Q(device__primary_ip4__address__contains=value.strip())
72+
| Q(device__primary_ip6__address__contains=value.strip())
73+
| Q(ip__address__contains=value.strip())
6274
)
6375

6476
try:
@@ -81,7 +93,7 @@ def filter_address(self, queryset, name, value):
8193
return queryset.filter(query)
8294
else:
8395
return queryset.filter(ip__address__net_host_contained=value)
84-
except ValidationError as e:
96+
except ValidationError:
8597
return queryset.none()
8698

8799

@@ -91,9 +103,7 @@ class BackupsFilterSet(BaseFilterSet):
91103
label=_('Search'),
92104
)
93105
type = django_filters.MultipleChoiceFilter(
94-
field_name='file__type',
95-
choices=FileTypeChoices,
96-
null_value=None
106+
field_name='file__type', choices=FileTypeChoices, null_value=None
97107
)
98108

99109
class Meta:
@@ -103,8 +113,5 @@ class Meta:
103113
def search(self, queryset, name, value):
104114
if not value.strip():
105115
return queryset
106-
qs_filter = (
107-
Q(file__type=value) |
108-
Q(file__type__startswith=value)
109-
)
116+
qs_filter = Q(file__type=value) | Q(file__type__startswith=value)
110117
return queryset.filter(qs_filter)

0 commit comments

Comments
 (0)