|
|
@@ -1,8 +1,16 @@
|
|
|
import logging
|
|
|
-import requests
|
|
|
import sys
|
|
|
+from datetime import timedelta
|
|
|
+from importlib import import_module
|
|
|
|
|
|
+import requests
|
|
|
from django.conf import settings
|
|
|
+from django.core.cache import cache
|
|
|
+from django.utils import timezone
|
|
|
+from packaging import version
|
|
|
+
|
|
|
+from core.models import Job, ObjectChange
|
|
|
+from netbox.config import Config
|
|
|
from netbox.jobs import JobRunner, system_job
|
|
|
from netbox.search.backends import search_backend
|
|
|
from utilities.proxy import resolve_proxies
|
|
|
@@ -53,16 +61,23 @@ class SystemHousekeepingJob(JobRunner):
|
|
|
if settings.DEBUG or 'test' in sys.argv:
|
|
|
return
|
|
|
|
|
|
- # TODO: Migrate other housekeeping functions from the `housekeeping` management command.
|
|
|
self.send_census_report()
|
|
|
+ self.clear_expired_sessions()
|
|
|
+ self.prune_changelog()
|
|
|
+ self.delete_expired_jobs()
|
|
|
+ self.check_for_new_releases()
|
|
|
|
|
|
@staticmethod
|
|
|
def send_census_report():
|
|
|
"""
|
|
|
Send a census report (if enabled).
|
|
|
"""
|
|
|
- # Skip if census reporting is disabled
|
|
|
- if settings.ISOLATED_DEPLOYMENT or not settings.CENSUS_REPORTING_ENABLED:
|
|
|
+ logging.info("Reporting census data...")
|
|
|
+ if settings.ISOLATED_DEPLOYMENT:
|
|
|
+ logging.info("ISOLATED_DEPLOYMENT is enabled; skipping")
|
|
|
+ return
|
|
|
+ if not settings.CENSUS_REPORTING_ENABLED:
|
|
|
+ logging.info("CENSUS_REPORTING_ENABLED is disabled; skipping")
|
|
|
return
|
|
|
|
|
|
census_data = {
|
|
|
@@ -79,3 +94,94 @@ class SystemHousekeepingJob(JobRunner):
|
|
|
)
|
|
|
except requests.exceptions.RequestException:
|
|
|
pass
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def clear_expired_sessions():
|
|
|
+ """
|
|
|
+ Clear any expired sessions from the database.
|
|
|
+ """
|
|
|
+ logging.info("Clearing expired sessions...")
|
|
|
+ engine = import_module(settings.SESSION_ENGINE)
|
|
|
+ try:
|
|
|
+ engine.SessionStore.clear_expired()
|
|
|
+ logging.info("Sessions cleared.")
|
|
|
+ except NotImplementedError:
|
|
|
+ logging.warning(
|
|
|
+ f"The configured session engine ({settings.SESSION_ENGINE}) does not support "
|
|
|
+ f"clearing sessions; skipping."
|
|
|
+ )
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def prune_changelog():
|
|
|
+ """
|
|
|
+ Delete any ObjectChange records older than the configured changelog retention time (if any).
|
|
|
+ """
|
|
|
+ logging.info("Pruning old changelog entries...")
|
|
|
+ config = Config()
|
|
|
+ if not config.CHANGELOG_RETENTION:
|
|
|
+ logging.info("No retention period specified; skipping.")
|
|
|
+ return
|
|
|
+
|
|
|
+ cutoff = timezone.now() - timedelta(days=config.CHANGELOG_RETENTION)
|
|
|
+ logging.debug(f"Retention period: {config.CHANGELOG_RETENTION} days")
|
|
|
+ logging.debug(f"Cut-off time: {cutoff}")
|
|
|
+
|
|
|
+ count = ObjectChange.objects.filter(time__lt=cutoff).delete()[0]
|
|
|
+ logging.info(f"Deleted {count} expired records")
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def delete_expired_jobs():
|
|
|
+ """
|
|
|
+ Delete any jobs older than the configured retention period (if any).
|
|
|
+ """
|
|
|
+ logging.info("Deleting expired jobs...")
|
|
|
+ config = Config()
|
|
|
+ if not config.JOB_RETENTION:
|
|
|
+ logging.info("No retention period specified; skipping.")
|
|
|
+ return
|
|
|
+
|
|
|
+ cutoff = timezone.now() - timedelta(days=config.JOB_RETENTION)
|
|
|
+ logging.debug(f"Retention period: {config.CHANGELOG_RETENTION} days")
|
|
|
+ logging.debug(f"Cut-off time: {cutoff}")
|
|
|
+
|
|
|
+ count = Job.objects.filter(created__lt=cutoff).delete()[0]
|
|
|
+ logging.info(f"Deleted {count} expired records")
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def check_for_new_releases():
|
|
|
+ """
|
|
|
+ Check for new releases and cache the latest release.
|
|
|
+ """
|
|
|
+ logging.info("Checking for new releases...")
|
|
|
+ if settings.ISOLATED_DEPLOYMENT:
|
|
|
+ logging.info("ISOLATED_DEPLOYMENT is enabled; skipping")
|
|
|
+ return
|
|
|
+ if not settings.RELEASE_CHECK_URL:
|
|
|
+ logging.info("RELEASE_CHECK_URL is not set; skipping")
|
|
|
+ return
|
|
|
+
|
|
|
+ # Fetch the latest releases
|
|
|
+ logging.debug(f"Release check URL: {settings.RELEASE_CHECK_URL}")
|
|
|
+ try:
|
|
|
+ response = requests.get(
|
|
|
+ url=settings.RELEASE_CHECK_URL,
|
|
|
+ headers={'Accept': 'application/vnd.github.v3+json'},
|
|
|
+ proxies=resolve_proxies(url=settings.RELEASE_CHECK_URL)
|
|
|
+ )
|
|
|
+ response.raise_for_status()
|
|
|
+ except requests.exceptions.RequestException as exc:
|
|
|
+ logging.error(f"Error fetching release: {exc}")
|
|
|
+ return
|
|
|
+
|
|
|
+ # Determine the most recent stable release
|
|
|
+ releases = []
|
|
|
+ for release in response.json():
|
|
|
+ if 'tag_name' not in release or release.get('devrelease') or release.get('prerelease'):
|
|
|
+ continue
|
|
|
+ releases.append((version.parse(release['tag_name']), release.get('html_url')))
|
|
|
+ latest_release = max(releases)
|
|
|
+ logging.debug(f"Found {len(response.json())} releases; {len(releases)} usable")
|
|
|
+ logging.info(f"Latest release: {latest_release[0]}")
|
|
|
+
|
|
|
+ # Cache the most recent release
|
|
|
+ cache.set('latest_release', latest_release, None)
|