Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v2.1.1 #1387

Merged
merged 14 commits into from
Aug 2, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 45 additions & 3 deletions docs/configuration/optional-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,53 @@ An API consumer can request an arbitrary number of objects by appending the "lim

---

## NETBOX_USERNAME
## NAPALM_USERNAME

## NETBOX_PASSWORD
## NAPALM_PASSWORD

If provided, NetBox will use these credentials to authenticate against devices when collecting data.
NetBox will use these credentials when authenticating to remote devices via the [NAPALM library](https://napalm-automation.net/), if installed. Both parameters are optional.

Note: If SSH public key authentication has been set up for the system account under which NetBox runs, these parameters are not needed.

---

## NAPALM_ARGS

A dictionary of optional arguments to pass to NAPALM when instantiating a network driver. See the NAPALM documentation for a [complete list of optional arguments](http://napalm.readthedocs.io/en/latest/support/#optional-arguments). An example:

```
NAPALM_ARGS = {
'api_key': '472071a93b60a1bd1fafb401d9f8ef41',
'port': 2222,
}
```

Note: Some platforms (e.g. Cisco IOS) require an argument named `secret` to be passed in addition to the normal password. If desired, you can use the configured `NAPALM_PASSWORD` as the value for this argument:

```
NAPALM_USERNAME = 'username'
NAPALM_PASSWORD = 'MySecretPassword'
NAPALM_ARGS = {
'secret': NAPALM_PASSWORD,
# Include any additional args here
}
```

---

## NAPALM_TIMEOUT

Default: 30 seconds

The amount of time (in seconds) to wait for NAPALM to connect to a device.

---

## NETBOX_USERNAME (Deprecated)

## NETBOX_PASSWORD (Deprecated)

These settings have been deprecated and will be removed in NetBox v2.2. Please use `NAPALM_USERNAME` and `NAPALM_PASSWORD` instead.

---

Expand Down
12 changes: 2 additions & 10 deletions docs/installation/ldap.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ AUTH_LDAP_USER_DN_TEMPLATE = "uid=%(user)s,ou=users,dc=example,dc=com"
# You can map user attributes to Django attributes as so.
AUTH_LDAP_USER_ATTR_MAP = {
"first_name": "givenName",
"last_name": "sn"
"last_name": "sn",
"email": "mail"
}
```

Expand Down Expand Up @@ -108,12 +109,3 @@ AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
* `is_active` - All users must be mapped to at least this group to enable authentication. Without this, users cannot log in.
* `is_staff` - Users mapped to this group are enabled for access to the administration tools; this is the equivalent of checking the "staff status" box on a manually created user. This doesn't grant any specific permissions.
* `is_superuser` - Users mapped to this group will be granted superuser status. Superusers are implicitly granted all permissions.

It is also possible map user attributes to Django attributes:

```python
AUTH_LDAP_USER_ATTR_MAP = {
"first_name": "givenName",
"last_name": "sn",
}
```
13 changes: 7 additions & 6 deletions netbox/dcim/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

from rest_framework.decorators import detail_route
from rest_framework.mixins import ListModelMixin
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ViewSet

Expand All @@ -21,7 +20,7 @@
from extras.api.serializers import RenderedGraphSerializer
from extras.api.views import CustomFieldModelViewSet
from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
from utilities.api import ServiceUnavailable, WritableSerializerMixin
from utilities.api import IsAuthenticatedOrLoginNotRequired, ServiceUnavailable, WritableSerializerMixin
from .exceptions import MissingFilterException
from . import serializers

Expand Down Expand Up @@ -272,15 +271,17 @@ def napalm(self, request, pk):
ip_address = str(device.primary_ip.address.ip)
d = driver(
hostname=ip_address,
username=settings.NETBOX_USERNAME,
password=settings.NETBOX_PASSWORD
username=settings.NAPALM_USERNAME,
password=settings.NAPALM_PASSWORD,
timeout=settings.NAPALM_TIMEOUT,
optional_args=settings.NAPALM_ARGS
)
try:
d.open()
for method in napalm_methods:
response[method] = getattr(d, method)()
except Exception as e:
raise ServiceUnavailable("Error connecting to the device: {}".format(e))
raise ServiceUnavailable("Error connecting to the device at {}: {}".format(ip_address, e))

d.close()
return Response(response)
Expand Down Expand Up @@ -385,7 +386,7 @@ class ConnectedDeviceViewSet(ViewSet):
* `peer-device`: The name of the peer device
* `peer-interface`: The name of the peer interface
"""
permission_classes = [IsAuthenticated]
permission_classes = [IsAuthenticatedOrLoginNotRequired]

def get_view_name(self):
return "Connected Device Locator"
Expand Down
30 changes: 22 additions & 8 deletions netbox/dcim/filters.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from __future__ import unicode_literals

import django_filters
from netaddr import EUI
from netaddr.core import AddrFormatError

from django.contrib.auth.models import User
from django.db.models import Q

from extras.filters import CustomFieldFilterSet
from tenancy.models import Tenant
from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter
from utilities.filters import NullableCharFieldFilter, NullableModelMultipleChoiceFilter, NumericInFilter
from .models import (
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, STATUS_CHOICES, IFACE_FF_LAG, Interface, InterfaceConnection,
Expand Down Expand Up @@ -113,6 +114,7 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
method='search',
label='Search',
)
facility_id = NullableCharFieldFilter()
site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(),
label='Site (ID)',
Expand Down Expand Up @@ -156,7 +158,7 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):

class Meta:
model = Rack
fields = ['facility_id', 'type', 'width', 'u_height', 'desc_units']
fields = ['type', 'width', 'u_height', 'desc_units']

def search(self, queryset, name, value):
if not value.strip():
Expand Down Expand Up @@ -383,6 +385,8 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug',
label='Platform (slug)',
)
name = NullableCharFieldFilter()
asset_tag = NullableCharFieldFilter()
site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(),
label='Site (ID)',
Expand Down Expand Up @@ -439,25 +443,33 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):

class Meta:
model = Device
fields = ['name', 'serial', 'asset_tag']
fields = ['serial']

def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
qs_filter = (
Q(name__icontains=value) |
Q(serial__icontains=value.strip()) |
Q(inventory_items__serial__icontains=value.strip()) |
Q(asset_tag=value.strip()) |
Q(comments__icontains=value)
).distinct()
)
# If the query value looks like a MAC address, search interfaces as well.
try:
mac = EUI(value.strip())
qs_filter |= Q(interfaces__mac_address=mac)
except AddrFormatError:
pass
return queryset.filter(qs_filter).distinct()

def _mac_address(self, queryset, name, value):
value = value.strip()
if not value:
return queryset
try:
return queryset.filter(interfaces__mac_address=value).distinct()
mac = EUI(value.strip())
return queryset.filter(interfaces__mac_address=mac).distinct()
except AddrFormatError:
return queryset.none()

Expand Down Expand Up @@ -569,7 +581,8 @@ def _mac_address(self, queryset, name, value):
if not value:
return queryset
try:
return queryset.filter(mac_address=value)
mac = EUI(value.strip())
return queryset.filter(mac_address=mac)
except AddrFormatError:
return queryset.none()

Expand All @@ -596,10 +609,11 @@ class InventoryItemFilter(DeviceComponentFilterSet):
to_field_name='slug',
label='Manufacturer (slug)',
)
asset_tag = NullableCharFieldFilter()

class Meta:
model = InventoryItem
fields = ['name', 'part_id', 'serial', 'asset_tag', 'discovered']
fields = ['name', 'part_id', 'serial', 'discovered']


class ConsoleConnectionFilter(django_filters.FilterSet):
Expand Down
10 changes: 10 additions & 0 deletions netbox/dcim/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,16 @@ def get_available_units(self, u_height=1, rack_face=None, exclude=list()):

return list(reversed(available_units))

def get_reserved_units(self):
"""
Return a dictionary mapping all reserved units within the rack to their reservation.
"""
reserved_units = {}
for r in self.reservations.all():
for u in r.units:
reserved_units[u] = r
return reserved_units

def get_0u_devices(self):
return self.devices.filter(position=0)

Expand Down
5 changes: 0 additions & 5 deletions netbox/dcim/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,15 +417,10 @@ def get(self, request, pk):
prev_rack = Rack.objects.filter(site=rack.site, name__lt=rack.name).order_by('-name').first()

reservations = RackReservation.objects.filter(rack=rack)
reserved_units = {}
for r in reservations:
for u in r.units:
reserved_units[u] = r

return render(request, 'dcim/rack.html', {
'rack': rack,
'reservations': reservations,
'reserved_units': reserved_units,
'nonracked_devices': nonracked_devices,
'next_rack': next_rack,
'prev_rack': prev_rack,
Expand Down
4 changes: 2 additions & 2 deletions netbox/extras/management/commands/run_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

class Command(BaseCommand):
help = "Update inventory information for specified devices"
username = settings.NETBOX_USERNAME
password = settings.NETBOX_PASSWORD
username = settings.NAPALM_USERNAME
password = settings.NAPALM_PASSWORD

def add_arguments(self, parser):
parser.add_argument('-u', '--username', dest='username', help="Specify the username to use")
Expand Down
4 changes: 2 additions & 2 deletions netbox/netbox/configuration.docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@
MAINTENANCE_MODE = os.environ.get('MAINTENANCE_MODE', False)

# Credentials that NetBox will use to access live devices.
NETBOX_USERNAME = os.environ.get('NETBOX_USERNAME', '')
NETBOX_PASSWORD = os.environ.get('NETBOX_PASSWORD', '')
NAPALM_USERNAME = os.environ.get('NAPALM_USERNAME', '')
NAPALM_PASSWORD = os.environ.get('NAPALM_PASSWORD', '')

# Determine how many objects to display per page within a list. (Default: 50)
PAGINATE_COUNT = os.environ.get('PAGINATE_COUNT', 50)
Expand Down
13 changes: 10 additions & 3 deletions netbox/netbox/configuration.example.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,16 @@
# all objects by specifying "?limit=0".
MAX_PAGE_SIZE = 1000

# Credentials that NetBox will use to access live devices (future use).
NETBOX_USERNAME = ''
NETBOX_PASSWORD = ''
# Credentials that NetBox will uses to authenticate to devices when connecting via NAPALM.
NAPALM_USERNAME = ''
NAPALM_PASSWORD = ''

# NAPALM timeout (in seconds). (Default: 30)
NAPALM_TIMEOUT = 30

# NAPALM optional arguments (see http://napalm.readthedocs.io/en/latest/support/#optional-arguments). Arguments must
# be provided as a dictionary.
NAPALM_ARGS = {}

# Determine how many objects to display per page within a list. (Default: 50)
PAGINATE_COUNT = 50
Expand Down
29 changes: 23 additions & 6 deletions netbox/netbox/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
)


VERSION = '2.1.0'
VERSION = '2.1.1'

# Import required configuration parameters
ALLOWED_HOSTS = DATABASE = SECRET_KEY = None
Expand Down Expand Up @@ -46,8 +46,12 @@
MAX_PAGE_SIZE = getattr(configuration, 'MAX_PAGE_SIZE', 1000)
PAGINATE_COUNT = getattr(configuration, 'PAGINATE_COUNT', 50)
PREFER_IPV4 = getattr(configuration, 'PREFER_IPV4', False)
NETBOX_USERNAME = getattr(configuration, 'NETBOX_USERNAME', '')
NETBOX_PASSWORD = getattr(configuration, 'NETBOX_PASSWORD', '')
NAPALM_USERNAME = getattr(configuration, 'NAPALM_USERNAME', '')
NAPALM_PASSWORD = getattr(configuration, 'NAPALM_PASSWORD', '')
NAPALM_TIMEOUT = getattr(configuration, 'NAPALM_TIMEOUT', 30)
NAPALM_ARGS = getattr(configuration, 'NAPALM_ARGS', {})
NETBOX_USERNAME = getattr(configuration, 'NETBOX_USERNAME', '') # Deprecated
NETBOX_PASSWORD = getattr(configuration, 'NETBOX_PASSWORD', '') # Deprecated
SHORT_DATE_FORMAT = getattr(configuration, 'SHORT_DATE_FORMAT', 'Y-m-d')
SHORT_DATETIME_FORMAT = getattr(configuration, 'SHORT_DATETIME_FORMAT', 'Y-m-d H:i')
SHORT_TIME_FORMAT = getattr(configuration, 'SHORT_TIME_FORMAT', 'H:i:s')
Expand All @@ -56,6 +60,19 @@

CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS

# Check for deprecated configuration parameters
config_logger = logging.getLogger('configuration')
config_logger.addHandler(logging.StreamHandler())
config_logger.setLevel(logging.WARNING)
if NETBOX_USERNAME:
config_logger.warning('NETBOX_USERNAME is deprecated and will be removed in v2.2. Please use NAPALM_USERNAME instead.')
if not NAPALM_USERNAME:
NAPALM_USERNAME = NETBOX_USERNAME
if NETBOX_PASSWORD:
config_logger.warning('NETBOX_PASSWORD is deprecated and will be removed in v2.2. Please use NAPALM_PASSWORD instead.')
if not NAPALM_PASSWORD:
NAPALM_PASSWORD = NETBOX_PASSWORD

# Attempt to import LDAP configuration if it has been defined
LDAP_IGNORE_CERT_ERRORS = False
try:
Expand All @@ -78,9 +95,9 @@
if LDAP_IGNORE_CERT_ERRORS:
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
# Enable logging for django_auth_ldap
logger = logging.getLogger('django_auth_ldap')
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.DEBUG)
ldap_logger = logging.getLogger('django_auth_ldap')
ldap_logger.addHandler(logging.StreamHandler())
ldap_logger.setLevel(logging.DEBUG)
except ImportError:
raise ImproperlyConfigured(
"LDAP authentication has been configured, but django-auth-ldap is not installed. You can remove "
Expand Down
4 changes: 1 addition & 3 deletions netbox/templates/circuits/circuit.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
{% extends '_base.html' %}
{% load helpers %}

{% block title %}{{ circuit.provider }} - {{ circuit.cid }}{% endblock %}

{% block content %}
<div class="row">
<div class="col-sm-8 col-md-9">
Expand Down Expand Up @@ -39,7 +37,7 @@
</a>
{% endif %}
</div>
<h1>{{ circuit.provider }} - {{ circuit.cid }}</h1>
<h1>{% block title %}{{ circuit.provider }} - {{ circuit.cid }}{% endblock %}</h1>
{% include 'inc/created_updated.html' with obj=circuit %}
<div class="row">
<div class="col-md-6">
Expand Down
4 changes: 1 addition & 3 deletions netbox/templates/circuits/circuit_list.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
{% extends '_base.html' %}
{% load helpers %}

{% block title %}Circuits{% endblock %}

{% block content %}
<div class="pull-right">
{% if perms.circuits.add_circuit %}
Expand All @@ -17,7 +15,7 @@
{% endif %}
{% include 'inc/export_button.html' with obj_type='circuits' %}
</div>
<h1>Circuits</h1>
<h1>{% block title %}Circuits{% endblock %}</h1>
<div class="row">
<div class="col-md-9">
{% include 'utilities/obj_table.html' with bulk_edit_url='circuits:circuit_bulk_edit' bulk_delete_url='circuits:circuit_bulk_delete' %}
Expand Down
Loading