feat: add tenancy support for software licenses
Add tenant group and tenant assignments across the model, forms, filters, API, tables, imports, and tests. Replace the Docker-based setup with Git/Pip installation documentation for NetBox 4.6.5.
This commit is contained in:
@@ -1,43 +0,0 @@
|
||||
---
|
||||
name: Docker Image CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Build the Docker image
|
||||
run: docker compose build --no-cache
|
||||
|
||||
- name: Verify the Docker image
|
||||
# smoke test by checking if the migrations for app netbox_slm can be displayed
|
||||
run: docker compose run netbox sh -c "/opt/netbox/venv/bin/python manage.py showmigrations netbox_slm"
|
||||
|
||||
- name: Run Django tests with coverage for netbox_slm
|
||||
run: |
|
||||
chmod go+w ci/reports
|
||||
docker compose run netbox sh -c "\
|
||||
/opt/netbox/venv/bin/coverage run --source='netbox_slm' manage.py test netbox_slm &&\
|
||||
/opt/netbox/venv/bin/coverage report --fail-under=0 &&\
|
||||
/opt/netbox/venv/bin/coverage xml -o /ci/reports/coverage.xml"
|
||||
sed -i "s/<source>\/opt\/netbox\/netbox<\/source>/<source><\/source>/" ci/reports/coverage.xml
|
||||
timeout-minutes: 6
|
||||
|
||||
- name: Sonar scan
|
||||
if: env.SONAR_TOKEN != null
|
||||
uses: SonarSource/sonarqube-scan-action@713881670b6b3676cda39549040e2d88c70d582e # v8.2.0
|
||||
env:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
@@ -2,6 +2,15 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
* Optional tenant group and tenant assignments for software licenses
|
||||
|
||||
### Changed
|
||||
|
||||
* Support NetBox v4.6.5
|
||||
* Replace the Docker-based setup with Git/Pip installation documentation
|
||||
|
||||
## [1.9.0](https://github.com/ICTU/netbox_slm/releases/tag/1.9.0) - 2026-06-25
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -1,60 +1,116 @@
|
||||
# NetBox SLM
|
||||
|
||||
<p align="center"><i>NetBox SLM is a plugin for lifecycle management of software components, including versions and installations.</i></p>
|
||||
NetBox SLM erweitert NetBox um die Verwaltung von Softwareprodukten, Versionen, Installationen und Lizenzen.
|
||||
Lizenzen können optional einer NetBox-Mandantengruppe und einem Mandanten zugeordnet werden.
|
||||
|
||||
<div align="center">
|
||||
<a href="https://pypi.org/project/netbox_slm/"><img src="https://img.shields.io/pypi/v/netbox_slm" alt="PyPi"/></a>
|
||||
<a href="https://github.com/ICTU/netbox_slm/stargazers"><img src="https://img.shields.io/github/stars/ICTU/netbox_slm" alt="Stars Badge"/></a>
|
||||
<a href="https://github.com/ICTU/netbox_slm/network/members"><img src="https://img.shields.io/github/forks/ICTU/netbox_slm" alt="Forks Badge"/></a>
|
||||
<a href="https://github.com/ICTU/netbox_slm/pulls"><img src="https://img.shields.io/github/issues-pr/ICTU/netbox_slm" alt="Pull Requests Badge"/></a>
|
||||
<a href="https://github.com/ICTU/netbox_slm/issues"><img src="https://img.shields.io/github/issues/ICTU/netbox_slm" alt="Issues Badge"/></a>
|
||||
<a href="https://github.com/ICTU/netbox_slm/graphs/contributors"><img alt="GitHub contributors" src="https://img.shields.io/github/contributors/ICTU/netbox_slm?color=2b9348"></a>
|
||||
<a href="https://github.com/ICTU/netbox_slm/blob/master/LICENSE"><img src="https://img.shields.io/github/license/ICTU/netbox_slm?color=2b9348" alt="License Badge"/></a>
|
||||
</div>
|
||||
Diese Variante ist für **NetBox 4.6.5** vorgesehen.
|
||||
|
||||
## Funktionen
|
||||
|
||||
## Installation Guide
|
||||
- Softwareprodukte und Hersteller verwalten
|
||||
- Versionen, Release-Typen und Supportzeiträume erfassen
|
||||
- Installationen Geräten, virtuellen Maschinen oder Clustern zuordnen
|
||||
- Softwarelizenzen einschließlich Laufzeit, Umfang und Ablageort verwalten
|
||||
- Lizenzen Mandantengruppen und Mandanten zuordnen
|
||||
- Bedienung über die NetBox-Oberfläche und REST-API
|
||||
- Filter, Bulk-Import und Bulk-Bearbeitung
|
||||
|
||||
When using the Docker version of NetBox, first follow the `netbox-docker` [quickstart](https://github.com/netbox-community/netbox-docker#quickstart) instructions to clone the `netbox-docker` repository and set up the ``docker-compose.override.yml``.
|
||||
## Installation
|
||||
|
||||
Note that this plugin is only tested against a single NetBox version at this time, see [Dockerfile-CI](https://github.com/ICTU/netbox_slm/blob/master/ci/Dockerfile-CI).
|
||||
NetBox installiert lokale Erweiterungen aus `/opt/netbox/local_requirements.txt`. Die Git-URL muss dort dauerhaft
|
||||
eingetragen werden, damit das Plugin bei einem späteren NetBox-Upgrade erneut installiert wird.
|
||||
|
||||
Next, follow these instructions (based on the NetBox docker variant [instructions](https://github.com/netbox-community/netbox-docker/wiki/Configuration#custom-configuration-files)) to install the NetBox SLM plugin:
|
||||
### 1. Git-Repository als lokale Anforderung eintragen
|
||||
|
||||
1. Add ``netbox_slm`` to the ``PLUGINS`` list in
|
||||
``configuration/plugins.py``.
|
||||
2. Create a ``plugin_requirements.txt`` with ``netbox-slm`` as
|
||||
contents.
|
||||
3. Create a ``Dockerfile-SLM`` with contents:
|
||||
|
||||
```dockerfile
|
||||
FROM netboxcommunity/netbox:vX.Y.Z
|
||||
|
||||
COPY ../pyproject.toml /tmp/
|
||||
RUN uv pip install -r /tmp/pyproject.toml
|
||||
```bash
|
||||
echo "git+https://git.mrblake.cc/MrBlake/NetBox-SLM.git" >> /opt/netbox/local_requirements.txt
|
||||
```
|
||||
|
||||
4. Create a ``docker-compose.override.yml`` with contents:
|
||||
Vorher kann geprüft werden, ob der Eintrag bereits vorhanden ist:
|
||||
|
||||
```yaml
|
||||
version: "3.7"
|
||||
services:
|
||||
netbox:
|
||||
ports:
|
||||
- "8000:8080"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile-SLM
|
||||
image: netbox:slm
|
||||
netbox-worker:
|
||||
image: netbox:slm
|
||||
```bash
|
||||
grep -qxF "git+https://git.mrblake.cc/MrBlake/NetBox-SLM.git" /opt/netbox/local_requirements.txt || \
|
||||
echo "git+https://git.mrblake.cc/MrBlake/NetBox-SLM.git" >> /opt/netbox/local_requirements.txt
|
||||
```
|
||||
|
||||
Now, build the image: ``docker compose build --no-cache``
|
||||
### 2. Plugin mit Pip installieren
|
||||
|
||||
And finally, run NetBox with the SLM plugin: ``docker compose up``
|
||||
Die Installation muss im Python-Virtual-Environment von NetBox erfolgen:
|
||||
|
||||
```bash
|
||||
/opt/netbox/venv/bin/pip install "git+https://git.mrblake.cc/MrBlake/NetBox-SLM.git"
|
||||
```
|
||||
|
||||
## Get in touch
|
||||
Alternativ können alle lokalen Anforderungen installiert werden:
|
||||
|
||||
Point of contact for this repository is [Mart Visser](https://github.com/MartVisser), who can be reached by [opening a new issue in this repository's issue tracker](https://github.com/ICTU/netbox_slm/issues/new).
|
||||
```bash
|
||||
/opt/netbox/venv/bin/pip install -r /opt/netbox/local_requirements.txt
|
||||
```
|
||||
|
||||
### 3. Plugin in NetBox aktivieren
|
||||
|
||||
In `/opt/netbox/netbox/netbox/configuration.py` ergänzen:
|
||||
|
||||
```python
|
||||
PLUGINS = [
|
||||
"netbox_slm",
|
||||
]
|
||||
```
|
||||
|
||||
Falls bereits andere Plugins eingetragen sind, wird `"netbox_slm"` lediglich zur vorhandenen Liste hinzugefügt.
|
||||
Eine zusätzliche `PLUGINS_CONFIG`-Konfiguration ist für den Standardbetrieb nicht erforderlich.
|
||||
|
||||
### 4. Datenbank migrieren und statische Dateien aktualisieren
|
||||
|
||||
```bash
|
||||
cd /opt/netbox/netbox
|
||||
/opt/netbox/venv/bin/python manage.py migrate
|
||||
/opt/netbox/venv/bin/python manage.py collectstatic --no-input
|
||||
```
|
||||
|
||||
### 5. NetBox-Dienste neu starten
|
||||
|
||||
Bei einer üblichen systemd-Installation:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart netbox netbox-rq
|
||||
```
|
||||
|
||||
Je nach Installation können die Namen der Dienste abweichen.
|
||||
|
||||
## Aktualisierung
|
||||
|
||||
```bash
|
||||
/opt/netbox/venv/bin/pip install --upgrade --force-reinstall \
|
||||
"git+https://git.mrblake.cc/MrBlake/NetBox-SLM.git"
|
||||
cd /opt/netbox/netbox
|
||||
/opt/netbox/venv/bin/python manage.py migrate
|
||||
/opt/netbox/venv/bin/python manage.py collectstatic --no-input
|
||||
sudo systemctl restart netbox netbox-rq
|
||||
```
|
||||
|
||||
Der Eintrag in `/opt/netbox/local_requirements.txt` bleibt dabei bestehen.
|
||||
|
||||
## Deinstallation
|
||||
|
||||
1. `"netbox_slm"` aus der `PLUGINS`-Liste entfernen.
|
||||
2. Die Git-Zeile aus `/opt/netbox/local_requirements.txt` entfernen.
|
||||
3. Das Python-Paket deinstallieren:
|
||||
|
||||
```bash
|
||||
/opt/netbox/venv/bin/pip uninstall netbox-slm
|
||||
```
|
||||
|
||||
Die Plugin-Tabellen werden dabei nicht automatisch aus der Datenbank gelöscht.
|
||||
|
||||
## Ursprung und Lizenz
|
||||
|
||||
Dieses Repository basiert auf dem ursprünglichen Projekt
|
||||
[ICTU/netbox_slm](https://github.com/ICTU/netbox_slm). **ICTU** ist der ursprüngliche Autor und Herausgeber.
|
||||
[Mart Visser](https://github.com/MartVisser) wird im Originalprojekt als Ansprechpartner genannt.
|
||||
|
||||
Die Anpassungen in diesem Fork werden unter
|
||||
[git.mrblake.cc/MrBlake/NetBox-SLM](https://git.mrblake.cc/MrBlake/NetBox-SLM) gepflegt.
|
||||
|
||||
Das Projekt steht unter der [Apache License 2.0](LICENSE). Die Hinweise auf die ursprünglichen Urheber bleiben
|
||||
erhalten.
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
StylesPath = styles
|
||||
|
||||
MinAlertLevel = suggestion
|
||||
Vocab = Base
|
||||
|
||||
Packages = proselint
|
||||
|
||||
[*.md]
|
||||
BasedOnStyles = Vale, proselint
|
||||
|
||||
[ci/styles/**]
|
||||
BasedOnStyles =
|
||||
@@ -1,18 +0,0 @@
|
||||
ARG PYTHON_VERSION=3.14
|
||||
FROM python:${PYTHON_VERSION}-alpine AS compile-image
|
||||
# Python version build arg is not actually supported by NetBox container image, only used for maintenance convenience
|
||||
|
||||
WORKDIR /build
|
||||
RUN python3 -m venv /opt/netbox/venv &&\
|
||||
source /opt/netbox/venv/bin/activate
|
||||
|
||||
COPY ../ /build
|
||||
|
||||
RUN pip install -U .[build] &&\
|
||||
python -m build &&\
|
||||
pip install --no-index /build
|
||||
|
||||
FROM netboxcommunity/netbox:v4.6.3
|
||||
ARG PYTHON_VERSION=3.14
|
||||
|
||||
COPY --from=compile-image /opt/netbox/venv/lib/python${PYTHON_VERSION}/site-packages/netbox_slm /opt/netbox/venv/lib/python${PYTHON_VERSION}/site-packages/netbox_slm
|
||||
@@ -1,5 +0,0 @@
|
||||
FROM netboxcommunity/netbox:v4.6.3
|
||||
ARG PYTHON_VERSION=3.14
|
||||
|
||||
COPY ../pyproject.toml /tmp/
|
||||
RUN uv pip install -r /tmp/pyproject.toml --extra ci
|
||||
@@ -1,48 +0,0 @@
|
||||
"""
|
||||
Minimal NetBox config for NetBox SLM plugin testing, docs: https://netbox.readthedocs.io/en/stable/configuration/
|
||||
Based on https://github.com/netbox-community/netbox/blob/v4.6.3/netbox/netbox/configuration_testing.py
|
||||
"""
|
||||
from os import environ
|
||||
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'NAME': environ.get('DB_NAME', 'netbox'),
|
||||
'USER': environ.get('DB_USER', ''),
|
||||
'PASSWORD': environ.get('DB_PASSWORD', ''),
|
||||
'HOST': environ.get('DB_HOST', 'localhost'),
|
||||
'PORT': environ.get('DB_PORT', ''),
|
||||
'CONN_MAX_AGE': int(environ.get('DB_CONN_MAX_AGE', '300')),
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG = environ.get('DEBUG', 'False').lower() == 'true'
|
||||
DEVELOPER = environ.get('DEVELOPER', 'False').lower() == 'true'
|
||||
|
||||
PLUGINS = ["netbox_slm"]
|
||||
|
||||
REDIS = {
|
||||
'tasks': {
|
||||
'HOST': environ.get('REDIS_HOST', 'localhost'),
|
||||
'PORT': int(environ.get('REDIS_PORT', 6379)),
|
||||
'PASSWORD': environ.get('REDIS_PASSWORD', ''),
|
||||
'DATABASE': int(environ.get('REDIS_DATABASE', 0)),
|
||||
'SSL': environ.get('REDIS_SSL', 'False').lower() == 'true',
|
||||
},
|
||||
'caching': {
|
||||
'HOST': environ.get('REDIS_CACHE_HOST', environ.get('REDIS_HOST', 'localhost')),
|
||||
'PORT': int(environ.get('REDIS_CACHE_PORT', environ.get('REDIS_PORT', 6379))),
|
||||
'PASSWORD': environ.get('REDIS_CACHE_PASSWORD', environ.get('REDIS_PASSWORD', '')),
|
||||
'DATABASE': int(environ.get('REDIS_CACHE_DATABASE', 1)),
|
||||
'SSL': environ.get('REDIS_CACHE_SSL', environ.get('REDIS_SSL', 'False')).lower() == 'true',
|
||||
},
|
||||
}
|
||||
|
||||
SECRET_KEY = 'dummydummydummydummydummydummydummydummydummydummy'
|
||||
|
||||
DEFAULT_PERMISSIONS = {}
|
||||
|
||||
API_TOKEN_PEPPERS = {
|
||||
1: 'TEST-VALUE-DO-NOT-USE-TEST-VALUE-DO-NOT-USE-TEST-VALUE-DO-NOT-USE',
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
services:
|
||||
netbox:
|
||||
ports:
|
||||
- "8001:8080"
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: ci/Dockerfile-CI
|
||||
image: netbox:slm
|
||||
environment:
|
||||
- COVERAGE_FILE=/tmp/.coverage
|
||||
healthcheck:
|
||||
disable: true
|
||||
volumes:
|
||||
- ./config:/etc/netbox/config:z,ro
|
||||
- ./reports:/ci/reports:rw
|
||||
- ../netbox_slm:/opt/netbox/netbox/netbox_slm:ro
|
||||
netbox-worker:
|
||||
image: netbox:slm
|
||||
depends_on:
|
||||
- netbox
|
||||
healthcheck:
|
||||
disable: true
|
||||
volumes:
|
||||
- ./config:/etc/netbox/config:z,ro
|
||||
- ../netbox_slm:/opt/netbox/netbox/netbox_slm:ro
|
||||
postgres:
|
||||
ports:
|
||||
- "5432:5432"
|
||||
redis:
|
||||
ports:
|
||||
- "6379:6379"
|
||||
@@ -1,89 +0,0 @@
|
||||
---
|
||||
# based on https://github.com/netbox-community/netbox-docker/blob/5.0.1/docker-compose.yml
|
||||
services:
|
||||
netbox: &netbox
|
||||
image: docker.io/netboxcommunity/netbox:${VERSION-v4.6.3}
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
- redis-cache
|
||||
env_file: env/netbox.env
|
||||
user: "netbox:root"
|
||||
healthcheck:
|
||||
test: curl -f http://localhost:8080/login/ || exit 1
|
||||
start_period: 90s
|
||||
timeout: 3s
|
||||
interval: 15s
|
||||
volumes:
|
||||
- ./config:/etc/netbox/config:z,ro
|
||||
- netbox-media-files:/opt/netbox/netbox/media:rw
|
||||
- netbox-reports-files:/opt/netbox/netbox/reports:rw
|
||||
- netbox-scripts-files:/opt/netbox/netbox/scripts:rw
|
||||
netbox-worker:
|
||||
<<: *netbox
|
||||
depends_on:
|
||||
netbox:
|
||||
condition: service_healthy
|
||||
command:
|
||||
- /opt/netbox/venv/bin/python
|
||||
- /opt/netbox/netbox/manage.py
|
||||
- rqworker
|
||||
healthcheck:
|
||||
test: ps -aux | grep -v grep | grep -q rqworker || exit 1
|
||||
start_period: 20s
|
||||
timeout: 3s
|
||||
interval: 15s
|
||||
|
||||
# postgres
|
||||
postgres:
|
||||
image: docker.io/postgres:18-alpine
|
||||
healthcheck:
|
||||
test: pg_isready -q -t 2 -d $$POSTGRES_DB -U $$POSTGRES_USER
|
||||
start_period: 20s
|
||||
timeout: 30s
|
||||
interval: 10s
|
||||
retries: 5
|
||||
env_file: env/postgres.env
|
||||
volumes:
|
||||
- netbox-postgres:/var/lib/postgresql
|
||||
|
||||
# redis
|
||||
redis:
|
||||
image: docker.io/valkey/valkey:9.0-alpine
|
||||
command:
|
||||
- sh
|
||||
- -c # this is to evaluate the $REDIS_PASSWORD from the env
|
||||
- valkey-server --appendonly yes --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose
|
||||
healthcheck: &redis-healthcheck
|
||||
test: '[ $$(valkey-cli --pass "$${REDIS_PASSWORD}" ping) = ''PONG'' ]'
|
||||
start_period: 5s
|
||||
timeout: 3s
|
||||
interval: 1s
|
||||
retries: 5
|
||||
env_file: env/redis.env
|
||||
volumes:
|
||||
- netbox-redis-data:/data
|
||||
redis-cache:
|
||||
image: docker.io/valkey/valkey:9.0-alpine
|
||||
command:
|
||||
- sh
|
||||
- -c # this is to evaluate the $REDIS_PASSWORD from the env
|
||||
- valkey-server --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose
|
||||
healthcheck: *redis-healthcheck
|
||||
env_file: env/redis-cache.env
|
||||
volumes:
|
||||
- netbox-redis-cache-data:/data
|
||||
|
||||
volumes:
|
||||
netbox-media-files:
|
||||
driver: local
|
||||
netbox-postgres:
|
||||
driver: local
|
||||
netbox-redis-cache-data:
|
||||
driver: local
|
||||
netbox-redis-data:
|
||||
driver: local
|
||||
netbox-reports-files:
|
||||
driver: local
|
||||
netbox-scripts-files:
|
||||
driver: local
|
||||
Vendored
-42
@@ -1,42 +0,0 @@
|
||||
CORS_ORIGIN_ALLOW_ALL=True
|
||||
DB_HOST=postgres
|
||||
DB_NAME=netbox
|
||||
DB_PASSWORD=J5brHrAXFLQSif0K
|
||||
DB_USER=netbox
|
||||
EMAIL_FROM=netbox@bar.com
|
||||
EMAIL_PASSWORD=
|
||||
EMAIL_PORT=25
|
||||
EMAIL_SERVER=localhost
|
||||
EMAIL_SSL_CERTFILE=
|
||||
EMAIL_SSL_KEYFILE=
|
||||
EMAIL_TIMEOUT=5
|
||||
EMAIL_USERNAME=netbox
|
||||
# EMAIL_USE_SSL and EMAIL_USE_TLS are mutually exclusive, i.e. they can't both be `true`!
|
||||
EMAIL_USE_SSL=false
|
||||
EMAIL_USE_TLS=false
|
||||
GRAPHQL_ENABLED=true
|
||||
HOUSEKEEPING_INTERVAL=86400
|
||||
MAX_PAGE_SIZE=1000
|
||||
MEDIA_ROOT=/opt/netbox/netbox/media
|
||||
METRICS_ENABLED=false
|
||||
NAPALM_PASSWORD=
|
||||
NAPALM_TIMEOUT=10
|
||||
NAPALM_USERNAME=
|
||||
REDIS_CACHE_DATABASE=1
|
||||
REDIS_CACHE_HOST=redis-cache
|
||||
REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY=false
|
||||
REDIS_CACHE_PASSWORD=t4Ph722qJ5QHeQ1qfu36
|
||||
REDIS_CACHE_SSL=false
|
||||
REDIS_DATABASE=0
|
||||
REDIS_HOST=redis
|
||||
REDIS_INSECURE_SKIP_TLS_VERIFY=false
|
||||
REDIS_PASSWORD=H733Kdjndks81
|
||||
REDIS_SSL=false
|
||||
RELEASE_CHECK_URL=https://api.github.com/repos/netbox-community/netbox/releases
|
||||
SECRET_KEY=r8OwDznj!!dci#P9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNj
|
||||
SKIP_SUPERUSER=false
|
||||
SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567
|
||||
SUPERUSER_EMAIL=admin@example.com
|
||||
SUPERUSER_NAME=admin
|
||||
SUPERUSER_PASSWORD=admin
|
||||
WEBHOOKS_ENABLED=true
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
POSTGRES_DB=netbox
|
||||
POSTGRES_PASSWORD=J5brHrAXFLQSif0K
|
||||
POSTGRES_USER=netbox
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
REDIS_PASSWORD=t4Ph722qJ5QHeQ1qfu36
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
REDIS_PASSWORD=H733Kdjndks81
|
||||
@@ -1,66 +0,0 @@
|
||||
Anchore
|
||||
APIs
|
||||
Caddy
|
||||
Checkmarx
|
||||
Cobertura
|
||||
Dependabot
|
||||
Docker-composition
|
||||
Dockerfile
|
||||
DTDs
|
||||
ESLint
|
||||
Gravatar
|
||||
Jira
|
||||
JMeter
|
||||
JUnit
|
||||
NCover
|
||||
Nginx
|
||||
OJAudit
|
||||
OpenShift
|
||||
PDFs
|
||||
Pydantic
|
||||
Robocop
|
||||
Snyk
|
||||
Trello
|
||||
Trivy
|
||||
UUIDs
|
||||
Wekan
|
||||
[Hh]ostname
|
||||
[Uu]nmerged
|
||||
asyncio
|
||||
autoformatting
|
||||
breakpoint
|
||||
clearable
|
||||
cloc
|
||||
discoverability
|
||||
donut
|
||||
errored
|
||||
favicon
|
||||
fixme
|
||||
hostnames?
|
||||
hotspots?
|
||||
lookback
|
||||
misconfigured
|
||||
mypy
|
||||
namespace
|
||||
npm
|
||||
parameterizable
|
||||
phpldapadmin
|
||||
[Pp]erformancetest
|
||||
severities
|
||||
sparkline
|
||||
subfolders
|
||||
submenus
|
||||
suppressions
|
||||
todo
|
||||
tooltips?
|
||||
tracebacks?
|
||||
unencrypted
|
||||
unicode
|
||||
unmerged
|
||||
upvotes
|
||||
url
|
||||
xml
|
||||
NetBox
|
||||
NetBox SLM
|
||||
Mart
|
||||
Visser
|
||||
@@ -1,3 +0,0 @@
|
||||
docker composition
|
||||
Docker composition
|
||||
docker-composition
|
||||
@@ -1,3 +1,4 @@
|
||||
from rest_framework import serializers
|
||||
from rest_framework.serializers import SerializerMethodField, HyperlinkedIdentityField
|
||||
|
||||
from netbox.api.serializers import NetBoxModelSerializer
|
||||
@@ -27,6 +28,8 @@ class SoftwareLicenseSerializer(NetBoxModelSerializer):
|
||||
"software_product",
|
||||
"version",
|
||||
"installation",
|
||||
"tenant_group",
|
||||
"tenant",
|
||||
"tags",
|
||||
"comments",
|
||||
"custom_field_data",
|
||||
@@ -38,6 +41,15 @@ class SoftwareLicenseSerializer(NetBoxModelSerializer):
|
||||
def get_display(self, obj):
|
||||
return f"{obj}"
|
||||
|
||||
def validate(self, attrs):
|
||||
tenant_group = attrs.get("tenant_group", getattr(self.instance, "tenant_group", None))
|
||||
tenant = attrs.get("tenant", getattr(self.instance, "tenant", None))
|
||||
if tenant_group and tenant and tenant.group_id != tenant_group.pk:
|
||||
raise serializers.ValidationError(
|
||||
{"tenant": "The selected tenant does not belong to the selected tenant group."}
|
||||
)
|
||||
return attrs
|
||||
|
||||
|
||||
class SoftwareProductSerializer(NetBoxModelSerializer):
|
||||
display = SerializerMethodField()
|
||||
|
||||
@@ -11,6 +11,7 @@ from netbox_slm.models import (
|
||||
SoftwareReleaseTypes,
|
||||
)
|
||||
from virtualization.models import Cluster, VirtualMachine
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
|
||||
|
||||
class SoftwareProductFilterSet(NetBoxModelFilterSet):
|
||||
@@ -122,6 +123,8 @@ class SoftwareLicenseFilterSet(NetBoxModelFilterSet):
|
||||
)
|
||||
version_id = ModelMultipleChoiceFilter(queryset=SoftwareProductVersion.objects.all())
|
||||
installation_id = ModelMultipleChoiceFilter(queryset=SoftwareProductInstallation.objects.all())
|
||||
tenant_group_id = ModelMultipleChoiceFilter(queryset=TenantGroup.objects.all(), label="Tenant Group")
|
||||
tenant_id = ModelMultipleChoiceFilter(queryset=Tenant.objects.all(), label="Tenant")
|
||||
|
||||
class Meta:
|
||||
model = SoftwareLicense
|
||||
@@ -139,6 +142,8 @@ class SoftwareLicenseFilterSet(NetBoxModelFilterSet):
|
||||
| Q(installation__device__name__icontains=value)
|
||||
| Q(installation__virtualmachine__name__icontains=value)
|
||||
| Q(installation__cluster__name__icontains=value)
|
||||
| Q(tenant_group__name__icontains=value)
|
||||
| Q(tenant__name__icontains=value)
|
||||
| Q(comments__icontains=value)
|
||||
)
|
||||
return queryset.filter(qs_filter)
|
||||
|
||||
@@ -9,6 +9,7 @@ from netbox_slm.models import (
|
||||
SoftwareLicense,
|
||||
spdx_license_names,
|
||||
)
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from utilities.forms.constants import BOOLEAN_WITH_BLANK_CHOICES
|
||||
from utilities.forms.fields import (
|
||||
CommentField,
|
||||
@@ -48,6 +49,17 @@ class SoftwareLicenseForm(NetBoxModelForm):
|
||||
),
|
||||
query_params=dict(software_product="$software_product"),
|
||||
)
|
||||
tenant_group = DynamicModelChoiceField(
|
||||
queryset=TenantGroup.objects.all(),
|
||||
required=False,
|
||||
label="Tenant Group",
|
||||
)
|
||||
tenant = DynamicModelChoiceField(
|
||||
queryset=Tenant.objects.all(),
|
||||
required=False,
|
||||
query_params={"group_id": "$tenant_group"},
|
||||
label="Tenant",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = SoftwareLicense
|
||||
@@ -65,6 +77,8 @@ class SoftwareLicenseForm(NetBoxModelForm):
|
||||
"license_amount",
|
||||
"version",
|
||||
"installation",
|
||||
"tenant_group",
|
||||
"tenant",
|
||||
"tags",
|
||||
"comments",
|
||||
)
|
||||
@@ -84,6 +98,8 @@ class SoftwareLicenseFilterForm(NetBoxModelFilterSetForm):
|
||||
"software_product_id",
|
||||
"version_id",
|
||||
"installation_id",
|
||||
"tenant_group_id",
|
||||
"tenant_id",
|
||||
),
|
||||
)
|
||||
selector_fields = ("q", "filter_id", "name")
|
||||
@@ -112,6 +128,17 @@ class SoftwareLicenseFilterForm(NetBoxModelFilterSetForm):
|
||||
required=False,
|
||||
label="Installation",
|
||||
)
|
||||
tenant_group_id = DynamicModelMultipleChoiceField(
|
||||
queryset=TenantGroup.objects.all(),
|
||||
required=False,
|
||||
label="Tenant Group",
|
||||
)
|
||||
tenant_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Tenant.objects.all(),
|
||||
required=False,
|
||||
query_params={"group_id": "$tenant_group_id"},
|
||||
label="Tenant",
|
||||
)
|
||||
|
||||
|
||||
class SoftwareLicenseBulkImportForm(NetBoxModelImportForm):
|
||||
@@ -132,6 +159,8 @@ class SoftwareLicenseBulkImportForm(NetBoxModelImportForm):
|
||||
"license_amount",
|
||||
"version",
|
||||
"installation",
|
||||
"tenant_group",
|
||||
"tenant",
|
||||
"tags",
|
||||
)
|
||||
|
||||
@@ -151,6 +180,8 @@ class SoftwareLicenseBulkEditForm(NetBoxModelBulkEditForm):
|
||||
"software_product",
|
||||
"version",
|
||||
"installation",
|
||||
"tenant_group",
|
||||
"tenant",
|
||||
),
|
||||
)
|
||||
nullable_fields = (
|
||||
@@ -164,6 +195,8 @@ class SoftwareLicenseBulkEditForm(NetBoxModelBulkEditForm):
|
||||
"license_amount",
|
||||
"version",
|
||||
"installation",
|
||||
"tenant_group",
|
||||
"tenant",
|
||||
)
|
||||
|
||||
tag = TagFilterField(model)
|
||||
@@ -197,3 +230,14 @@ class SoftwareLicenseBulkEditForm(NetBoxModelBulkEditForm):
|
||||
),
|
||||
query_params=dict(software_product="$software_product"),
|
||||
)
|
||||
tenant_group = DynamicModelChoiceField(
|
||||
queryset=TenantGroup.objects.all(),
|
||||
required=False,
|
||||
label="Tenant Group",
|
||||
)
|
||||
tenant = DynamicModelChoiceField(
|
||||
queryset=Tenant.objects.all(),
|
||||
required=False,
|
||||
query_params={"group_id": "$tenant_group"},
|
||||
label="Tenant",
|
||||
)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("tenancy", "0001_initial"),
|
||||
("netbox_slm", "0010_softwarelicense_spdx_expression"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="softwarelicense",
|
||||
name="tenant_group",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="software_licenses",
|
||||
to="tenancy.tenantgroup",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="softwarelicense",
|
||||
name="tenant",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="software_licenses",
|
||||
to="tenancy.tenant",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -163,6 +163,20 @@ class SoftwareLicense(NetBoxModel):
|
||||
installation = models.ForeignKey(
|
||||
to="netbox_slm.SoftwareProductInstallation", on_delete=models.SET_NULL, null=True, blank=True
|
||||
)
|
||||
tenant_group = models.ForeignKey(
|
||||
to="tenancy.TenantGroup",
|
||||
on_delete=models.PROTECT,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="software_licenses",
|
||||
)
|
||||
tenant = models.ForeignKey(
|
||||
to="tenancy.Tenant",
|
||||
on_delete=models.PROTECT,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="software_licenses",
|
||||
)
|
||||
|
||||
objects = RestrictedQuerySet.as_manager()
|
||||
|
||||
@@ -172,6 +186,11 @@ class SoftwareLicense(NetBoxModel):
|
||||
def get_absolute_url(self):
|
||||
return reverse("plugins:netbox_slm:softwarelicense", kwargs={"pk": self.pk})
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
if self.tenant_group and self.tenant and self.tenant.group_id != self.tenant_group_id:
|
||||
raise ValidationError({"tenant": "The selected tenant does not belong to the selected tenant group."})
|
||||
|
||||
@property
|
||||
def stored_location_txt(self):
|
||||
if self.stored_location_url and not self.stored_location:
|
||||
|
||||
@@ -144,6 +144,8 @@ class SoftwareLicenseTable(NetBoxTable):
|
||||
software_product = tables.Column(accessor="software_product", verbose_name="Software Product", linkify=True)
|
||||
version = tables.Column(accessor="version", linkify=True)
|
||||
installation = tables.Column(accessor="installation", linkify=True)
|
||||
tenant_group = tables.Column(accessor="tenant_group", verbose_name="Tenant Group", linkify=True)
|
||||
tenant = tables.Column(accessor="tenant", linkify=True)
|
||||
|
||||
tags = columns.TagColumn(url_name="plugins:netbox_slm:softwarelicense_list")
|
||||
|
||||
@@ -162,6 +164,8 @@ class SoftwareLicenseTable(NetBoxTable):
|
||||
"software_product",
|
||||
"version",
|
||||
"installation",
|
||||
"tenant_group",
|
||||
"tenant",
|
||||
"support",
|
||||
"license_amount",
|
||||
"tags",
|
||||
@@ -173,6 +177,8 @@ class SoftwareLicenseTable(NetBoxTable):
|
||||
"manufacturer",
|
||||
"software_product",
|
||||
"installation",
|
||||
"tenant_group",
|
||||
"tenant",
|
||||
"expiration_date",
|
||||
"tags",
|
||||
)
|
||||
|
||||
@@ -40,6 +40,14 @@
|
||||
<th scope="row">Installation</th>
|
||||
<td>{{ object.installation|linkify }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Tenant Group</th>
|
||||
<td>{{ object.tenant_group|linkify }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Tenant</th>
|
||||
<td>{{ object.tenant|linkify }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Stored location</th>
|
||||
{% if object.stored_location_url %}
|
||||
|
||||
@@ -2,6 +2,7 @@ from django.test import TestCase
|
||||
|
||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from virtualization.models import Cluster, ClusterType, VirtualMachine
|
||||
|
||||
|
||||
@@ -26,6 +27,8 @@ class SlmBaseTestCase(TestCase):
|
||||
cls.vm = VirtualMachine.objects.create(name="test VM")
|
||||
cluster_type = ClusterType.objects.create(name="test cluster type")
|
||||
cls.cluster = Cluster.objects.create(name="test cluster", type=cluster_type)
|
||||
cls.tenant_group = TenantGroup.objects.create(name="test tenant group", slug="test-tenant-group")
|
||||
cls.tenant = Tenant.objects.create(name="test tenant", slug="test-tenant", group=cls.tenant_group)
|
||||
|
||||
cls.software_product = SoftwareProduct.objects.create(name=cls.p_name)
|
||||
cls.software_product_version = SoftwareProductVersion.objects.create(
|
||||
@@ -40,6 +43,8 @@ class SlmBaseTestCase(TestCase):
|
||||
version=cls.software_product_version,
|
||||
installation=cls.software_product_installation,
|
||||
stored_location_url=cls.test_url,
|
||||
tenant_group=cls.tenant_group,
|
||||
tenant=cls.tenant,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -48,6 +53,8 @@ class SlmBaseTestCase(TestCase):
|
||||
SoftwareProductInstallation.objects.all().delete()
|
||||
SoftwareProductVersion.objects.all().delete()
|
||||
SoftwareProduct.objects.all().delete()
|
||||
Tenant.objects.all().delete()
|
||||
TenantGroup.objects.all().delete()
|
||||
Cluster.objects.all().delete()
|
||||
ClusterType.objects.all().delete()
|
||||
VirtualMachine.objects.all().delete()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
|
||||
from .base import SlmBaseTestCase
|
||||
|
||||
|
||||
@@ -56,3 +59,15 @@ class ModelTestCase(SlmBaseTestCase):
|
||||
self.software_license.stored_location = "GitHub"
|
||||
self.software_license.save()
|
||||
self.assertEqual("GitHub", self.software_license.stored_location_txt)
|
||||
|
||||
def test_software_license_tenancy(self):
|
||||
self.assertEqual(self.tenant_group, self.software_license.tenant_group)
|
||||
self.assertEqual(self.tenant, self.software_license.tenant)
|
||||
|
||||
def test_software_license_rejects_tenant_from_other_group(self):
|
||||
other_group = TenantGroup.objects.create(name="other group", slug="other-group")
|
||||
other_tenant = Tenant.objects.create(name="other tenant", slug="other-tenant", group=other_group)
|
||||
self.software_license.tenant = other_tenant
|
||||
|
||||
with self.assertRaisesMessage(ValidationError, "does not belong to the selected tenant group"):
|
||||
self.software_license.full_clean()
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
git clone -b release https://github.com/netbox-community/netbox-docker.git
|
||||
cd netbox-docker
|
||||
tee docker-compose.override.yml <<EOF
|
||||
version: '3.7'
|
||||
services:
|
||||
netbox:
|
||||
ports:
|
||||
- 8000:8080
|
||||
postgres:
|
||||
ports:
|
||||
- 5432:5432
|
||||
redis:
|
||||
ports:
|
||||
- 6379:6379
|
||||
EOF
|
||||
docker-compose pull
|
||||
docker-compose up
|
||||
Reference in New Issue
Block a user