From 2fcdc90d3f8addfa05e873b2758560c3b92b9792 Mon Sep 17 00:00:00 2001 From: Sander Steffann Date: Fri, 24 Jan 2020 00:15:32 +0100 Subject: [PATCH] Automatically check for new versions --- docs/configuration/optional-settings.md | 16 +++++++++++++++ netbox/netbox/configuration.example.py | 8 ++++++++ netbox/netbox/settings.py | 10 +++++++++ netbox/templates/_base.html | 11 +++++++++- netbox/utilities/context_processors.py | 23 +++++++++++++++++++++ netbox/utilities/versions.py | 27 +++++++++++++++++++++++++ 6 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 netbox/utilities/versions.py diff --git a/docs/configuration/optional-settings.md b/docs/configuration/optional-settings.md index cbe01728cf6..36687343af0 100644 --- a/docs/configuration/optional-settings.md +++ b/docs/configuration/optional-settings.md @@ -157,6 +157,22 @@ Enforcement of unique IP space can be toggled on a per-VRF basis. To enforce uni --- +## GITHUB_REPOSITORY + +Default: 'netbox-community/netbox' + +The tags of this repository are checked to detect new releases, which are shown in the footer of the web interface. You can change this to your own fork of the NetBox repository, or set it to `None` to disable the check. + +--- + +## GITHUB_VERSION_TIMEOUT + +Default: 8 * 3600 + +The number of seconds to retain the latest version that is fetched from the GitHub API before automatically invalidating it and fetching it from the API again. Set to 0 to disable the version check. + +--- + ## LOGGING By default, all messages of INFO severity or higher will be logged to the console. Additionally, if `DEBUG` is False and email access has been configured, ERROR and CRITICAL messages will be emailed to the users defined in `ADMINS`. diff --git a/netbox/netbox/configuration.example.py b/netbox/netbox/configuration.example.py index 7002def9b42..63521789aef 100644 --- a/netbox/netbox/configuration.example.py +++ b/netbox/netbox/configuration.example.py @@ -124,6 +124,14 @@ EXEMPT_VIEW_PERMISSIONS = [ # 'ipam.prefix', ] +# This repository is used to check whether there is a new release of NetBox available. Set to None to disable the +# version check. +GITHUB_REPOSITORY = 'netbox-community/netbox' + +# This determines how often the GitHub API is called to check the latest release of NetBox. Set to 0 to disable the +# version check. +GITHUB_VERSION_TIMEOUT = 8 * 3600 + # Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs: # https://docs.djangoproject.com/en/stable/topics/logging/ LOGGING = {} diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 89958bc13fb..1e598f85d66 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -1,6 +1,7 @@ import logging import os import platform +import re import socket import warnings @@ -78,6 +79,8 @@ DEVELOPER = getattr(configuration, 'DEVELOPER', False) EMAIL = getattr(configuration, 'EMAIL', {}) ENFORCE_GLOBAL_UNIQUE = getattr(configuration, 'ENFORCE_GLOBAL_UNIQUE', False) EXEMPT_VIEW_PERMISSIONS = getattr(configuration, 'EXEMPT_VIEW_PERMISSIONS', []) +GITHUB_REPOSITORY = getattr(configuration, 'GITHUB_REPOSITORY', 'netbox-community/netbox') +GITHUB_VERSION_TIMEOUT = getattr(configuration, 'GITHUB_VERSION_TIMEOUT', 8 * 3600) LOGGING = getattr(configuration, 'LOGGING', {}) LOGIN_REQUIRED = getattr(configuration, 'LOGIN_REQUIRED', False) LOGIN_TIMEOUT = getattr(configuration, 'LOGIN_TIMEOUT', None) @@ -292,6 +295,7 @@ TEMPLATES = [ 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'utilities.context_processors.settings', + 'utilities.context_processors.latest_version', ], }, }, @@ -302,6 +306,12 @@ AUTHENTICATION_BACKENDS = [ 'utilities.auth_backends.ViewExemptModelBackend', ] +# GitHub repository for version check +if GITHUB_REPOSITORY and not re.fullmatch(r'[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+', GITHUB_REPOSITORY): + raise ImproperlyConfigured( + "GITHUB_REPOSITORY must contain the name of a GitHub repository in the form '/'" + ) + # Internationalization LANGUAGE_CODE = 'en-us' USE_I18N = True diff --git a/netbox/templates/_base.html b/netbox/templates/_base.html index 1b7a9da8094..cfc2a9cc88f 100644 --- a/netbox/templates/_base.html +++ b/netbox/templates/_base.html @@ -50,7 +50,16 @@
-

{{ settings.HOSTNAME }} (v{{ settings.VERSION }})

+

+ {{ settings.HOSTNAME }} (v{{ settings.VERSION }}) + {% if latest_version %} + {% if latest_version_url %}{% endif %} + + New version: {{ latest_version }} + + {% if latest_version_url %}{% endif %} + {% endif %} +

{% now 'Y-m-d H:i:s T' %}

diff --git a/netbox/utilities/context_processors.py b/netbox/utilities/context_processors.py index 06c5c8784e6..b12a127fad4 100644 --- a/netbox/utilities/context_processors.py +++ b/netbox/utilities/context_processors.py @@ -1,4 +1,7 @@ from django.conf import settings as django_settings +from packaging import version + +from utilities.versions import get_latest_version def settings(request): @@ -8,3 +11,23 @@ def settings(request): return { 'settings': django_settings, } + + +def latest_version(request): + """ + Get the latest version from the GitHub repository + """ + github_latest_version, github_url = get_latest_version() + + latest_version_str = None + latest_version_url = None + if isinstance(github_latest_version, version.Version): + current_version = version.parse(django_settings.VERSION) + if github_latest_version > current_version: + latest_version_str = str(github_latest_version) + latest_version_url = github_url + + return { + 'latest_version': latest_version_str, + 'latest_version_url': latest_version_url + } diff --git a/netbox/utilities/versions.py b/netbox/utilities/versions.py new file mode 100644 index 00000000000..bebe26fbaf3 --- /dev/null +++ b/netbox/utilities/versions.py @@ -0,0 +1,27 @@ +import requests +from cacheops import cached +from django.conf import settings +from packaging import version + +if settings.GITHUB_VERSION_TIMEOUT and settings.GITHUB_REPOSITORY: + @cached(timeout=settings.GITHUB_VERSION_TIMEOUT) + def get_latest_version(): + url = 'https://api.github.com/repos/{}/releases'.format(settings.GITHUB_REPOSITORY) + headers = { + 'Accept': 'application/vnd.github.v3+json', + } + try: + response = requests.get(url, headers=headers) + versions = [(version.parse(release['tag_name']), release.get('html_url')) + for release in response.json() + if 'tag_name' in release] + if versions: + return max(versions) + except: + pass + + return 'unknown', None + +else: + def get_latest_version(): + return None