diff --git a/docs/configuration/optional-settings.md b/docs/configuration/optional-settings.md index cbe01728cf..36687343af 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 7002def9b4..63521789ae 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 89958bc13f..1e598f85d6 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 1b7a9da809..cfc2a9cc88 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 06c5c8784e..b12a127fad 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 0000000000..bebe26fbaf --- /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