Просмотр исходного кода

Merge branch 'develop' into feature

jeremystretch 3 лет назад
Родитель
Сommit
e208404e3a

+ 1 - 1
.github/ISSUE_TEMPLATE/bug_report.yaml

@@ -14,7 +14,7 @@ body:
     attributes:
       label: NetBox version
       description: What version of NetBox are you currently running?
-      placeholder: v3.2.2
+      placeholder: v3.2.3
     validations:
       required: true
   - type: dropdown

+ 1 - 1
.github/ISSUE_TEMPLATE/feature_request.yaml

@@ -14,7 +14,7 @@ body:
     attributes:
       label: NetBox version
       description: What version of NetBox are you currently running?
-      placeholder: v3.2.2
+      placeholder: v3.2.3
     validations:
       required: true
   - type: dropdown

+ 2 - 0
README.md

@@ -60,6 +60,8 @@ The complete documentation for NetBox can be found at [docs.netbox.dev](https://
             
   [![NS1](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/ns1.png)](https://ns1.com/)
   <br />
+  [![Sentry](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/sentry.png)](https://sentry.io/)
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
   [![Stellar Technologies](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/stellar.png)](https://stellar.tech/)
 
 </div>

+ 4 - 0
base_requirements.txt

@@ -102,6 +102,10 @@ psycopg2-binary
 # https://github.com/yaml/pyyaml
 PyYAML
 
+# Sentry SDK
+# https://github.com/getsentry/sentry-python
+sentry-sdk
+
 # Social authentication framework
 # https://github.com/python-social-auth/social-core
 social-auth-core

+ 46 - 0
docs/administration/error-reporting.md

@@ -0,0 +1,46 @@
+# Error Reporting
+
+## Sentry
+
+### Enabling Error Reporting
+
+NetBox v3.2.3 and later support native integration with [Sentry](https://sentry.io/) for automatic error reporting. To enable this functionality, simply set `SENTRY_ENABLED` to True in `configuration.py`. Errors will be sent to a Sentry ingestor maintained by the NetBox team for analysis.
+
+```python
+SENTRY_ENABLED = True
+```
+
+### Using a Custom DSN
+
+If you prefer instead to use your own Sentry ingestor, you'll need to first create a new project under your Sentry account to represent your NetBox deployment and obtain its corresponding data source name (DSN). This looks like a URL similar to the example below:
+
+```
+https://examplePublicKey@o0.ingest.sentry.io/0
+```
+
+Once you have obtained a DSN, configure Sentry in NetBox's `configuration.py` file with the following parameters:
+
+```python
+SENTRY_ENABLED = True
+SENTRY_DSN = "https://examplePublicKey@o0.ingest.sentry.io/0"
+```
+
+### Assigning Tags
+
+You can optionally attach one or more arbitrary tags to the outgoing error reports if desired by setting the `SENTRY_TAGS` parameter:
+
+```python
+SENTRY_TAGS = {
+    "custom.foo": "123",
+    "custom.bar": "abc",
+}
+```
+
+!!! warning "Reserved tag prefixes"
+    Avoid using any tag names which begin with `netbox.`, as this prefix is reserved by the NetBox application.
+
+### Testing
+
+Once the configuration has been saved, restart the NetBox service.
+
+To test Sentry operation, try generating a 404 (page not found) error by navigating to an invalid URL, such as `https://netbox/404-error-testing`. (Be sure that debug mode has been disabled.) After receiving a 404 response from the NetBox server, you should see the issue appear shortly in Sentry.

+ 54 - 0
docs/configuration/error-reporting.md

@@ -0,0 +1,54 @@
+# Error Reporting Settings
+
+## SENTRY_DSN
+
+Default: None
+
+Defines a Sentry data source name (DSN) for automated error reporting. `SENTRY_ENABLED` must be True for this parameter to take effect. For example:
+
+```
+SENTRY_DSN = "https://examplePublicKey@o0.ingest.sentry.io/0"
+```
+
+---
+
+## SENTRY_ENABLED
+
+Default: False
+
+Set to True to enable automatic error reporting via [Sentry](https://sentry.io/).
+
+---
+
+## SENTRY_SAMPLE_RATE
+
+Default: 1.0 (all)
+
+The sampling rate for errors. Must be a value between 0 (disabled) and 1.0 (report on all errors).
+
+---
+
+## SENTRY_TAGS
+
+An optional dictionary of tag names and values to apply to Sentry error reports.For example:
+
+```
+SENTRY_TAGS = {
+    "custom.foo": "123",
+    "custom.bar": "abc",
+}
+```
+
+!!! warning "Reserved tag prefixes"
+    Avoid using any tag names which begin with `netbox.`, as this prefix is reserved by the NetBox application.
+
+---
+
+## SENTRY_TRACES_SAMPLE_RATE
+
+Default: 0 (disabled)
+
+The sampling rate for transactions. Must be a value between 0 (disabled) and 1.0 (report on all transactions).
+
+!!! warning "Consider performance implications"
+    A high sampling rate for transactions can induce significant performance penalties. If transaction reporting is desired, it is recommended to use a relatively low sample rate of 10% to 20% (0.1 to 0.2).

+ 10 - 1
docs/release-notes/version-3.2.md

@@ -1,21 +1,30 @@
 # NetBox v3.2
 
-## v3.2.3 (FUTURE)
+## v3.2.4 (FUTURE)
+
+---
+
+## v3.2.3 (2022-05-12)
 
 ### Enhancements
 
+* [#8805](https://github.com/netbox-community/netbox/issues/8805) - Add "mixed" option for device airflow indication
 * [#8894](https://github.com/netbox-community/netbox/issues/8894) - Include full names when listing users
 * [#8998](https://github.com/netbox-community/netbox/issues/8998) - Enable filtering racks & reservations by site group
 * [#9122](https://github.com/netbox-community/netbox/issues/9122) - Introduce `clearcache` management command & clear cache during upgrade
+* [#9221](https://github.com/netbox-community/netbox/issues/9221) - Add definition list support for Markdown
 * [#9260](https://github.com/netbox-community/netbox/issues/9260) - Apply user preferences to tables under object detail views
 * [#9278](https://github.com/netbox-community/netbox/issues/9278) - Linkify device types count under manufacturers list
 * [#9280](https://github.com/netbox-community/netbox/issues/9280) - Allow adopting existing components when installing a module
 * [#9314](https://github.com/netbox-community/netbox/issues/9314) - Add device and VM filters for FHRP group assignments
+* [#9340](https://github.com/netbox-community/netbox/issues/9340) - Introduce support for error reporting via Sentry
+* [#9343](https://github.com/netbox-community/netbox/issues/9343) - Add Ubiquiti SmartPower power outlet type
 
 ### Bug Fixes
 
 * [#9190](https://github.com/netbox-community/netbox/issues/9190) - Prevent exception when attempting to instantiate module components which already exist on the parent device
 * [#9267](https://github.com/netbox-community/netbox/issues/9267) - Remove invalid entry in IP address role choices
+* [#9296](https://github.com/netbox-community/netbox/issues/9296) - Improve Markdown link sanitization
 * [#9306](https://github.com/netbox-community/netbox/issues/9306) - Include VC master interfaces when selecting a LAG/bridge for a VC member interface
 * [#9311](https://github.com/netbox-community/netbox/issues/9311) - Permit creating contact assignment without a priority via the REST API
 * [#9313](https://github.com/netbox-community/netbox/issues/9313) - Remove HTML code from CSV output of many-to-many relationships

+ 2 - 0
mkdocs.yml

@@ -73,6 +73,7 @@ nav:
         - Required Settings: 'configuration/required-settings.md'
         - Optional Settings: 'configuration/optional-settings.md'
         - Dynamic Settings: 'configuration/dynamic-settings.md'
+        - Error Reporting: 'configuration/error-reporting.md'
         - Remote Authentication: 'configuration/remote-authentication.md'
     - Core Functionality:
         - IP Address Management: 'core-functionality/ipam.md'
@@ -123,6 +124,7 @@ nav:
             - Microsoft Azure AD: 'administration/authentication/microsoft-azure-ad.md'
             - Okta: 'administration/authentication/okta.md'
         - Permissions: 'administration/permissions.md'
+        - Error Reporting: 'administration/error-reporting.md'
         - Housekeeping: 'administration/housekeeping.md'
         - Replicating NetBox: 'administration/replicating-netbox.md'
         - NetBox Shell: 'administration/netbox-shell.md'

+ 4 - 0
netbox/dcim/choices.py

@@ -159,6 +159,7 @@ class DeviceAirflowChoices(ChoiceSet):
     AIRFLOW_RIGHT_TO_LEFT = 'right-to-left'
     AIRFLOW_SIDE_TO_REAR = 'side-to-rear'
     AIRFLOW_PASSIVE = 'passive'
+    AIRFLOW_MIXED = 'mixed'
 
     CHOICES = (
         (AIRFLOW_FRONT_TO_REAR, 'Front to rear'),
@@ -167,6 +168,7 @@ class DeviceAirflowChoices(ChoiceSet):
         (AIRFLOW_RIGHT_TO_LEFT, 'Right to left'),
         (AIRFLOW_SIDE_TO_REAR, 'Side to rear'),
         (AIRFLOW_PASSIVE, 'Passive'),
+        (AIRFLOW_MIXED, 'Mixed'),
     )
 
 
@@ -575,6 +577,7 @@ class PowerOutletTypeChoices(ChoiceSet):
     TYPE_NEUTRIK_POWERCON_32A = 'neutrik-powercon-32a'
     TYPE_NEUTRIK_POWERCON_TRUE1 = 'neutrik-powercon-true1'
     TYPE_NEUTRIK_POWERCON_TRUE1_TOP = 'neutrik-powercon-true1-top'
+    TYPE_UBIQUITI_SMARTPOWER = 'ubiquiti-smartpower'
     # Other
     TYPE_HARDWIRED = 'hardwired'
 
@@ -683,6 +686,7 @@ class PowerOutletTypeChoices(ChoiceSet):
             (TYPE_NEUTRIK_POWERCON_32A, 'Neutrik powerCON (32A)'),
             (TYPE_NEUTRIK_POWERCON_TRUE1, 'Neutrik powerCON TRUE1'),
             (TYPE_NEUTRIK_POWERCON_TRUE1_TOP, 'Neutrik powerCON TRUE1 TOP'),
+            (TYPE_UBIQUITI_SMARTPOWER, 'Ubiquiti SmartPower'),
         )),
         ('Other', (
             (TYPE_HARDWIRED, 'Hardwired'),

+ 42 - 0
netbox/netbox/settings.py

@@ -1,3 +1,4 @@
+import hashlib
 import importlib
 import logging
 import os
@@ -8,9 +9,11 @@ import sys
 import warnings
 from urllib.parse import urlsplit
 
+import sentry_sdk
 from django.contrib.messages import constants as messages
 from django.core.exceptions import ImproperlyConfigured, ValidationError
 from django.core.validators import URLValidator
+from sentry_sdk.integrations.django import DjangoIntegration
 
 from netbox.config import PARAMS
 
@@ -40,6 +43,7 @@ if sys.version_info < (3, 8):
         f"NetBox requires Python 3.8 or later. (Currently installed: Python {platform.python_version()})"
     )
 
+DEFAULT_SENTRY_DSN = 'https://198cf560b29d4054ab8e583a1d10ea58@o1242133.ingest.sentry.io/6396485'
 
 #
 # Configuration import
@@ -68,6 +72,9 @@ DATABASE = getattr(configuration, 'DATABASE')
 REDIS = getattr(configuration, 'REDIS')
 SECRET_KEY = getattr(configuration, 'SECRET_KEY')
 
+# Calculate a unique deployment ID from the secret key
+DEPLOYMENT_ID = hashlib.sha256(SECRET_KEY.encode('utf-8')).hexdigest()[:16]
+
 # Set static config parameters
 ADMINS = getattr(configuration, 'ADMINS', [])
 AUTH_PASSWORD_VALIDATORS = getattr(configuration, 'AUTH_PASSWORD_VALIDATORS', [])
@@ -113,6 +120,11 @@ REMOTE_AUTH_GROUP_SEPARATOR = getattr(configuration, 'REMOTE_AUTH_GROUP_SEPARATO
 REPORTS_ROOT = getattr(configuration, 'REPORTS_ROOT', os.path.join(BASE_DIR, 'reports')).rstrip('/')
 RQ_DEFAULT_TIMEOUT = getattr(configuration, 'RQ_DEFAULT_TIMEOUT', 300)
 SCRIPTS_ROOT = getattr(configuration, 'SCRIPTS_ROOT', os.path.join(BASE_DIR, 'scripts')).rstrip('/')
+SENTRY_DSN = getattr(configuration, 'SENTRY_DSN', DEFAULT_SENTRY_DSN)
+SENTRY_ENABLED = getattr(configuration, 'SENTRY_ENABLED', False)
+SENTRY_SAMPLE_RATE = getattr(configuration, 'SENTRY_SAMPLE_RATE', 1.0)
+SENTRY_TRACES_SAMPLE_RATE = getattr(configuration, 'SENTRY_TRACES_SAMPLE_RATE', 0)
+SENTRY_TAGS = getattr(configuration, 'SENTRY_TAGS', {})
 SESSION_FILE_PATH = getattr(configuration, 'SESSION_FILE_PATH', None)
 SESSION_COOKIE_NAME = getattr(configuration, 'SESSION_COOKIE_NAME', 'sessionid')
 SHORT_DATE_FORMAT = getattr(configuration, 'SHORT_DATE_FORMAT', 'Y-m-d')
@@ -428,6 +440,36 @@ EXEMPT_PATHS = (
 )
 
 
+#
+# Sentry
+#
+
+if SENTRY_ENABLED:
+    if not SENTRY_DSN:
+        raise ImproperlyConfigured("SENTRY_ENABLED is True but SENTRY_DSN has not been defined.")
+    # If using the default DSN, force sampling rates
+    if SENTRY_DSN == DEFAULT_SENTRY_DSN:
+        SENTRY_SAMPLE_RATE = 1.0
+        SENTRY_TRACES_SAMPLE_RATE = 0
+    # Initialize the SDK
+    sentry_sdk.init(
+        dsn=SENTRY_DSN,
+        release=VERSION,
+        integrations=[DjangoIntegration()],
+        sample_rate=SENTRY_SAMPLE_RATE,
+        traces_sample_rate=SENTRY_TRACES_SAMPLE_RATE,
+        send_default_pii=True,
+        http_proxy=HTTP_PROXIES.get('http') if HTTP_PROXIES else None,
+        https_proxy=HTTP_PROXIES.get('https') if HTTP_PROXIES else None
+    )
+    # Assign any configured tags
+    for k, v in SENTRY_TAGS.items():
+        sentry_sdk.set_tag(k, v)
+    # If using the default DSN, append a unique deployment ID tag for error correlation
+    if SENTRY_DSN == DEFAULT_SENTRY_DSN:
+        sentry_sdk.set_tag('netbox.deployment_id', DEPLOYMENT_ID)
+
+
 #
 # Django social auth
 #

+ 1 - 0
netbox/netbox/urls.py

@@ -100,4 +100,5 @@ urlpatterns = [
     path('{}'.format(settings.BASE_PATH), include(_patterns))
 ]
 
+handler404 = 'netbox.views.handler_404'
 handler500 = 'netbox.views.server_error'

+ 11 - 3
netbox/netbox/views/__init__.py

@@ -2,7 +2,6 @@ import platform
 import sys
 
 from django.conf import settings
-from django.contrib.contenttypes.models import ContentType
 from django.core.cache import cache
 from django.db.models import F
 from django.http import HttpResponseServerError
@@ -11,9 +10,10 @@ from django.template import loader
 from django.template.exceptions import TemplateDoesNotExist
 from django.urls import reverse
 from django.views.decorators.csrf import requires_csrf_token
-from django.views.defaults import ERROR_500_TEMPLATE_NAME
+from django.views.defaults import ERROR_500_TEMPLATE_NAME, page_not_found
 from django.views.generic import View
 from packaging import version
+from sentry_sdk import capture_message
 
 from circuits.models import Circuit, Provider
 from dcim.models import (
@@ -190,13 +190,21 @@ class StaticMediaFailureView(View):
     """
     Display a user-friendly error message with troubleshooting tips when a static media file fails to load.
     """
-
     def get(self, request):
         return render(request, 'media_failure.html', {
             'filename': request.GET.get('filename')
         })
 
 
+def handler_404(request, exception):
+    """
+    Wrap Django's default 404 handler to enable Sentry reporting.
+    """
+    capture_message("Page not found", level="error")
+
+    return page_not_found(request, exception)
+
+
 @requires_csrf_token
 def server_error(request, template_name=ERROR_500_TEMPLATE_NAME):
     """

+ 3 - 3
netbox/utilities/templatetags/builtins/filters.py

@@ -150,15 +150,15 @@ def render_markdown(value):
     value = strip_tags(value)
 
     # Sanitize Markdown links
-    pattern = fr'\[([^\]]+)\]\((?!({schemes})).*:(.+)\)'
+    pattern = fr'\[([^\]]+)\]\(\s*(?!({schemes})).*:(.+)\)'
     value = re.sub(pattern, '[\\1](\\3)', value, flags=re.IGNORECASE)
 
     # Sanitize Markdown reference links
-    pattern = fr'\[(.+)\]:\s*(?!({schemes}))\w*:(.+)'
+    pattern = fr'\[([^\]]+)\]:\s*(?!({schemes}))\w*:(.+)'
     value = re.sub(pattern, '[\\1]: \\3', value, flags=re.IGNORECASE)
 
     # Render Markdown
-    html = markdown(value, extensions=['fenced_code', 'tables', StrikethroughExtension()])
+    html = markdown(value, extensions=['def_list', 'fenced_code', 'tables', StrikethroughExtension()])
 
     # If the string is not empty wrap it in rendered-markdown to style tables
     if html:

+ 4 - 3
requirements.txt

@@ -1,5 +1,5 @@
 Django==4.0.4
-django-cors-headers==3.11.0
+django-cors-headers==3.12.0
 django-debug-toolbar==3.2.4
 django-filter==21.1
 django-graphiql-debug-toolbar==0.2.0
@@ -16,14 +16,15 @@ drf-yasg[validation]==1.20.0
 graphene-django==2.15.0
 gunicorn==20.1.0
 Jinja2==3.1.2
-Markdown==3.3.6
+Markdown==3.3.7
 markdown-include==0.6.0
-mkdocs-material==8.2.11
+mkdocs-material==8.2.14
 mkdocstrings[python-legacy]==0.18.1
 netaddr==0.8.0
 Pillow==9.1.0
 psycopg2-binary==2.9.3
 PyYAML==6.0
+sentry-sdk==1.5.12
 social-auth-app-django==5.0.0
 social-auth-core==4.2.0
 svgwrite==1.4.2